Tommi Prami 148 Posted yesterday at 04:40 AM (edited) Spoiler, SwitshToThread <> Sleep(0). https://stackoverflow.com/questions/1383943/switchtothread-vs-sleep1 Quote There are two differences. The first is mentioned in the MSDN docs for SwitchToThread: The yield of execution is limited to the processor of the calling thread. The operating system will not switch execution to another processor, even if that processor is idle or is running a thread of lower priority. Sleep(0) will allow threads on other processors to run, as well. Seems that this is quite tricky, dependig what yuo are doing and what you are trying to achioeve. No silver bullets... -tee- Edited yesterday at 04:46 AM by Tommi Prami Share this post Link to post
Kas Ob. 147 Posted yesterday at 09:02 AM Spent two hours trying to reproduce anything close to that mentioned article https://joeduffyblog.com/2006/08/22/priorityinduced-starvation-why-sleep1-is-better-than-sleep0-and-the-windows-balance-set-manager/ I read that article years ago, many years, yet i couldn't reproduce anything even small hint if that is the case with Sleep(0) vs Sleep(1) vs SwitchToThread, So i just wrote two tests, one is mimicking the starvation pointed and presented in the article producer/consumer, and the other just how efficient these three method. Also to be clear, i am not saying the article is misleading or wrong, but it is most likely testing different thing completely (namely the efficiency of ThreadPool in C# in 2006), or the test is happening is single core CPU belongs to past and gone era, know this Sleep(1) by definition can't perform less than OS timer granularity which is by default 1000/64 seconds almost 15-16 ms, and in best case scenario it will be 1 ms, this is guaranteed by the OS, so the article result is irrelevant today. First test program ThreadIterationTest; {$APPTYPE CONSOLE} uses System.SysUtils, System.Classes, Winapi.Windows; {$WARN SYMBOL_PLATFORM OFF} type TSwitchMethod = (smSleep0, smSleep1, smSwitchToThread); TCounterThread = class(TThread) private FSwitchMethod: TSwitchMethod; FIterationCount: Int64; FDuration: Integer; procedure Execute; override; public constructor Create(SwitchMethod: TSwitchMethod; Duration: Integer; Priority: TThreadPriority); property IterationCount: Int64 read FIterationCount; property SwitchMethod: TSwitchMethod read FSwitchMethod; end; constructor TCounterThread.Create(SwitchMethod: TSwitchMethod; Duration: Integer; Priority: TThreadPriority); begin inherited Create(True); // Create suspended FSwitchMethod := SwitchMethod; FDuration := Duration; FIterationCount := 0; Self.Priority := Priority; FreeOnTerminate := True; end; procedure TCounterThread.Execute; var StartTick, Duration: Cardinal; begin Duration := FDuration * 1000; StartTick := GetTickCount; while (GetTickCount - StartTick) < Duration do begin Inc(FIterationCount); case FSwitchMethod of {(*} smSleep0: Sleep(0); smSleep1: Sleep(1); smSwitchToThread: SwitchToThread; {*)} end; end; end; function ThPriorityToString(ThProirity: TThreadPriority): string; var P: Integer; begin case ThProirity of {(*} tpIdle: Result:= 'Idle'; tpLowest: Result:= 'Lowest'; tpLower: Result:= 'Lower'; tpNormal: Result:= 'Normal'; tpHigher: Result:= 'Higher'; tpHighest: Result:= 'Highest'; tpTimeCritical: Result:= 'TimeCritical'; {*)} else Result := 'Unknown'; end; Result := Result + '('; case ThProirity of {(*} tpIdle: P:= THREAD_PRIORITY_IDLE; tpLowest: P:= THREAD_PRIORITY_LOWEST; tpLower: P:= THREAD_PRIORITY_BELOW_NORMAL; tpNormal: P:= THREAD_PRIORITY_NORMAL; tpHigher: P:= THREAD_PRIORITY_ABOVE_NORMAL; tpHighest: P:= THREAD_PRIORITY_HIGHEST; tpTimeCritical: P:= THREAD_PRIORITY_TIME_CRITICAL; {*)} else P := 999; end; Result := Result + IntToStr(P) + ')'; end; procedure RunTest(Duration: Integer; Priority1, Priority2, Priority3: TThreadPriority); var Thread1, Thread2, Thread3: TCounterThread; begin Writeln('Starting test with duration: ', Duration, ' seconds'); Writeln('Thread priorities: Sleep(0)=', ThPriorityToString(Priority1), ', Sleep(1)=', ThPriorityToString(Priority2), ', SwitchToThread=', ThPriorityToString(Priority3)); Thread1 := TCounterThread.Create(smSleep0, Duration, Priority1); Thread2 := TCounterThread.Create(smSleep1, Duration, Priority2); Thread3 := TCounterThread.Create(smSwitchToThread, Duration, Priority3); Thread1.Start; Thread2.Start; Thread3.Start; WaitForSingleObject(Thread1.Handle, INFINITE); WaitForSingleObject(Thread2.Handle, INFINITE); WaitForSingleObject(Thread3.Handle, INFINITE); Writeln('Results:'); Writeln('Sleep(0) iterations: ', Thread1.IterationCount); Writeln('Sleep(1) iterations: ', Thread2.IterationCount); Writeln('SwitchToThread iterations: ', Thread3.IterationCount); Writeln; end; begin try Writeln('Test 1: All threads with normal priority'); RunTest(3, tpNormal, tpNormal, tpNormal); Writeln('Test 1.1: All threads with normal priority'); RunTest(1, tpNormal, tpNormal, tpNormal); Writeln('Test 1.5: All threads with normal priority'); RunTest(5, tpNormal, tpNormal, tpNormal); Writeln('Test 2: Different priorities'); RunTest(5, tpHigher, tpNormal, tpLower); Writeln('Test 3: Different priorities'); RunTest(5, tpLowest, tpHighest, tpNormal); Writeln('Test 4: Different priorities'); RunTest(5, tpLowest, tpLowest, tpLowest); Writeln('Done.'); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. Its result on my device Test 1: All threads with normal priority Starting test with duration: 3 seconds Thread priorities: Sleep(0)=Normal(0), Sleep(1)=Normal(0), SwitchToThread=Normal(0) Results: Sleep(0) iterations: 15700875 Sleep(1) iterations: 175 SwitchToThread iterations: 19869985 Test 1.1: All threads with normal priority Starting test with duration: 1 seconds Thread priorities: Sleep(0)=Normal(0), Sleep(1)=Normal(0), SwitchToThread=Normal(0) Results: Sleep(0) iterations: 5266693 Sleep(1) iterations: 60 SwitchToThread iterations: 6658333 Test 1.5: All threads with normal priority Starting test with duration: 5 seconds Thread priorities: Sleep(0)=Normal(0), Sleep(1)=Normal(0), SwitchToThread=Normal(0) Results: Sleep(0) iterations: 26351894 Sleep(1) iterations: 269 SwitchToThread iterations: 33344803 Test 2: Different priorities Starting test with duration: 5 seconds Thread priorities: Sleep(0)=Higher(1), Sleep(1)=Normal(0), SwitchToThread=Lower(-1) Results: Sleep(0) iterations: 26332342 Sleep(1) iterations: 299 SwitchToThread iterations: 33324362 Test 3: Different priorities Starting test with duration: 5 seconds Thread priorities: Sleep(0)=Lowest(-2), Sleep(1)=Highest(2), SwitchToThread=Normal(0) Results: Sleep(0) iterations: 26220753 Sleep(1) iterations: 299 SwitchToThread iterations: 33216074 Test 4: Different priorities Starting test with duration: 5 seconds Thread priorities: Sleep(0)=Lowest(-2), Sleep(1)=Lowest(-2), SwitchToThread=Lowest(-2) Results: Sleep(0) iterations: 26350390 Sleep(1) iterations: 291 SwitchToThread iterations: 33374685 Done. Sleep(1) is in the expected range of 60-64 per second Now different test to emulate the article example, not using Delphi RTL thread pool and anonymous thread, because i never trust them on my XE8. program ThreadStarvationTest; {$APPTYPE CONSOLE} uses System.SysUtils, System.Classes, Winapi.Windows; {$WARN SYMBOL_PLATFORM OFF} type TYieldMethod = (ymSleep0, ymSleep1, ymSwitchToThread); TStarvationThread = class(TThread) private FYieldMethod: TYieldMethod; FIsProducer: Boolean; FDuration: Cardinal; procedure Execute; override; public constructor Create(YieldMethod: TYieldMethod; IsProducer: Boolean; Priority: TThreadPriority); property Duration: Cardinal read FDuration; end; var x: Integer = 0; constructor TStarvationThread.Create(YieldMethod: TYieldMethod; IsProducer: Boolean; Priority: TThreadPriority); begin inherited Create(True); FYieldMethod := YieldMethod; FIsProducer := IsProducer; Self.Priority := Priority; //FreeOnTerminate := True; // don't care, irrelevant FDuration := 0; end; procedure TStarvationThread.Execute; var StartTick: Cardinal; begin // run threads on one core, core 0 SetThreadAffinityMask(GetCurrentThread, 1); if FIsProducer then begin //Sleep(50); // Sleep(500); // Sleep(1500); x := 1; // Producer sets x end else begin StartTick := GetTickCount; while x = 0 do begin case FYieldMethod of ymSleep0: Sleep(0); ymSleep1: Sleep(1); ymSwitchToThread: SwitchToThread; end; end; FDuration := GetTickCount - StartTick; end; end; function ThPriorityToString(ThPriority: TThreadPriority): string; var P: Integer; begin case ThPriority of {(*} tpIdle: Result := 'Idle'; tpLowest: Result := 'Lowest'; tpLower: Result := 'Lower'; tpNormal: Result := 'Normal'; tpHigher: Result := 'Higher'; tpHighest: Result := 'Highest'; tpTimeCritical: Result := 'TimeCritical'; {*)} else Result := 'Unknown'; end; Result := Result + '('; case ThPriority of {(*} tpIdle: P := THREAD_PRIORITY_IDLE; tpLowest: P := THREAD_PRIORITY_LOWEST; tpLower: P := THREAD_PRIORITY_BELOW_NORMAL; tpNormal: P := THREAD_PRIORITY_NORMAL; tpHigher: P := THREAD_PRIORITY_ABOVE_NORMAL; tpHighest: P := THREAD_PRIORITY_HIGHEST; tpTimeCritical: P := THREAD_PRIORITY_TIME_CRITICAL; {*)} else P := 999; end; Result := Result + IntToStr(P) + ')'; end; function YieldMethodToStr(YieldMethod:TYieldMethod):string; begin case YieldMethod of {(*} ymSleep0: Result := 'Sleep(0)'; ymSleep1: Result := 'Sleep(1)'; ymSwitchToThread: Result := 'SwitchToThread'; {*)} end; end; procedure RunStarvationTest(YieldMethod: TYieldMethod; ConsumerPriority, ProducerPriority: TThreadPriority); var Consumer, Producer: TStarvationThread; begin Writeln('Starting starvation test with ', YieldMethodToStr(YieldMethod), ', Consumer=', ThPriorityToString(ConsumerPriority), ', Producer=', ThPriorityToString(ProducerPriority)); x := 0; Consumer := TStarvationThread.Create(YieldMethod, False, ConsumerPriority); Producer := TStarvationThread.Create(YieldMethod, True, ProducerPriority); Consumer.Start; Producer.Start; Consumer.WaitFor; Producer.WaitFor; Writeln('Result: ', YieldMethodToStr(YieldMethod), ' time: ', Consumer.Duration, ' ms'); Writeln; end; begin try // Test Sleep(0) with equal priorities RunStarvationTest(ymSleep0, tpNormal, tpNormal); // Test Sleep(0) with different priorities RunStarvationTest(ymSleep0, tpNormal, tpLower); // Test Sleep(0) with different priorities RunStarvationTest(ymSleep0, tpLower, tpNormal); // Test Sleep(1) with equal priorities RunStarvationTest(ymSleep1, tpNormal, tpNormal); // Test Sleep(1) with different priorities RunStarvationTest(ymSleep1, tpNormal, tpLower); // Test Sleep(1) with different priorities RunStarvationTest(ymSleep1, tpLower, tpNormal); // Test SwitchToThread with equal priorities RunStarvationTest(ymSwitchToThread, tpNormal, tpNormal); // Test SwitchToThread with different priorities RunStarvationTest(ymSwitchToThread, tpNormal, tpLower); // Test SwitchToThread with different priorities RunStarvationTest(ymSwitchToThread, tpLower, tpNormal); Writeln('Done.'); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. Its result with and without delaying the producer by uncommenting the Sleep(50).. no starvation observed at all and the result is consistent with the delay introduced being Sleep(50), Sleep(500) or Sleep(1500) Starting starvation test with Sleep(0), Consumer=Normal(0), Producer=Normal(0) Result: Sleep(0) time: 0 ms Starting starvation test with Sleep(0), Consumer=Normal(0), Producer=Lower(-1) Result: Sleep(0) time: 0 ms Starting starvation test with Sleep(0), Consumer=Lower(-1), Producer=Normal(0) Result: Sleep(0) time: 0 ms Starting starvation test with Sleep(1), Consumer=Normal(0), Producer=Normal(0) Result: Sleep(1) time: 0 ms Starting starvation test with Sleep(1), Consumer=Normal(0), Producer=Lower(-1) Result: Sleep(1) time: 15 ms Starting starvation test with Sleep(1), Consumer=Lower(-1), Producer=Normal(0) Result: Sleep(1) time: 0 ms Starting starvation test with SwitchToThread, Consumer=Normal(0), Producer=Normal(0) Result: SwitchToThread time: 0 ms Starting starvation test with SwitchToThread, Consumer=Normal(0), Producer=Lower(-1) Result: SwitchToThread time: 0 ms Starting starvation test with SwitchToThread, Consumer=Lower(-1), Producer=Normal(0) Result: SwitchToThread time: 0 ms Done. 4 hours ago, Tommi Prami said: No silver bullets... True, there is no silver bullet, but Sleep(1) is better for completely different reason and it is impossible to be less than 1ms delay, unless with my vivid imagination your PC has no other threads running, like the OS has nothing else to do, so it will be faced with either 1) put the CPU/core to sleep, i mean really sleep signal and reduce the power for the core. 2) ignore the delay continue, even in this case, the article result can't be reproduce because we have two threads not one, hence the producer will execute releasing the consumer. ps: these tests are for specific measurement, and i know they are not very helpful in real life, but they are accurate in the delays and shows how SwitchToThread is faster then Sleep(0), due the reduced context switch with threads from different processes, as for mixing the priorities, well this is way different subject and longer, but even so as tests shows it is irrelevant with Windows 10 and modern CPU, my CPU is Sandy Bridge so around 15 years old. I would love to see result form modern and different CPUs, just for general information, also from different Windows versions, that would be nice, the discussion of how much relevant the test in real life doesn't concern me, as i am trying to replicate that article strange result. Share this post Link to post
Tommi Prami 148 Posted yesterday at 09:10 AM As I understand what they say, for some ypou would need multiple CPUs. You for sure might have such a hardware. Thanks for extra info.,.. Share this post Link to post
Kas Ob. 147 Posted yesterday at 09:16 AM 3 minutes ago, Tommi Prami said: As I understand what they say, for some ypou would need multiple CPUs. You for sure might have such a hardware. Thanks for extra info.,.. No sure i do understand that, but lets say on single core the test will yield similar result to the article, meaning Sleep(0) is magnitude slower than Sleep(1) and SwitchToThread, then that OS should not be working at all ! and if worked then it will be slower than computer in 70s and 80s, i mean less than 10Mhz CPU. Share this post Link to post
Tommi Prami 148 Posted yesterday at 09:20 AM Just now, Kas Ob. said: No sure i do understand that, but lets say on single core the test will yield similar result to the article, meaning Sleep(0) is magnitude slower than Sleep(1) and SwitchToThread, then that OS should not be working at all ! and if worked then it will be slower than computer in 70s and 80s, i mean less than 10Mhz CPU. One of the replies says that SwitchToThread will yeld to waiting thread on same CPU (not the core). If computer has multiple CPUs then Sleep(0) would run threads on other CPU also. That's how I understood the repply. But, thread cheduler might have changed over the years, so these might be very hard to be sure how it behaves... -Tee- Share this post Link to post
DelphiUdIT 248 Posted yesterday at 09:53 AM I think all is linked to the time of SO post, 15 years ago. Talking about CPU here is about CORE, when there are multiple CPU other things come in play and the sleep is the last of the issues. Take care now ( @Kas Ob. told this) that the real life is different: no one use affinity with one thread (yes, I do it but for really unique needs), the threads work are balanced by ITD (Intel Thread Director) that acts between OS and hardware, and also Windows may have changed some logics. If you look at your thread (not the one in the example that's "blocked" by Affinity), you'll see that it's "moved" by core during its lifetime. That is, a thread doesn't necessarily always run in the same core. So, if Microsoft were to say that SwitchToThread works in a certain way with modern hardware... well, I wouldn't be so sure, or rather, not in the context we're imagining, given that the load distribution is dynamic. 1 Share this post Link to post
pyscripter 791 Posted yesterday at 10:33 AM By the way, TThread.Yield is the cross-platform way to call SwitchToThread. 1 Share this post Link to post
Tommi Prami 148 Posted yesterday at 10:37 AM 3 minutes ago, pyscripter said: By the way, TThread.Yield is the cross-platform way to call SwitchToThread. That is where this journey started... 😉 -Tee- Share this post Link to post