3 ms·
> I'm sorry, but the system call "calling convention" is different too. Sufficiently different to not be a problem? As I understand it system calls don't usual
by smegel 11y ago
> I'm sorry, but the system call "calling convention" is different too.
Sufficiently different to not be a problem? As I understand it system calls don't usually rely on the stack whereas C calls do, and it is the stack mismatch between C and Go that is the issue.
> but it's sufficiently low-overhead that in most cases you can ignore the overhead
Maybe for user called function...but if every single system call had to swap out the stack surely that would have a pretty heavy impact...
- geofft 11y agoThe Go calling convention passes everything, in both directions, on the stack. Most platform-native ("C") calling conventions put as many things in registers as possible, and reuse one of the registers for the return value when possible. However, this is just a convention from one function to the next, and they can share the same stack; the caller of a C standard library function merely needs to know what to put on the stack and what to put in registers (and which registers to save). And in fact, the C calling convention is more efficient than Go's from the caller's point of view (fewer things have to go on the stack, some registers are promised not to be clobbered), so calling a C library function with the native calling convention is, on all reasonable platforms, a fine idea performance-wise. There is the problem of the stack itself. The C model envisions a large, contiguous stack, possibly with some kernel magic to grow the stack as you approach the end (by handling page faults). Go previously used "segmented stacks", essentially a linked list of heap-allocated buffers used as stack, where functions knew how to allocate and deallocate segments when necessary. C doesn't know how to do that, so a C call previously would have to allocate plenty of memory just in case the function wanted to use a bunch of stack space. Go no longer uses segmented stacks, so that's essentially not a concern. Go still does reallocate stacks, but does so by copying the entire stack, and the stack is basically never freed. So there might be a one-time performance penalty on the first C call (unless the compiler is smart enough to tell when a Goroutine might possibly call into C, and pre-allocate that space), or the first C call after most of the existing stack was used in Go code, but not after that. tl;dr: The C calling convention is strictly faster than the Go one (but more complicated), and in Go 1.3+ the existing stack is fine to use, so there shouldn't be performance concerns. (Unless, of course, you think the C library itself is slow. But that's a very different problem.)
- 4ad 11y ago> and the stack is basically never freed Not true, stacks are still freed if utilisation drops below a certain percentage. > So there might be a one-time performance penalty on the first C call [...] in Go 1.3+ the existing stack is fine to use Not true, contiguous stacks start even smaller than segmented stacks, also they can't be used for C code at all. Only type-safe code with stack maps can run on the goroutine stacks, because the precise garbage collector has to scan the stack and has to understand where on the stack there are pointers and where there aren't. C code breaks this requirement. For calls into C, the thread stack is used, which in Go usually has 32kB, but varies on some platforms, for example on Windows, it's 256kB IIRC. The runtime switches the stack (which only takes a few instructions), does the ABI translation and calls the function. The stack switching mechanism and ABI translation mechanism have trivial overhead, most of the overhead comes from runtime bookkeeping, e.g. the runtime must ensure there are always enough threads to run user-level Go code.
- geofft 11y ago> also they can't be used for C code at all. Only type-safe code with stack maps can run on the goroutine stacks, because the precise garbage collector has to scan the stack and has to understand where on the stack there are pointers and where there aren't. C code breaks this requirement. Is this still true of C code that doesn't call back into Go code, like libc itself? The Go garbage collector will never get triggered, and any Go code should treat anything below %esp after the call completes as uninitialized data. (Also, on Windows, don't actual system calls reuse the same stack, too?) It seems like if you wanted to design Go for dynamically linking to libc instead of making raw system calls, it wouldn't be difficult.
- 4ad 11y ago> Is this still true of C code that doesn't call back into Go code, like libc itself? The Go garbage collector will never get triggered, and any Go code should treat anything below %esp after the call completes as uninitialized data. I should give a more thorough explanation. If you call C code, the only way that would be useful is if we pass Go-allocated buffers to the C code. This only works if we pass to the C code untyped uintptr's, which must live on the stack because of garbage collector reasons. In your hypothetical world, you want to call C code on the g stacks. This can only works if the g stacks are copied into a larger stack. The stack copying mechanism inspects the stack map to see which pointers need updating. This stack map is also used by the garbage collector, but I shouldn't have mentioned it in my original post. The stack copying mechanism can't update the untyped uintptr's living on the stack, therefore you can't call C code on the g stack. There are other reasons why calling C code on the g stack would be a bad idea but I won't get into them. Final point is, C code is called on the system stack. > (Also, on Windows, don't actual system calls reuse the same stack, too?) Which stack and which system calls? On Windows, target calls are made on the g0 stack, which is the thread stack. These calls are just calls into regular C code, most of it in kernel32.dll, some of it in ntdll.dll. This C code will eventually do system calls. Those actual system calls don't need any stack.
- 4ad 11y ago> Sufficiently different to not be a problem? As I understand it system calls don't usually rely on the stack whereas C calls do, and it is the stack mismatch between C and Go that is the issue. Switching the stack only takes a few machine instructions. Most of the overhead comes from runtime bookkeeping, in contrast ABI translation and switching the stack take a trivial amount of code and time. > Maybe for user called function...but if every single system call had to swap out the stack surely that would have a pretty heavy impact... As mentioned, on some platforms, for example Solaris and Windows, this always happens, and even on other platforms, in some conditions, it happens too. Not to mention that the runtime switches stacks for its own reasons all the time. The overhead is totally dwarfed by the cost of doing system calls in the first place.