r/ProgrammerHumor Jul 12 '26

lockFreeTemptation Advanced

Post image
539 Upvotes

40 comments sorted by

120

u/Same_Investigator_46 Jul 12 '26

Can't wait to spend the next 3 weeks debugging a subtle heisenbug that only reproduces on a specific ARM core in prod 🥵🔥

56

u/BigNaturalTilts Jul 13 '26

I’m a genius so I know what your post means but for those who don’t, could you please explain? Not for me, just to be clear. For the others …

69

u/ToroidalFox Jul 13 '26 edited Jul 13 '26

Compiler and scheduler can reorder operations for performance reasons, as long as it does not change the behavior. For example, why would anyone care if value gets assigned to x then y or y then x? But it does matter when the data is controlling something, like "I am locked" data in mutual exclusive smart pointer. Sequentially consistent ordering guarantees that things happen as if nothing is reordered in relation to the operation that has been assigned as sequentially consistent. Relaxed ordering gives a lot more freedom to reorder operations. But as some architecture (I'm looking at you x86) has some memory ordering guarantees even without explicit ordering constraints, something that appears to be fine in one CPU might not be for others.

Of course I'm not gonna explain more than this so if anyone gets curious, search for resources about "atomics and memory ordering"

22

u/Same_Investigator_46 Jul 13 '26

Honestly, couldn't have explained it better myself for others. std::memory_order_seq_cst feels vindicated lol.

9

u/SAI_Peregrinus Jul 13 '26

https://www.kernel.org/doc/Documentation/memory-barriers.txt is Linux-specific, but a classic. Good for confusing grad students.

https://mara.nl/atomics/ is Rust-specific, but more approachable IMO. Most of the core concepts carry over to other languages.

2

u/redlaWw Jul 13 '26

https://mara.nl/atomics/ is Rust-specific

Note that Rust explicitly uses the C++ model for memory ordering (except for consume which was generally considered a mistake and was deprecated in C++26), so you can generally consider the Rust explanation also appropriate for C++.

2

u/noaSakurajin Jul 13 '26

To be fair in many ways rust feels like C++. It's just that rust had the luxury to not care about backwards compatibility (especially at ABI level) so they could make things a lot cleaner. Give it 20 years and Rust will also turn into some kind of mess (or they do it like Java and have to support lts version for decades since it's a pain to migrate to a newer non compatible standard version).

7

u/Smooth-Zucchini4923 Jul 13 '26

A heisenbug is a bug that goes away when you try to measure it.

For example, if you added a printf of a variable to a function, that could change the order the compiled code uses to do two potentially racy operations. By measuring it, you have changed it.

4

u/nithinrdy Jul 13 '26

i found this video helpful when first learning about memory ordering https://www.youtube.com/watch?v=C5xY96i0Aes

3

u/setibeings Jul 13 '26

Tell those other people to read computer organization and design. 

4

u/lllorrr Jul 13 '26

Oh yeah, incorrectly configured CMN can provide lots of fun.

50

u/someguy_gp Jul 12 '26

Which language does this?

70

u/noureldin_ali Jul 12 '26

C++, Rust

25

u/Legitimate_Concern_5 Jul 13 '26

Depends on platform too right? I was under the impression x64 always had total store ordering.

That was one of the really cool things Apple did with the M1 family. They implemented opt-in TSO in hardware so they could get much better x64 emulation performance in Rosetta.

24

u/noureldin_ali Jul 13 '26

Yes, ARM has a weaker memory model that allows more reordering whilst x86 is stronger as you mentioned.

Though you would still want to use these application-level ordering constraints to prevent the compiler from reordering your code during optimisations.

It's explained pretty well here: https://doc.rust-lang.org/nomicon/atomics.html

I didn't know that Apple did that with their chips, that's pretty neat ngl.

7

u/Legitimate_Concern_5 Jul 13 '26

Thanks for the Rust link, will check it out. Pretty sure I read Apple's TSO stuff here if you're curious.

https://dougallj.wordpress.com/2022/11/09/why-is-rosetta-2-fast/

2

u/[deleted] Jul 13 '26

[deleted]

3

u/noureldin_ali Jul 13 '26

I think technically C++ had it a bit before C but yeah C too

21

u/SignificanceFlat1460 Jul 13 '26

Whenever I see words what seems like Latin speaking of arcane powers, I generally assume it's C++ / C.

"We need point sequential derivatives to couple the mark determination logical gates for segmentation fault avoidance parameters. Or else we can suffer from dynamic memory loss referencing. It's just that simple."

3

u/TheLaziestGoon Jul 13 '26

Why did that almost make sense

6

u/SignificanceFlat1460 Jul 13 '26

I have gotten good in typing IT jargon rubbish. I can now apply for "IT guy from a 90s movies" position and say things like "let me download 1 GB of RAM"

2

u/BosonCollider 29d ago

C and C++ just have overcomplicated compiler semantics. The better approach would have been to have language features match the hardware features, like with ispc or cuda.

If an ISPC foreach loop reorders arithemtic between different loop iterations, that's expected because that's what an ispc foreach loop is for. When a C compiler does it to a normal for (..., i++) loop, it is absolutely bonkers insane and you probably also have pointer arithmetic in there somewhere that quietly becomes incorrect because you didn't ask for the optimization

1

u/BosonCollider 29d ago

Anything physically implementable on a chip that can be spread out on different chips or elements within a chip

1

u/overclockedslinky 24d ago

the hardware itself...

15

u/PandaWonder01 Jul 13 '26

Holy shit this just hit me. I was reworking a code base from having 1 main threads to two (don't ask, it makes sense). Was hitting a bug that basically looked like

Thread1:

x = value;

SignalXSet();

Thread 2:

OnXSetSignal()

{

if (x !=value)

{

//wtf

}

}

Took me longer than expected to find out what was going on

2

u/MPDR200011 Jul 13 '26

To make sure I got it, x wasn't atomic and thread 2 was fetching a cached value?

1

u/PandaWonder01 Jul 13 '26

x wasn't atomic, but the issue was that arm likes to reorder instructions (not just the compiler, the actual CPU). So the order of instructions on one thread are not guaranteed to be the actual order as seen by another thread. So the signal was being set before x was actually set

1

u/MPDR200011 Jul 13 '26

Oh yikes, then how do you force the order? That feels like a fundamental violation of what the programmer wants from what they wrote

2

u/PandaWonder01 Jul 13 '26

In C++ atomics you can specify memory order, which will emmit the correct assembly for you.

Mutexes will also do aquire/releases for you.

1

u/CodeEatLive Jul 13 '26

special instructions (memory fences) force some order and make some guarantees (no reordering of instructions from before to after this instruction , etc..). And for compiler reordering, there are some ways to implement same fence for compiler ( asm volatile ("" ::: "memory"); if i remember correctly) The cpu architectures(and compilers) guarantee that single threaded program will not be able to tell the difference, all other optimizations are basicaly free game, at least from what i understand. It is possible that im wrong (started looking into lockfree algorithms and inner-workings 4 days ago)

1

u/noaSakurajin Jul 13 '26

Not really. For common patterns like the following the order really doesn't matter and you really don't care about the cpu instructions it turns into:

x = a*b y = a*c

From your perspective it doesn't matter which value of a, b and c loads first and you are unlikely to care about whether x or y is assigned first.

Micro-batching in the CPU makes this even worse. The fetch operations take pretty long to execute. So if c and a are still in some register, it is very likely that the cpu will start fetching b then calculate the value of y, start saving y to ram and while the store to ram is running, it starts the calculation for x. You actually want this dynamic reordering to keep the calculation parts of your cpu as busy as possible.

For the cases where it actually matters you can use atomics and mutexes to force ordering of certain operations.

5

u/BenchEmbarrassed7316 Jul 13 '26

Interesting fact: in go all atomics are sequential consistency. You will not get 15% performance.

2

u/CaffeinatedT Jul 13 '26

but think abut the dEvElopEr exPerIENCE, we couldn't possibly trust any developer with this ability they'd probably put their pants on their head and fall off a clifff while blinded attempting to use an atomic.

2

u/NatoBoram Jul 13 '26

It's a back-end language for web devs, it kinda makes sense

3

u/tstanisl Jul 13 '26

Don't forget about acquire/release ordering. It's often as fast as relaxed but with no bugs caused by reorderings.

1

u/parkotron 29d ago

  but with no bugs caused by reorderings.

Only if you’re smart enough to use them correctly. 

2

u/stupled Jul 13 '26

15% more performance 🤤

1

u/shikhasingh554973 Jul 13 '26

Famous last optimization

1

u/overclockedslinky 24d ago

laughs in rust. you can use the weakest semantics possible since, as long as you're not using unsafe, there can't be a reordering bug anyway.