11 comments

[ 2.1 ms ] story [ 33.6 ms ] thread
Off-topic, but I seriously dislike the syntax of Rust. It is chaotic and mind-boggling to me. See the "insert" function.

Good post though.

As a denser gas, Ozone would have greater friction getting through small pores, so that would be one example?
I thought this would be an article about how ozone is heavier than oxygen and the molecules move slower or something.

I found that surprising, but not as surprising as suddenly reading about Rust!

I love these type of comments! Thank you.
-O3 -march=haswell

The second one is your problem. Haswell is 15 years old now. Almost nobody owns a CPU that old. -O3 makes a lot of architecture-dependent decisions, and tying yourself to an antique architecture gives you very bad results.

I do. Had to replace the plastics of the laptop and the screen's definition is unacceptable but with Linux and a SSD it's still fine for basic computer usage. Not my daily driver but kept it as a daily drivers for 10 years.
It seems to me that if there is one way that is always faster it would hardly deserve a secret setting, it should be the default?
While working on my game, I tried implementing a custom priority queue.

It ended up being > 2x faster in debug build, but 2x-5x slower in the release build (??!!?) [1]. I haven't learned much about compilers/"lower level" C++, so I moved on at that point.

How it worked:

1.) The P.Q. created a vector and resized it to known bounds.

2.) The P.Q. kept tract of and updated the "active sorting range" each time an element was inserted or popped.

2B.) So each time an element is added, it uses the closest unused vector element and updates the ".end" range to sort

2C.) Each time an element was removed, it updated the ".start" range

3.) In theory this should have saved reallocation overhead.

[1] I believe Visual Studio uses -O0 for debug, and -O2 for release.

In the branchy function, id is only compared if distance is equal, and since distance is a random float, this almost never happens and the corresponding branch is nearly perfectly predicted. The branchless function always compares both id and distance, effectively doing twice the work. It's only part of the reason why there's a 2x performance difference, but I thought it was interesting.

I really don't know how LLVM picks between branches or conditional moves, but my guess is that it doesn't assume that float equality is any less likely than other conditions, and some optimization pass in O3 turns unpredictable branches into conditional moves. I base this on the fact that adding std::hint::unlikely to the "equal" branch produces the same assembly for the function in both modes.

https://godbolt.org/z/erGPKaPcx

Whether it's safe to assume in general that float equality is unlikely for the purpose of program optimization, I'll leave to the compiler engineers. If you know the data your program will be handling, adding hints will avoid these surprises.

Great post.

I wonder whether profile-guided optimization would have led LLVM to select a better (or worse) decision.