237 karma · joined February 3, 2012
There are lots of cases in the allocator that are conditional on size class (tiny allocations have a bunch of special cases, as do large allocations). Pointer/no-pointer allocations also have lots of special cases.
In the specialized functions, these conditionals become constant and lots of code falls away. We don’t pay the cost for those comparisons anymore, plus since code size is smaller it is now more reasonable to inline callees a bit more aggressively.
Michael also put a lot of time into finding the right balance of code size. At first you want to specialize everything, but that is going to increase code size a lot and thus hurt icache performance. If I recall correctly, our initial version had a code size increase ~4x higher than what we ended up with in 1.27. The final version specializes far fewer size classes yet actually had better performance than the original version.
Regarding the ABI, we definitely see an improvement with static calls vs computing the size class in the runtime. If I recall correctly, it’s the indirect call to the specialized function through a lookup table that stalls the dynamic case.
https://lwn.net/Articles/1057561/ which goes on to explain that this was the motivation for git history.
-Joseph Heller, Catch-22
(It's basically an easter egg, but if you look at the source of the release notes, you will see that most entries in the release notes have an HTML comment referencing the Go CL or issue that the entry refers to.)
Not in New York, unfortunately. All of the stations have cell service, and one tunnel (14th Street L train tunnel under the East River), but everywhere else has no service between stations. It’s an annoying limitation that most cities seem to have fixed by now.
It certainly may be useful to define an specific ordering. Maps ordered by insertion time and maps ordered by key order are fairly common. But maps with these constraints probably won't be as fast as those with arbitrary ordering, so there is a trade-off decision to be made. A map constrained to the arbitrary ordering of the initial implementation is the worst of both worlds: difficult to make faster despite not having a particularly useful ordering.
As a concrete example, I am currently in the middle of rewriting Go's map implementation to use a more efficient design (https://go.dev/issue/54766). If Go hadn't randomized map iteration order this work would likely be impossible. It is unlikely we could completely change the design while keeping iteration order identical to the old implementation (assuming we want the new version to be faster at least).
That said, I don’t think this bill would target something like that, as it clearly discusses the “size” and “amount” of a product.
[1] https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/...
I'll note that while all of gVisor's user-mode Linux is in the same Go module, we've actually gone to decent lengths to keep the network stack logically separate from the rest of the user-mode Linux code.
So while go.sum might look a bit frightening, Brad's depaware shows that the extra code you pull in to binaries by using netstack is actually quite minimal: https://github.com/tailscale/tailscale/commit/5aa5db89d6a9a6....
https://developer.arm.com/docs/ddi0595/h/aarch64-system-regi...
So e.g., on x86 if you store to A then store to B, then if another core sees the store to B it is guaranteed to see the store to A as well. This guarantee does not exist on ARM.
gVisor is not a rewritten version of an internal tool. The code you see really does run in production for App Engine and Cloud Run. While there are some internal modifications to better integrate with internal infrastructure, the vast majority of the code is identical to open source, critically including all of the system call handling, filesystem, and memory management code.
While browsing through our issues will show that we still have plenty to work on, the vast majority of applications work well inside gVisor.
> how do you implement features that ostensibly require calls to the OS anyways?
gVisor's kernel is a user-space program, so it can and does make system calls to the host OS. Some examples:
* An application blocks trying to read(2) from a pipe. gVisor ultimately implements blocking by waiting on a Go channel. The Go runtime will ultimately implement this with a futex(2) call to the host OS. * An application reads from a file that is ultimately backed by a file on the host (provided by the Gofer [3]). This will result in a pread(2) system call to the host.
The purpose here isn't to avoid the host completely (that's not possible), but to limit exposure to the host. gVisor can implement all the parts of Linux it does on a much smaller subset of host system calls. Anything we don't use is blocked by a second-level seccomp sandbox around the kernel. e.g., the kernel cannot make obscure system calls, or even open files or create sockets on the host (those operations are controlled by an external agent).
[1] https://github.com/google/gvisor/tree/master/test/syscalls/l...
gVisor actually intercepts and implements the system calls in the user-space kernel. Two specific goals of gVisor are that (1) system calls are never simply allowed and passed through to the host kernel, and (2) you don't need to write a policy configuration for your application; just put your application inside gVisor and go. These are significant differences over simply using something like seccomp on its own (what the architecture guide calls "Rule-based execution").
Some of this is covered in our security model: https://gvisor.dev/docs/architecture_guide/security/#princip...