News for hackers. Stuff that matters.
Re:No nuances, just buggy code (was: related to Spinlock imp... by kstenerud on Sunday January 05, 2020 @03:26AM (#21960304)
It's important to read Malte's response to Linus:

https://www.realworldtech.com/forum/?threadid=189711&curpost...

Followed by Linus response to Malte:

https://www.realworldtech.com/forum/?threadid=189711&curpost...

The long and the short of it:

- It's nearly impossible to make meaningful benchmarks for this because reality will almost always be different and more complicated

- Calling sched_yield() is almost always a mistake

- Any scheduling optimization is probably a waste of time because either your running env will change, or the kernel will change, or someone else's optimizations will clash with yours.

- Even with decades of research into locking, everyone (including the experts) continually gets it wrong.

- If you're not running a specialized embedded setup where you control all of the running processes and other such variables, just use sleep locks and call it a day.

Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @11:42AM (#21962077)
Basically Linus is right.

One caveat is that calling sched_yield is a fantastic throughput optimization for the spinning portion of an adaptive mutex. If you have parallel code that sporadically contends for short critical sections then my data has always said this:

- something like a futex lock is optimal

- you want barging, not fairness

- spinning the CPU is a t questionable idea even if you do it for a short time. The pause instruction does something like jack and shit. Busy waiting of any kind seems great for microbenchmarks sometimes but it’s pretty bad for anything complex.

- spinning 40 times while calling sched_yield prior to futex_wait (or whatever) appears optimal on many schedulers and benchmarks. It’s hard to tell why. Best I can tell it’s because in the large parallel apps I’ve worked on and benchmarked, there are often threads that are ready to do some useful work right now, so yielding from a thread that can’t get a lock to one of those is a system throughput optimization. If there are no such threads then it’s a bit like spinning the CPU.

The 40 yields thing has a funny story for me. I learned of it from a comment in the JikesRVM code that was more than a decade old claiming it was optimal on their custom scheduler on a 12 core POWER box running AIX. The benchmark cited was something I’d never heard of. When I rewrote how JikesRVM did thread scheduling, I was sure that this would need tuning. Nope. It’s optimal on Linux and Darwin on x86 on basically any Java benchmark that has lock contention. I later implemented the same heuristic in WebKit’s locks. It’s still optimal.

To be sure, there is a broad plateau of goodness around 40. Like, 50 is about as good. But if you go order of magnitude in either direction, performance tanks. I’m superstitious so I’ve stayed with exactly 40 whenever I have to write this code.

I wrote a lot more about this stuff once: https://webkit.org/blog/6161/locking-in-webkit/

Re:No nuances, just buggy code (was: related to Spinlock imp... by gmueckl on Sunday January 05, 2020 @12:33PM (#21962479)
Interesting. A Win32 CriticalSection is also a two step primitive that spins for a short amount of time and then yielda to the system scheduler.

It would be interesting to see a distribution of wait time per lock for different locks. If such a two step approach performs well, a hypothesis might be that there are three kinds of locks:

- mostly uncontended: lock succeeds without spinning

- held for exceptionally short amounts of time: lock usually gets released again before waiting thread on different CPU stops spinning. Wait time is lower than the time it takes to yield to the scheduler and/or reschedule.

- held for a really long time: wait time is much longer than scheduling another thread and re-scheduling the initial one.

And what you are saying seems to indicate that optimizing for the first and second one is worth it because the overhead is negligible for the third type, that is, there are few locks that are held for an amount of time so that spinning adds noticable overhead.

Re:No nuances, just buggy code (was: related to Spinlock imp... by YZF on Sunday January 05, 2020 @02:03PM (#21963159)
Do you have a reference for this? AFAIK the win32 critical section (like any modern mutex implementation) first uses atomic instructions to check if anyone is already in the critical section so it's really fast if no one is, and otherwise falls back to the OS synchronization objects.
Re:No nuances, just buggy code (was: related to Spinlock imp... by gmueckl on Sunday January 05, 2020 @02:54PM (#21963578)
The information is right there in the documentation:

https://docs.microsoft.com/en-us/windows/win32/sync/critical...

The paragraph about InitializeCriticalSectionAndSpinCount and SetCriticalSectionSpinCount describe the behavior. IIRC, the default spin count used to be pretty high (1000 loops or so). Not sure if that was changed.

Re:No nuances, just buggy code (was: related to Spinlock imp... by YZF on Sunday January 05, 2020 @08:19PM (#21965634)
Interesting. There's also that anecdote about the heap manager using a spin count of 4000. I wasn't aware this happened by default (I didn't see any mention of this in InitializeCriticalSection). I guess it's all down to the probability of contention vs. the amount of time the mutex is held.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @02:10PM (#21963206)
Your description and their description are not mutually exclusive.
Re:No nuances, just buggy code (was: related to Spinlock imp... by YZF on Sunday January 05, 2020 @08:19PM (#21965639)
Spinning vs. a single check...
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @01:02PM (#21962745)
I don’t have as much experience with the Win32 scheduler. But I remember getting conflicting data about the profitability of yielding. In particular, lots of threads yielding on Win32 can cost you a lot of overall system perf, if I remember right. Not so on Linux or Darwin, on the same workloads.
Re:No nuances, just buggy code (was: related to Spinlock imp... by gmueckl on Sunday January 05, 2020 @02:56PM (#21963594)
I don't have too much experience with the Windows scheduler, either. I've written most of my multithreaded code exclusively for Linux.
Re:No nuances, just buggy code (was: related to Spinlock imp... by kitsuac on Sunday January 05, 2020 @11:53AM (#21962150)
All of these little details vary dramatically depending on the exact CPU and workload. I've developed a wide variety of scheduling strategies and have used neural networks to predict when a given strategy will be better. Scheduling is giant non deterministic mess with no ideal answers.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @12:12PM (#21962297)
Ok but my data disagrees with you. Specifically: when apps get complex enough, the differences between CPUs and schedulers wash out in the chaos.

I’ve tested this over the course of a decade on multiple Linuxes, multiple Darwins, multiple x86 flavors (Intel, amd, and lots of core counts and topologies), POWER, and various arms, and on many large benchmarks in two very different languages (Java and C/C++). In Java I tested in in two very different VMs (JikesRVM and FijiVM). I think the key is that a typical benchmark for me is >million lines of code with very heterogenous and chaotic locking behavior stemming from the fact that there are hundreds (at least) of different hot critical sections of varying lengths and subtle relationships between them. So you get a law of large numbers or maybe wisdom of the masses kind of “averaging” of differences between CPUs and schedulers.

I’d love to see some contradictory data on similarly big stuff. But if you’re just saying that some benchmark that had a very homogenous lock behavior (like ~one hot critical section in the code that always runs for a predictable amount of time and never blocks on weird OS stuff) experiences wild differences between CPUs and schedulers then sure. But that just means there are no ideal answers for that scenario, not that there aren’t ideal answers for anyone.

Re:No nuances, just buggy code (was: related to Spinlock imp... by MegaDeKay on Sunday January 05, 2020 @10:43AM (#21961726)
Linus is backed up by Josh Simmons on Twitter. He does some perf tracing and also tweaks Malte's "utterly terrible benchmark" to be "about twice as fast".

https://twitter.com/dotstdy/status/1213578864842162176

Re:No nuances, just buggy code (was: related to Spinlock imp... by weinzierl on Sunday January 05, 2020 @04:43AM (#21960518)
The most important point is:

- you cannot implement a functioning spin lock in user space (for all practical intents and purposes and if you don't resort to extreme measures and talking modern general purpose operating systems)

Re:No nuances, just buggy code (was: related to Spinlock imp... by lmilcin on Sunday January 05, 2020 @11:15AM (#21961899)
You can, but it requires special architecture for the application. The cpuset can be used to disable scheduler use of the core and then you can pin application threads to cores, single thread per core.

Only then it makes sense tu use spin locks.

Re:No nuances, just buggy code (was: related to Spinlock imp... by kanox on Sunday January 05, 2020 @03:32PM (#21963842)
Reserving CPUs for your application definitely qualifies as "extreme measures".

In general you should expect userspace code to be scheduled out at any time and use locking primitives that the kernel knows about.

Re:No nuances, just buggy code (was: related to Spinlock imp... by xakahnx on Sunday January 05, 2020 @11:29AM (#21961988)
This is still a disaster with hyperthreading where you can have two threads contending for a lock on the same physical core.

Even if your thread won't be scheduled off the core I don't know if you might still have irqs stealing time during the critical section.

Re:No nuances, just buggy code (was: related to Spinlock imp... by touisteur on Sunday January 05, 2020 @11:51AM (#21962144)
Can't you isolate cores from irqs with irq affinities? I'd be more worried by SMM stealing CPU time (ugh... talk about sad stories of 'real-time' Linux...).
Re:No nuances, just buggy code (was: related to Spinlock imp... by cbsmith on Sunday January 05, 2020 @07:05AM (#21960914)
Depends on what you mean by "functioning". You can make one that functions in the sense that you can lock between concurrent threads. However, it's difficult to do right and even more difficult to do in a way that is performant (and the interface to the kernel is so ill defined for this, whatever efficient solution you come up with will likely prove to be far less efficient on other systems with just subtle alterations in the underlying kernel implementation.
Re:No nuances, just buggy code (was: related to Spinlock imp... by arendtio on Sunday January 05, 2020 @05:28AM (#21960620)
I like the end:

> Lots of users, all of which are basically buggy-by-definition, and all you can do is a bad half-arsed job of trying to make it "kind of work".

> Reality is messy.

> Linus

Re:No nuances, just buggy code (was: related to Spinlock imp... by gok on Sunday January 05, 2020 @11:38AM (#21962055)
They're talking past each other. Malte is really saying that yield() does what he wants on Windows and BSD, and Linus is giving a bunch of excuses for why Linux doesn't work that way.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @01:17PM (#21962852)
I’m pretty sure that the Linux behavior makes sched_yield more suitable to the 40-yields optimization I use for locks.

I don’t buy claims about lock behavior based on microbenchmarks. Microbenchmarks have wildly different behavior from one another so it stands to reason that they have wildly different behavior from real code.

Linus’s excuse is that if you use a real lock then shit is good. That’s a pretty good excuse IMO.

Re:No nuances, just buggy code (was: related to Spinlock imp... by gok on Sunday January 05, 2020 @02:51PM (#21963555)
Yeah Linus is correct here, he's just not directly addressing why it works on some other schedulers.
Re:No nuances, just buggy code (was: related to Spinlock imp... by spamizbad on Monday January 06, 2020 @10:52AM (#21970018)
One reason they might act that way: They have nowhere near the amount of NUMA or cache locality optimization work the Linux kernel possesses. Thinking of the 2nd-generation Threadripper issues in Windows in particular.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Monday January 06, 2020 @01:55PM (#21971899)
That's quite likely. I think that the sched_yield behavior is throughput-optimized, which is consistent with what you get when an OS is server-optimized like Linux.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @03:30PM (#21963828)
Agreed.
Re:No nuances, just buggy code (was: related to Spinlock imp... by cycloptic on Sunday January 05, 2020 @03:50PM (#21963983)
There's no excuses, the behavior of the scheduler is documented and straightforward. Up thread a twitter user [0] noted that you can somewhat replicate the behavior of windows by replacing sched_yield() with sleep(0). The original benchmark skips this detail and just assumes the C++ wrapper will exhibit the same behavior on all OSes with all schedulers, which is clearly wrong.

[0]: https://nitter.net/dotstdy/status/1213582199615901696#m

Re:No nuances, just buggy code (was: related to Spinlock imp... by soraminazuki on Sunday January 05, 2020 @07:24PM (#21965371)
The point is Windows and BSD isn't doing "what he wants," because the benchmark in question isn't producing any meaningful data for two reasons. First, it's not even measuring what Malte said he was measuring. Second, the benchmark assumes a single threaded HW model, and optimizing for it would very likely slow down performance for modern real-world workloads.
Re:No nuances, just buggy code (was: related to Spinlock imp... by gok on Monday January 06, 2020 @05:16PM (#21974075)
Ignore the benchmark for a moment; the original issue is that a real program (the game Rage 2) uses spin locks, and experiences really terrible hangs on Linux and not on the other kernels. What he wanted was for that to not happen. The upshot is that there really is no way to get spinlocks to not hang on Linux. Now, there's a very good argument that spinlocks are just dumb, and therefore that real game is broken.
Re:No nuances, just buggy code (was: related to Spinlock imp... by muststopmyths on Sunday January 05, 2020 @11:56AM (#21962181)
EXACTLY! I wish someone would just explain why the behavior is different. Does linux only sched_yield on the same NUMA node ? (Linus' reply seemed to imply that). Windows will not pin threads unless you set processor affinity, AFAIK, so maybe that's the difference.

There are runnable threads that are not being run. It would be nice to know why.

Tell a person their code is wrong and you may have saved them from one mistake. Tell them why their code is wrong and you will save them from a dozen.

Re:No nuances, just buggy code (was: related to Spinlock imp... by wyldfire on Sunday January 05, 2020 @12:07PM (#21962255)
> Does linux only sched_yield on the same NUMA node ? (Linus' reply seemed to imply that).

Perhaps Linus' concern is that sched_yield doesn't guarantee any particular behavior, it just happens to be implemented that way now.

Re:No nuances, just buggy code (was: related to Spinlock imp... by Dylan16807 on Sunday January 05, 2020 @01:11PM (#21962807)
> Does linux only sched_yield on the same NUMA node ?

> There are runnable threads that are not being run. It would be nice to know why.

More extreme. It only applies to the same core, more or less. The original benchmarks are on a one-node CPU, after all.

Re:No nuances, just buggy code (was: related to Spinlock imp... by dooglius on Sunday January 05, 2020 @09:42AM (#21961404)
sched_yield does seem like it should have a pretty obvious meaning: do whatever you would do if the preemption timer went off right now. Whether it's a good idea or not, I don't see why it's considered "undefined".
Re:No nuances, just buggy code (was: related to Spinlock imp... by gwd on Sunday January 05, 2020 @09:51AM (#21961455)
I haven't read Linus' discussions, but often what happens when you take that approach is:

1. Scheduler looks through all the processes on the system, decides A is the one that should run right now

2. A decides it doesn't want to run, calls yield.

3. Scheduler looks again, thinks A is still the right one to run, and runs it again.

That is, in most cases, if the scheduler thought there was a better process to run, it would already have been running.

Re:No nuances, just buggy code (was: related to Spinlock imp... by barkingcat on Sunday January 05, 2020 @10:02AM (#21961496)
Please read Linus' discussion.

He states (paraphrasing) - it's undefined because there's no real way (unless in a realtime scheduler) to find what to yield to without instigating complexity.

The search itself is the quest that the kernel would rather not do at all. Step 3 is undefined and thus sched_yield itself is undefined.

Re:No nuances, just buggy code (was: related to Spinlock imp... by gwd on Sunday January 05, 2020 @12:03PM (#21962216)
GGP had a simple solution: "yield is the same as a preemption tick". I was trying to briefly explain what usually happens when you define things that way. I'm not advocating one thing or another.
Re:No nuances, just buggy code (was: related to Spinlock imp... by jcranmer on Sunday January 05, 2020 @12:13PM (#21962317)
That is somewhat surprising to me. My idea of the canonical use case for yield is interprocess communication: process A sent a message to process B, and it wants to yield the remainder of its scheduling quantum to process B because process A has nothing left to do. Although maybe this use case is satisfied by something other than sched_yield.
Re:No nuances, just buggy code (was: related to Spinlock imp... by DougBTX on Sunday January 05, 2020 @01:20PM (#21962866)
Yes, something else. Linus is pointing out that sched_yield doesn’t allow specifying where to yield control to, so it is a bad way to transfer control to a particular task.
Re:No nuances, just buggy code (was: related to Spinlock imp... by rowanG077 on Sunday January 05, 2020 @12:32PM (#21962476)
Why would it yield to process B and not one of the hundreds of other processes running?
Re:No nuances, just buggy code (was: related to Spinlock imp... by Dylan16807 on Sunday January 05, 2020 @01:36PM (#21962969)
In the case of the benchmark, at any point in time there might be 2 background processes that aren't asleep.

So let's look over our cores:

6 of them are churning through processes that do nothing but call sched_yield. Nothing has been waiting to run for more than 3 microseconds.

2 of them are doing real work, and the benchmark threads that happen to be on the same core have been waiting to run for most of a millisecond.

If you're stuck, then B is one of the processes that has been waiting.

So the obvious change would be to move threads around in sched_yield. Then B will get scheduled within microseconds.

Re:No nuances, just buggy code (was: related to Spinlock imp... by rowanG077 on Sunday January 05, 2020 @01:53PM (#21963098)
How is it possible that there are only 2 background processes? Any normal Linux system has hundreds maybe even thousands of processes running.
Re:No nuances, just buggy code (was: related to Spinlock imp... by Dylan16807 on Sunday January 05, 2020 @02:13PM (#21963225)
Two processes that aren't asleep.

What's your CPU use right now? I bet it's less than 200%. Or will be if you exclude your web browser.

The typical background process is active far less than one percent of the time.

Re:No nuances, just buggy code (was: related to Spinlock imp... by rowanG077 on Sunday January 05, 2020 @04:42PM (#21964336)
That doesn't matter that still doesn't guaranty yielding the thread will allow another specific process to run. Especially not if that process is soft locked to a different core.
Re:No nuances, just buggy code (was: related to Spinlock imp... by Dylan16807 on Sunday January 05, 2020 @05:55PM (#21964796)
> That doesn't matter that still doesn't guaranty yielding the thread will allow another specific process to run.

Right, but you would expect the "switch to another thread" function to eventually pick the one that's sitting there with a growing and growing time since it last got a chance to run.

It would be easy to guarantee that if any thread has been waiting for at least x amount of time, then any context switch without long-waiting local threads will pick that thread. (If it's on the same NUMA node, at least.) But in the actual implementation that time can build up to a huge amount with no effect, even as thousands of yield calls go by.

> Especially not if that process is soft locked to a different core.

Yeah, that's the problem, it's getting locked to a core even though other cores are desperate for threads to run.

Re:No nuances, just buggy code (was: related to Spinlock imp... by Filligree on Sunday January 05, 2020 @01:24PM (#21962897)
The best way to do that is with a blocking write. You're sending the messages using a mechanism such as a pipe, right?

Pipes have a small buffer. Once it's full, the caller will block, and the scheduler will know precisely what to schedule to unblock it.

Re:No nuances, just buggy code (was: related to Spinlock imp... by pmoriarty on Sunday January 05, 2020 @02:57PM (#21963606)
"there's no real way (unless in a realtime scheduler) to find what to yield to without instigating complexity"

What about just temporarily increasing the process' nice value?

That should be simple to do, and the scheduler already knows how to yield to processes with lower nice values.

Re:No nuances, just buggy code (was: related to Spinlock imp... by dooglius on Sunday January 05, 2020 @11:09AM (#21961868)
Well, the scheduler has the same problem when the preemption timer goes off, doesn't it?
Re:No nuances, just buggy code (was: related to Spinlock imp... by mike_hock on Sunday January 05, 2020 @10:10AM (#21961532)
What's the postcondition of "whatever you do when the preemption timer goes off"? What's the observable side effect for the program?
Re:No nuances, just buggy code (was: related to Spinlock imp... by dooglius on Sunday January 05, 2020 @10:56AM (#21961792)
Well, it's a hint (but then so is `nice`), so it doesn't have any strict postcondition. The meaning of the hint would be that the process won't make good use of CPU cycles at present, and it would be preferable to yield to any other processes now, rather than being preempted later when it are busy.
Re:No nuances, just buggy code (was: related to Spinlock imp... by im3w1l on Sunday January 05, 2020 @10:36AM (#21961670)
I think I would state it like this: Yielding is communication with the OS. And communication with the OS is observable.
Re:No nuances, just buggy code (was: related to Spinlock imp... by sillysaurusx on Sunday January 05, 2020 @03:38AM (#21960334)
Why is calling sched_yield() a mistake? What's the correct way to yield CPU without calling sleep?
Re:No nuances, just buggy code (was: related to Spinlock imp... by mannykannot on Sunday January 05, 2020 @11:35AM (#21962032)
If a thread can do no more for the moment, then either it is finished, or it is waiting for something to happen. In the latter case, the best thing is to be woken on that event, and the next best is to poll for it. Sleep is only the best solution when that event is literally the passage of time.
Re:No nuances, just buggy code (was: related to Spinlock imp... by dante_dev on Sunday January 05, 2020 @04:01AM (#21960392)
you may want to read Linus's answer about it https://www.realworldtech.com/forum/?threadid=189711&curpost...
Re:No nuances, just buggy code (was: related to Spinlock imp... by atq2119 on Sunday January 05, 2020 @08:12AM (#21961106)
You really should just read the linked article, but the tl;dr is that sched_yield() does not convey intent (except in narrow realtime scenarios that you should not be using in the first place).

Tell the kernel why you want to yield CPU. Work with the kernel, not against it. Do you want to yield because you genuinely want to wait until some future point in time? Use sleep. Is it because you're waiting for some I/O? Use poll or its variants. Is it because you're waiting for a signal from another thread? Use mutexes, condition variables, etc. that call futex under the hood.

Re:No nuances, just buggy code (was: related to Spinlock imp... by gpderetta on Sunday January 05, 2020 @03:48AM (#21960361)
Futex wait, or any other blocking syscall.
Re:No nuances, just buggy code (was: related to Spinlock imp... by pizlonator on Sunday January 05, 2020 @02:49PM (#21963542)
sched_yield means “yield to any thread if it’s easy to do so or do nothing”. That meaning fits the requirements of some algorithms, particularly if you call it a finite number of times before doing something more drastic like a blocking syscall (like futex wait, lock acquisition, whatever) that will block until the condition you’re waiting for is fulfilled.

Think of it as a speculative optimization on top of busy waiting, not as a way to control the scheduler.

Re:No nuances, just buggy code (was: related to Spinlock imp... by colonwqbang on Sunday January 05, 2020 @05:40AM (#21960655)
It's explained in the linked post.

Why don't you want to call sleep?

Re:No nuances, just buggy code (was: related to Spinlock imp... by gpderetta on Sunday January 05, 2020 @07:41AM (#21961020)
Beacuse it would act as as an even worse sched yield.
Re:No nuances, just buggy code (was: related to Spinlock imp... by colonwqbang on Sunday January 05, 2020 @05:02PM (#21964467)
I guess I can't really see the use case here. If you want to sleep, it's usually because you're waiting for something: file descriptor, semaphore/mutex, or a certain amount of time to pass. If none of these conditions apply, can't you just continue to run and let the scheduler do its work? (maybe with an appropriate nice setting if you are doing "less important" work).

The scheduler itself uses fixed time interrupts for its basic functions, so "sleep(fixed_time_interval)" is what you will get in the end anyway on a preemptive system.

Re:No nuances, just buggy code (was: related to Spinlock imp... by mritun on Sunday January 05, 2020 @04:26PM (#21964230)
Calling sleep(1) instead of thread yield is faster - see the twitter thread for a proof.
Re:No nuances, just buggy code (was: related to Spinlock imp... by de_watcher on Monday January 06, 2020 @10:04AM (#21969619)
Still worse: it's a confusing code, and it's also confusing that it's faster.
Re:No nuances, just buggy code (was: related to Spinlock imp... by temac on Sunday January 05, 2020 @10:04AM (#21961505)
Not in every cases, but both are certainly suboptimal compared to a diretected wake up.