The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
questdb.io
questdb.io
I know it's a cliche that someone brings up Rust in any programming thread, but I can't help myself. ;-) Several of the techniques here are easy enough in Rust that I just do them routinely.
* Rayon makes it easy to process line-wise in reasonable-sized chunks without allocating a string per line.
* The standard integer parsing stuff takes a slice, not an owned string.
* The standard hash map doesn't have the same expectation "that we pass in an instance of the key class that is equal to an existing instance"; you can call HashMap<String>::get with a &str. (One limitation is that std::collections::HashMap::entry does expect a K, so there's some redundancy if you need to insert afterward, but you could drop to hashbrown::HashMap::raw_entry_mut to avoid that.)
HashMap was quite a bottleneck in Rust as well, for many reasons, not just the key allocation problem. But it was very easy to implement the same kind of custom hashtable, which almost by default ends up having a better memory layout than in Java.
https://github.com/mtopolnik/rust-1brc/blob/main/src/main.rs
I bet I could improve on it just a bit and match the Java time. Not sure about topping it, though.
You end up either having to do two lookups or always cloning the string even if you ended up not needing it.
https://doc.rust-lang.org/std/collections/hash_map/struct.Ha...
You might be getting confused because in that example the `HashMap` keys are references.
I'd be curious to hear more. Other than the default hash function being quite expensive, and occasionally wanting to use the more expressive hashbrown API, I've been happy with Rust's std::collections::HashMap.
To be fair, I didn't try out the hashbrown API. Maybe that, together with FxHash, would have been enough for the official dataset with 97% of keys < 16 bytes. But, I was optimizing for the "10k" dataset as well, with a lot of longer keys, and hashing just the first 8 bytes was the winning idea.
That's just a custom hash function, though; couldn't you do that with the standard std::collections::HashMap?
I took part in the One Billion Row challenge (1BRC). It was a lot of fun, but also a great learning experience. People came up with some pretty incredible optimization tricks. When you put them all together, it's a huge number, and they are all mingled up in individual solutions. They also happen on many levels -- from quite high, to incredibly low and detailed.
In retrospect, I could see there was a good number of tricks that are relatively easy to grasp, and reusable in other projects. I felt the urge to do a writeup that captures this knowledge in one place, isolating and explaining each of the tricks.
if you're parsing the file row by row, iterating over the trie as you process each character (as they argue to calculate the int value) (so what you have to do to hash it anyways), should be similar. What you'd end up in is micro-architectual issues on cache performance.
Tries have to allocate, walk, and interpret multiple nodes. Perhaps not as bad as trees (though that depends on how many possible trie node representations there are, vs what the data density is at each level), but still worse than the non-colliding hash.
That said, with a finite dataset a perfect hash would probably beat a general hash though.
Kind of like linked lists...conceptually pleasant but rarely if ever the best option for real-world perf.
But you're right about the contest -- each program was measured five times in a row, and there was enough RAM to fit the entire file into the page cache.
The best time using all 32 cores (64 hyperthreads) on the evaluation machine was 323 milliseconds.
Results are determined by running the program on a Hetzner AX161 dedicated server (32 core AMD EPYC™ 7502P (Zen2), 128 GB RAM).
Programs are run from a RAM disk (i.o. the IO overhead for loading the file from disk is not relevant), using 8 cores of the machine.
up to 6.6 seconds variant, i see the point of pushing the envelope of the programming environment and hacks that might help optimize future builds of the language. but beyond that the optimizations seems to be more about overcoming the inherent limitations of the environment, which are due to conciously made tradeoffs.
i feel that once you hit that limit, we have reached the upper limit of the competition.
https://github.com/noahfalk/1brc
a bit faster than fatest java still
So .NET has quite a few tricks that on Java side still require FFI, or clever use of Panama, maybe.
And it depends on Valhala being done.
In reality, though, data always have errors, and you get many validity checks in there, and data is never only ascii, but full of Unicode.
I so wish python would come with the equivalent of jvisualvm in its “batteries included” (not even talking about better profilers even).