Looks nice. Some explanation for those of us not familiar with Go would've been more educational. Could be future posts, I suppose.
Was there any particular part that felt like it needed more explanation?
31–40 of 88 posts
Looks nice. Some explanation for those of us not familiar with Go would've been more educational. Could be future posts, I suppose.
Was there any particular part that felt like it needed more explanation?
> False Sharing : "Pad for concurrent access: Separate goroutine data by cache lines" This is worth adding in Go race detector's mechanism to warn developer
Most modern processor architecture CPU cache line sizes are 64 bytes, but not all of them. Once you start to put performance optimizations like optimizing for cache line size, you're fundamentally optimizing for a particular processor architecture. That's fine for most deployments, since the vast majority of deployments will go to x86_64 or arm64 these days. But Go supports PowerPC, Sparc, RISCV, S390X... I don't kno…
If you use these tricks to align everything to 64-byte boundaries you'll see those speedups on most common systems but lose them on e.g. Apple's ARM64 chips, and POWER7, 8, and 9 chips (128 byte cache line), s390x (256 byte cache line), etc. Having some way of doing the alignment dynamically based on the build target would be optimal.
If you are worrying about cache structure latencies in Go, maybe you should just be using Rust or Zig instead that implicitly handle this better.
Not necessarily: you can go quite far with Go alone. It also makes it trivial to run "green threads" code, so if you need both (decent) performance and easy async code then Go still might be a good fit. Despite Go being pretty high level GC language on the surface it actually allows you to control stuff like struct layout, CPU affinity, etc, which typically matter more for performance than just a programming language…
Earlier quoted context omitted.
Not really, virtually all these patterns involve tradeoffs that require understanding the data access patterns. I don't want my compiler adding more padding than bare minimum to every struct. I don't want it transforming an AoS to SoA when I choose AoS to match data access patterns. And so on... At best Go could add some local directives for compiling these optimizations, but these code changes are really minimal any…
I could imagine some kind of compiler declaration in C that would do something like specify break points - sort of like page breaks - for structs, or tell the compiler to automatically pad structs out so that components are on page boundaries, cache line boundaries, etc. Sort of "If we're not properly aligned, add whatever padding you think is best here". I guess this is largely provided by std::hardware_destructive_…
struct foo {
_Alignas(64) float x,y;
_Alignas(64) int z;
};
_Static_assert(sizeof(struct foo) == 192, "");Source code of the benchmarks? At least, the False Sharing and AddVectors trick don't work on my computer. (I only benchmarked the two. The "Data-Oriented Design" trick is a joke to me, so I stopped benchmarking more.) And I never heard of this following trick. Can anyone explain it? // Force 64-byte alignment for cache lines type AlignedBuffer struct { _ [0]byte // Magic trick for alignment data [1024]float64 } Mayb…
I can't find any claim anywhere else about the [0]byte trick, and in fact my own experiments in the playground show that it doesn't do anything. If you embed an AlignedBuffer in another struct type, with smaller fields in front of it, it doesn't get 64-byte alignment. If you directly allocate an AlignedBuffer (as a stack var or with new), it seems to end up page-aligned (the allocator probably has size classes) regar…
Earlier quoted context omitted.
I can't find any claim anywhere else about the [0]byte trick, and in fact my own experiments in the playground show that it doesn't do anything. If you embed an AlignedBuffer in another struct type, with smaller fields in front of it, it doesn't get 64-byte alignment. If you directly allocate an AlignedBuffer (as a stack var or with new), it seems to end up page-aligned (the allocator probably has size classes) regar…
They meant "[0]uint64" probably, not 0[]byte.
Regarding AoS vs SoA, I'm curious about the impact in JS engines. I believe it would be a significant compute performance difference in favor of SoA if you use typed arrays.
Overall great article, applicable to other languages too. I'm curious about the Goroutine pinning though: // Pin goroutine to specific CPU func PinToCPU(cpuID int) { runtime.LockOSThread() // ... tid := unix.Gettid() unix.SchedSetaffinity(tid, &cpuSet) } The way I read this snippet is it pins the go runtime thread that happens to run this goroutine to a cpu, not the goroutine itself. Afaik a goroutine can move from o…
`runtime.LockOSThread()` will pin the current goroutine to the os thread that its currently running on