← Back to context

Comment by carefree-bob

17 days ago

Yes, I know Go's structs are similar to C in terms of syntax, but does the Go compiler guarantee the same bitwise layout for its data structures? Most GC languages add metadata to the data structures to track GC status, and this changes both the memory layout and the word alignment, which then sometimes forces the language to add extra padding to maintain alignment. And this nests as you put one struct inside another, or an array inside a struct.

Now you have "fat arrays" and "fat structs", so instead of grabbing a pointer and loading the next 128 bits into memory and doing an operation, you have to grab the pointer, read out data from individual elements, combine them, create a new element with the combined data, and then you have a 128 bits. But even then, you don't know whether you have 128 bits or not. Some gc-specific metadata might have been added by the compiler (and probably was).

Bottom line, it's very hard in the GC world to have bit-wise control over memory layout, even if user-level syntax of "structs" is the same. And one consequence of that is that you can't just "do" SIMD in Go. You have to wait for Go to expose a library that does this for you, and you will always be limited by what types of unpacking/repacking the language designers allowed you to do.

Or, you are stuck with hoping the compiler is very smart, which is never the case and requires huge compile times for marginal gains in compiler smarts.

So it's not about GC collection pauses so much as no longer having access to memory layouts.

> does the Go compiler guarantee the same bitwise layout for its data structures

It probably won't be fully 1:1 with C, but it's good enough that you can write code like this and it works: https://github.com/fsnotify/fsnotify/blob/main/backend_inoti... (unix.InotifyEvent is just a Go struct: https://pkg.go.dev/golang.org/x/sys/unix#InotifyEvent)

> Now you have "fat arrays" and "fat structs", so instead of grabbing a pointer and loading the next 128 bits into memory and doing an operation, you have to grab the pointer, read out data from individual elements, combine them, create a new element with the combined data, and then you have a 128 bits.

That is not how it works, you get real pointers that you can even do math with using unsafe package.

> Most GC languages add metadata to the data structures to track GC status, and this changes both the memory layout and the word alignment, which then sometimes forces the language to add extra padding to maintain alignment

Go GC uses a separate memory region to track GC metadata. It does not embed this information into structs, arrays, etc, directly.

> And one consequence of that is that you can't just "do" SIMD in Go. You have to wait for Go to expose a library that does this for you, and you will always be limited by what types of unpacking/repacking the language designers allowed you to do.

You very much could, thanks to what I described above. You'll have to write assembly (Go supports assembly), and it's even used in some e.g. crypto libraries not just for performance reasons, but to ensure constany-time operation too.

The downside of using assembly is that it doesn't support inlining, and there's a small shim to keep ABI backwards compatible with the original way functions were called (using stack, whereas newer ABI uses registers). So you need to write loops in assembly too to eliminate the function call overhead. The SIMD package solves this issue by allowing code inlining.

  • > Go GC uses a separate memory region to track GC metadata. It does not embed this information into structs, arrays, etc, directly.

    I didn't know this, thank you. That's a solid approach.