HNHacker News
TopNewBestAskShowJobs

haxen

174 karma · joined April 1, 2019

submissionscomments
haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
Can you provide more details on how that would work? Given that the input is CSV rows and not an array of integers alone, and the fact that getting to the next row is dependent on finding the delimiter in the current row.
haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
I think it would work if that was the only code in the loop. But the loop spans several more nontrivial operations, including hashtable insertion.
haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
> maybe 4 cycles worth in this optimized version?

It's quite a bit more than that, just the code discussed in the post is around 20 instructions, and there's a bunch more concerns like finding the delimiter between the name and the temperature, and hashtable operations. All put together, it comes to around 80 cycles per row.

When explaining the timing of 1.5 seconds, one must take into account that it's parallelized across 8 CPU cores.

haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
I wonder what you mean here. What code exactly would get auto-vectorized? Parsing the temperature surely not, since it's not in an array of fixed-size fields.
haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
There are many variations of the original code used in different solutions. Many of them return the temperature like the variant used in the post, but they split out the part that finds the decimal dot into a separate function. Then you can reuse that twice: to finish parsing the temperature, and to advance the cursor to the next row.
haxen··on 1BRC merykitty's magic SWAR: 8 lines of code explained in 3k words
The temperature fields are interleaved with name fields, so I don't think you'd get any extra benefit from SSE. Also, since the temperature field is variable-sized, it would probably not pay off even if it was stored by column.

SSE was successfully applied to finding the delimiter between the name and the temperature, though.

haxen··on 1BRC merykitty’s magic SWAR: 8 lines of code explained in 3k words
This post explains a piece of code that appeared on the recent One Billion Row Challenge, it parses a variable-layout string whose bytes are packed into a single 64-bit variable, and doesn't use any if statements to achieve it. The string represents a decimal number from the range [-99.9, 99.9].

The general class of techniques used is called SWAR -- SIMD Within A Register, because it uses the regular ALU instruction set to achieve the effect of SIMD (Single Instruction, Multiple Data).

The code in question was submitted by Quan Anh Mai (@merykitty on GitHub).

haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
It was known, but the requirement was that the program keep working for an arbitrary keyset that conforms to the specified rules (up to 10,000 unique keys, each up to 100 bytes in length).
haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
I didn't bother to try, so not sure. There would probably be some challenges and I don't see how I'd accomplish it without some branch instruction in the custom hasher.
haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
Running without RAM cache would be a great followup to this challenge. I think a time around 2-3 seconds should be achievable. But, it would be highly sensitive to the hardware setup and how well the disk-reading code is placed on cores relative to the connection to the disk. Not sure what it would take to allow hundreds of contestants to benefit from the best arrangement.
haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
I used to benchmark a lot on an enterprise-grade SSD 10 years ago, and that was already at 2 GB/s. Today, even my laptop's SSD supports multiple GB/s.

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.

haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
Thanks for the praise Gunnar, but we all owe it to you for organizing it, and especially sticking through thick and thin when it took off, and needed lots of attention to evaluate everyone and maintain a level playing field!
haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
The reason the custom hashtable wins out isn't something generally applicable. For the very specific dataset used in the challenge, the hash function could be radically simplified, to just a single multiply and rotate left.

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.

haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
I actually wrote a Rust version as well, and yes, it was far easier to write, far less code (although not incorporating all the tricks), completely safe, and pretty fast -- but still 2x slower than my end result in Java.

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.

haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
Interestingly enough, that was my first idea. But when you consider the tiny keyset size, it would be hard to beat two machine instructions to calculate the hash + a single array lookup.
haxen··on The Billion Row Challenge (1BRC) – Step-by-Step from 71s to 1.7s
Cool, I see mfiguiere linked to my recent blog post! Let me share a few words about it...

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.

haxen··on Show HN: Jet – in-memory, fault-tolerant, distributed stream processing
Here are some examples where user consent is undisputed: ride hailing, bicycle rental, street navigation, running/biking/sailing contests, location-sensitive searches. These are the kinds of applications for which Hazelcast Jet offers easy scaling into millions of users.
haxen··on Show HN: Jet – in-memory, fault-tolerant, distributed stream processing
This statement comes from our benchmarking work:

https://jet-start.sh/blog/2020/06/09/jdk-gc-benchmarks-part1

The point is that Jet can track several million distinct keys, even on a single machine, and finding velocity vectors boils down to linear regression sliding window against two FP variables.

If your concern is why you would specifically want to track locations, the answer is that there are plenty location-based apps that track locations with user's consent.

haxen··on Show HN: Jet – in-memory, fault-tolerant, distributed stream processing
An Apache Beam Runner is already implemented in Jet: https://beam.apache.org/documentation/runners/jet/

Beam is just an API layer with different backing implementations. But you don't typically use Beam to work with Jet, instead you use its own Pipeline API which is mostly like Java Streams. Jet will also soon get an SQL API.

haxen··on Show HN: Jet – in-memory, fault-tolerant, distributed stream processing
Hazelcast Jet will get an SQL API soon, and we're actively considering first-class support from other languages as well.
haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
I concur with your point about the programming model and style, but I do also maintain that "cooperative" vs. "preemptive" is not about that difference. It is a technical difference on how the system interleaves threads and whether it needs cooperation from the code running on them, and not on the programming model and critical sections.

For the distinction you have in mind, I see the terms "colored" vs. "non-colored functions" to be used the most, and they are both within the "cooperative multithreading" space.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
I think the distinction is pretty clear: either the mechanism requires cooperation by the application thread (which typically initiates the yield at a compiled-in, predefined point), or it doesn't and the runtime environment preempts it from the outside.

Virtual threads are of the former kind. (At least as long as we don't involve the forced preemption feature).

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
"processes voluntarily yield control periodically or when idle or logically blocked in order to enable multiple applications to be run concurrently."

This tells me virtual threads (without forced preemption) are cooperative.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
Without the very last point, forced preemption, they would indeed be cooperative because not calling any blocking method would make them non-cooperative.

This is exactly the same as within the "colored" subspace in a language that has this distinction. As long as all the functions you call are suspendable, you equally have no idea which one will actually suspend.

So, without forced preemption based on GC safepoints, and as long as there is any blocking operation left in the library, Loom qualifies as a cooperative multithreading system.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
> you are actually going to allocate both the parent and child.

You're going to allocate a single memory block that contains all the state of that object, which of course includes the superclass state. But that has nothing to do with the depth of the class hiererarchy.

> you're gonna be allocating functions everywhere as well.

If by "function" you mean a "Java method", they are code and not data, and they are not allocated dynamically at all.

In any case, this would have nothing to do with them being private or final. These options decide whether there will be an invokevirtual or invokespecial instruction to call them, where invokevirtual has more cost only before getting JIT-optimized.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part2.h...

Yes it can and actually it's quite low-hanging fruit on the JVM. GC safepoints are already there, you just have hook into the mechanism.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
>They do explicitly yield

Arguably, virtual threads also explicitly yield by calling one of the blocking methods in the JDK. This is very similar to putting all the bottom-level suspendable functions into the Kotlin standard library.

>virtual threads, which behave more like Go's goroutines or Erlang's processes.

I think this can be summarized as "Kotlin uses colored functions and Loom uses non-colored ones". This is a well-established core difference, I thought you had something else in mind with "explicit yield".

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
A month ago we benchmarked against the master of the ZGC repo. There wasn't a big difference.
haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
> ZGC will have a <1ms worst-case latency

In our benchmarks we never saw a GC pause of more than 2 ms on either ZGC or Shenandoah, but the end-to-end latency, the one the user cares about, is impacted by much more than a single GC pause. Sometimes there would be several pauses in a rapid sequence, or just the background GC thread would do too much work at once.

Even after dedicating a core or two to the GC, you still face the issues of cache pollution and RAM throughput stealing that heap walking incurs.

haxen··on Sub-10 ms Latency in Java: Concurrent GC with Green Threads
For < 10 µs latency there are hoops in any language, as well as the OS, like thread-pinning, marking CPU cores unusable for OS interrupt handling, all kinds of virtual memory issues, etc. And you can't afford a single network hop or even SSD access.
Page 1 of 2Next →