Making Faster B-Trees with Go Generics
scylladb.com
scylladb.com
Replacing interfaces is only part of the performance improvement. When it comes to the B-Tree, you want to improve data locality as much as possible. My implementation also replaced slices with arrays (again using code gen to specify the tree degree). This means each node on the heap literally contained all the keys and, and in some cases, the values themselves. It also helped to split the key/values into separate arrays so all the values didn't interlace the keys.
The insert/retrieval benchmark times compared to the open source version were significantly faster. Since this version relied on codegen, I never bothered to push it publicly. Though, there are tons of B-tree implementations on github, each touting themselves as fastest.
But honestly, I really don’t understand why Go became this hyped.
This is of course still very exciting to see though! It's a great demonstration that there's some data structures that was impossible to implement properly (and generically) without native support in the language.
The Taming of the B-Trees https://www.scylladb.com/2021/11/23/the-taming-of-the-b-tree...
"B+-trees have been supported since ScyllaDB Open Source 4.3, and our B-tree implementation was added in release 4.5. They are hidden, under-the-hood optimizations ScyllaDB users benefit from as we continue to evolve our infrastructure."
Go's purpose is to be easy to learn, not to write high performance code.
But is BTree[int] really a real world test? Wouldn’t BTree[*struct {…}] be more common use-case? I wonder what the stats look like for that (with and without pointer indirection)
type RingEntry struct { node *Node token Token }
There are few nodes and many tokens.
So if you're sorting the keys and swap two of them, you make the same change to the values.
I feel like so often, online discussions about these things become wars between the "X is the best thing ever and all the things you think are faults are actually features" camp and the "X is the worst thing ever because it lacks [single critical feature]" camp.
Don't worry, the anti-generics crowd is still here, waiting to see how things shake out. It will take years before we can confidently assess the impact of generics on the language and ecosystem.
For example I used to get a lot of pushback from the go community when using an assertion library, for those not familiar, go's testing library doesn't have support for asserts so you end up writing a lot of code like
if want != got {
t.Fatalf(....)
}
this gets esp annoying as you deal with more complex objects. Many in the go community criticize using an assertion library to write simple assertions as in other languages for your tests, citing that the go team members are against casually bringing dependencies in your project. Not realizing the intent Go Team's "statement against dependencies" is a targeted word of caution urging people to think about the dependencies they bring into their project rather than a blanket attitude against dependencies.Almost certainly Go Team would not advise against bringing in a build time time dependency that is not even gonna be part of the binary you ship that will make your tests easier to write and error messages consistent.
Citation needed. Afaik the actual reason was general lack of understanding how to effectively implement it within constraints of Go’s philosophy
Contrary to your first statement though, there’s very little turnabout in the Go community and your Go-hating child posts fit into the two camps you mention at the end. We wanted it too, even feeling hamstrung!
I’m an old Gopher and I’ve spent a lot of my career in C++ and, now, Rust, so it’s not like I don’t understand generics. But I’ve also seen the space-cadet architectures that come out of overuse thereof. (Rust has a latent version of this, what with Arc<Mutex<T>> being the only way to get things done, come at me).
A common argument from the peanut gallery is that Go folks hate generics. No. I just hate the overly meta use thereof. I’ve long wanted an honest generic in Go for a few container types. This shows up as a desperate need, eh, maybe twice a sizeable project (and in a different way each time!). And I hate working around it but I do.
So it’s disingenuous at best to say Go folks don’t understand generics or are buying a party line thereof. We know it well, and know the nightmares it can lead to. So maybe appreciate some caution in giving the mice a cookie?
This isn’t the first iteration of this war, even. Another thing Go eschewed (that I rarely hear argued) is classes and, particularly, subclasses! Yet, every so often (including a recent project) that would be a really useful paradigm. But OOP is not “in vogue” and so that’s not one of the BS battlegrounds. Point is, all languages have pros and cons and there’s no one right way.
To quote a famous film, “Life is pain. Anyone who says otherwise is selling something” :)
structs and functions on them are basically classes. golang lacks subclasses as you point out, otherwise everything is the same.
What concrete problems caused by the use of generics have you encountered? Do you see those problems coming to Go, as it now has generics, too?
Go seems to me a very verbose language and the addition of generics might improve things a bit in this direction.
Thanks!
-Pete.
But there are two major drawbacks in Go generics now:
1. Methods cannot be generic, so you can’t do any chaining-style interfaces like thing.Map(…).Reduce(…) if you want to output a different type.
2. Possibly even worse: you cannot cast from Container[ConcreteType] to Container[any], or from Container[any] to Container[ConcreteType]. In my limited experimentation this was the most surprising blocker for me since it’s quite natural to work with Container<unknown> in Typescript, and possible (although I’m not sure how idiomatic) to do Container<Object> or Container<*> in Java/Kotlin.
Still when writing “normal” Go (as opposed to trying to abuse the type system), the generic slices package is a noticeable pain relief. But I actually don’t find myself implementing generic containers too much in the Go domain.
nice usage of generics here.
The values have types it's just types aren't visible at compile time but can (must) be queried at run time to do anything useful with those values.