1

I am trying to create a simple demonstration of using 'parallel LINQ' (PLINQ). I have two versions of my task, in C#:

    var result = Enumerable.Range(1,1000000).Where(x => IsPrime(x)).ToList();
    var result = Enumerable.Range(1,1000000).AsParallel().Where(x => IsPrime(x)).ToList();

and use a System.Diagnostics.StopWatch to time both versions. On my machine (4 Cores, 8 Logical Processors), the first takes approx. 114 seconds, while the PLINQ version takes approx. 30 seconds. I know enough not to expect an 8x improvement, though I imagined I'd get better than 3.8X, given the length, and the purity (no I/O) of the task.

But looking at the individual core utilizations using the Resource Monitor, I am surprised by what I see.

Before the task starts, the overall processor utilization is 5-10%. When the task is run, after a start-up spike I see:

  • With the first (LINQ) version, all eight cores get significantly busier than before starting the task, some busier than others, but with a very spiky pattern for each. The overall processor usage runs at a little under 30%.

  • With the second version (PLINQ) all eight processors run at near 100%, as does the overall processor usage.

The second scenario makes more sense to me, though I think I imagined I would see 7 cores running flat out evaluating the query and the eighth one doing all the other stuff.

For the first version, I had (naïvely) expected that I would see one core running flat out and the others (running various background processes) much less. Can someone explain how the distribution is being managed? And why - in a pure processing scenario like this - the gain from parallelism isn't greater?

1
  • 2
    each pair of logical cores share the arithmetic units of one physical core, so if your bottleneck is arithmetic you are limited by physical core count. 3.8x speedup for 4 cores is pretty good
    – Caleth
    Commented Sep 28, 2022 at 11:48

1 Answer 1

2

For the first version, I had (naïvely) expected that I would see one core running flat out and the others (running various background processes) much less. Can someone explain how the distribution is being managed?

The OS will move a high usage thread around a few cores, part of the reason is to distribute the thermal load. But scheduling is a really complicated topic, and it is not made easier by CPUs where some cores share L2 cache, some are on different chips etc. So you can get different behaviors depending on OS, CPU, overall system load etc.

The second scenario makes more sense to me, though I think I imagined I would see 7 cores running flat out evaluating the query and the eighth one doing all the other stuff.

The system will use all cores that are available, and it does not 'reserve' any cores for 'other stuff'. If you have 8 threads calculating prime numbers it will schedule all of them if cores are available. The next time there is a time-slice available the scheduler will use some logic to decide what thread should run next.

But running code on multiple cores always have some overhead. The 95% scaling that you quote is actually very good.

Also note that just because windows lists a core at 100% usage does not mean it is actually doing useful work all of the time, it might wait for memory, spinning a bit while waiting for a lock, or something else that does not really contribute to the progress of your program. So if you are doing hard core optimization you need to dig down to much finer grained diagnostic info to see if you can utilize the processor more efficiently.

I know enough not to expect an 8x improvement

You only have 4 physical cores. The idea behind logical/physical cores, or Simultaneous multithreading (SMT) as it is usually called, is to allow the CPU to switch extremely quickly between two (or in some cases more) threads. So if one thread cannot progress since it is waiting for a memory read, the CPU core can work on the other. Or if one thread is mostly integer work, and the other mostly floating point, CPU core can run both at the same time, since they use different execution resources. It is not very useful when both threads do similar kind of work, and work can be done from cache, with few memory accesses. Your IsPrime-method sounds like it would gain little benefit from SMT.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.