Live data from Hacker News

CPU cache-friendly data structures in Go

skoredin.pro

31–40 of 88 posts

Re: CPU cache-friendly data structures in Go

#31
post #5

Looks nice. Some explanation for those of us not familiar with Go would've been more educational. Could be future posts, I suppose.

Honestly I don't think there's much in here that's Go-specific other than the syntax itself. I've seen basically the same tricks used in C or C++.

Was there any particular part that felt like it needed more explanation?

Re: CPU cache-friendly data structures in Go

#32
post #15

> 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…

Something like C++17's `std::hardware_destructive_interference_size` would be nice; being able to just say "Align this variable to whatever the cache line size is on the architecture I'm building for".

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.

Re: CPU cache-friendly data structures in Go

#35

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…

In what way does Go have async?

Re: CPU cache-friendly data structures in Go

#36
post #30
post #11

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_…

I think this is _Alignas/alignas.

    struct foo {
        _Alignas(64) float x,y;
        _Alignas(64) int     z;
    };
    _Static_assert(sizeof(struct foo) == 192, "");

Re: CPU cache-friendly data structures in Go

#37
post #25
post #17

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…

They meant "[0]uint64" probably, not 0[]byte.

Re: CPU cache-friendly data structures in Go

#38
post #25

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.

"[0]uint64" only guarantees 8-byte alignment (and only under certain scenarios), not 64-byte.

Re: CPU cache-friendly data structures in Go

#40

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

And prevents other goroutines from running on that thread. I think that’s crucial.
Post reply on HN