奇怪的并行 [英] Strange Parallel.For scheduling

查看:49
本文介绍了奇怪的并行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对习惯于Parallel的人们的快速提问,我有以下代码,没什么花哨的:

Quick question for people used to Parallel, I have the following piece of code, nothing fancy:

var result = Parallel.For(0, 10, (i) =>
    {
        Console.WriteLine(DateTime.Now.ToLongTimeString() + "Start sleep: " + i);
        Thread.Sleep(i * 1000);
        Console.WriteLine(DateTime.Now.ToLongTimeString() + "Done sleep: " + i);
    });

Console.WriteLine("DONE!");



我有点希望它花费9秒钟左右的时间,具体取决于调度程序.但是花了12到25秒钟以上的时间,有人可以解释原因吗?

这是一个20秒的样本输出和我的评论.为什么调度程序在启动最后一个调度程序之前要等待这么长时间?有很多时间来启动它.而且,有时它做得很好(很快)(为什么)……为什么会有如此巨大的差异?从跑步到跑步我的身边没有太多改变.
5:10:24 PM开始睡眠:5-> 5
下午5:10:24开始睡眠:0-> 5 0
5:10:24 PM完成睡眠:0-> 5
下午5:10:24开始睡眠:1-> 5 1
下午5:10:24开始睡眠:3-> 5 1 3
5:10:25 PM开始睡眠:6-> 5 1 3 6
5:10:25 PM完成睡眠:1-> 5 3 6
5:10:25 PM开始睡眠:2-> 5 3 6 2
5:10:26 PM开始睡眠:4-> 5 3 6 2 4
5:10:27 PM开始睡眠:7-> 5 3 6 2 4 7
5:10:27 PM完成睡眠:2-> 5 3 6 4 7
5:10:27 PM开始睡眠:8-> 5 3 6 4 7 8
下午5:10:27完成睡眠:3-> 5 6 4 7 8
5:10:29 PM完成睡眠:5-> 6 4 7 8
下午5:10:30完成睡眠:4-> 6 7 8
5:10:31 PM完成睡眠:6-> 7 8
5:10:34 PM完成睡眠:7-> 8
下午5:10:35完成睡眠:8->空
5:10:35 PM开始睡眠:9-> 9
下午5:10:44完成睡眠:9->空
完成!



And I sort of expected it to take around 9 seconds, maybe a little more depending on the scheduler. But it took anywhere from 12 to 25+ seconds Can someone explain why?

Here''s a sample output taking 20s and my comments. Why does the scheduler wait for such a long time before launching the last one? There was a lot of time to launch it. Also, sometimes it is done nicely and quickly (earlier)... why the huge variance? Not much changing on my side from run to run.
5:10:24 PMStart sleep: 5 --> 5
5:10:24 PMStart sleep: 0 --> 5 0
5:10:24 PMDone sleep: 0 --> 5
5:10:24 PMStart sleep: 1 --> 5 1
5:10:24 PMStart sleep: 3 --> 5 1 3
5:10:25 PMStart sleep: 6 --> 5 1 3 6
5:10:25 PMDone sleep: 1 --> 5 3 6
5:10:25 PMStart sleep: 2 --> 5 3 6 2
5:10:26 PMStart sleep: 4 --> 5 3 6 2 4
5:10:27 PMStart sleep: 7 --> 5 3 6 2 4 7
5:10:27 PMDone sleep: 2 --> 5 3 6 4 7
5:10:27 PMStart sleep: 8 --> 5 3 6 4 7 8
5:10:27 PMDone sleep: 3 --> 5 6 4 7 8
5:10:29 PMDone sleep: 5 --> 6 4 7 8
5:10:30 PMDone sleep: 4 --> 6 7 8
5:10:31 PMDone sleep: 6 --> 7 8
5:10:34 PMDone sleep: 7 --> 8
5:10:35 PMDone sleep: 8 --> EMPTY
5:10:35 PMStart sleep: 9 --> 9
5:10:44 PMDone sleep: 9 --> EMPTY
DONE!

推荐答案

我认为大部分差异是由于您正在排队要在线程池上完成的工作项与启动专用线程这一事实.在一种方法中,您为每个项目创建专用线程,这些线程都可以立即运行,但ThreadPool不能那样工作.

当您将项目排队到池中时,它最初仅以这么多的线程开始,并且如果没有足够的线程来执行其队列中的所有工作项,则最多每秒增加2(最后检查). (池中的数量或初始线程数取决于系统规格.)

编辑
应该在发布之前而不是之后检查事实:)

在对项目进行排队时,线程池会创建线程,直到达到最佳线程数"为止,最优线程数"根据系统规格而有所不同.根据我的参考,它是系统中的处理器数量-从.Net 3.5开始.达到限制后,它将每半秒最多限制新线程的创建. ThreadPool的用户不必担心添加线程的确切算法,在大多数情况下,它的设计已经足够好了.
/EDIT

一旦创建了这些线程,池就会保留它们一段时间,以防再次需要它们.当您在循环中调用ExecuteTests时,第一个迭代会为您灌注" ThreadPool.它创建了所有必要的线程,以便已经存在足够的线程来处理在后续调用中排队的所有项目.第一次迭代会花费更多的时间,因为它从处理所有项目的线程太少开始,并且花费了更多的时间来旋转.
I think the majority of the discrepancy is from the fact that you''re queueing work items to be done on the thread pool vs. starting dedicated threads. In the one method you create dedicated threads for each item that all run immediately but the ThreadPool doesn''t work that way.

When you queue items to the pool it initially starts out with only so many threads and adds at most 2 a second (last I checked) if there are not enough threads to execute all of the work items in its queue. (The number or initial threads in the pool varies depending on the system specs.)

EDIT
shoulda'' fact checked before posting instead of after :)

When queueing items the thread pool creates threads until it hits the "optimal number of threads" which varies based on the system specs. According to my reference it''s the number of processors in the system - as of .Net 3.5. After it hits that limit it throttles new thread creation to at most one every half second. The exact algorithm for adding threads isn''t something users of the ThreadPool should need to worry about, it''s designed to be good enough in most cases.
/EDIT

Once those threads are created, the pool keeps them around for a bit in case it needs them again. When you call ExecuteTests in the loop the first iteration "primes" the ThreadPool for you; it creates all the necessary threads so that enough already exist to handle all the items being queued in subsequent calls. The first iteration takes more time because it starts out with too few threads to handle all the items and takes time to spin more up.


不,预期的非并行时间约为45秒,不算开销!为什么要用索引乘以睡眠时间?

在2核系统上,我在顺序代码上花了45秒,在并行代码上花了18到27秒. 这绝对不好.

另外,我使用了更好的计时工具,使用System.Diagnostics.Stopwatch:

No, expected non-parallel time is about 45 second, not counting overhead! Why do you multiply sleep time by index?

I got the same 45 sec on sequential code, some 18 to 27 sec in parallel code on 2-core system. This is absolutely not wonderful.

Also, I used better timing tool, using System.Diagnostics.Stopwatch:

System.Diagnostics.Stopwatch watch1 = new System.Diagnostics.Stopwatch();
watch1.Start();
var result = Parallel.For(0, 10, (index) => {
        Console.WriteLine(DateTime.Now.ToLongTimeString() + "Start sleep: " + index);
        Thread.Sleep(index * 1000);
        Console.WriteLine(DateTime.Now.ToLongTimeString() + "Done sleep: " + index);
});
watch1.Stop();
Console.WriteLine("Total time: {0} seconds", watch1.Elapsed.TotalSeconds);

System.Diagnostics.Stopwatch watch2 = new System.Diagnostics.Stopwatch();
watch2.Start();
for (int index = 0; index < 10; index++) {
    Console.WriteLine(DateTime.Now.ToLongTimeString() + "Start sleep: " + index);
    Thread.Sleep(index * 1000);
    Console.WriteLine(DateTime.Now.ToLongTimeString() + "Done sleep: " + index);
}
watch2.Stop();
Console.WriteLine("Total time: {0} seconds", watch2.Elapsed.TotalSeconds);



如果删除索引的乘法运算,则顺序代码将获得9到10秒的时间,而并行代码将获得3秒钟的时间.

您知道在这种并行代码中休眠是没有意义的吗?我希望你只是做实验.另外,您应该了解并行执行并不总是可以提高吞吐量.您需要多代码CPU或多个CPU,以及一个在并行执行中有意义的任务.同样,其他过程也会降低效果.如果CPU负载很大,您可能无法获得性能上的改善.

—SA



If I remove multiplication by index, I get 9-10 sec on sequential code, and about 3 sec on parallel code.

Do you understand sleeping is pointless in such parallel code. I hope you have done only for experiment. Also, you should understand parallel executions does not always improve throughput. You need multi-code CPU or multiple CPU and a task which makes sense in parallel execution. Also, other processes can reduce the effect. With heavy CPU loading you may not get performance improvement.

—SA


我稍微更改了代码以测试几种方法.我的结果现在告诉了一个不同的故事……任务(或Parallel.For)最终出现了,从第二次运行开始,以使结果更接近我的预期(运行时间超过9秒).对我来说,这是一个调度问题,可能与初始化有关.有人知道吗?

代码
I changed my code a little to test several methods. My results tell a different story now... Tasks (or Parallel.For) turn out eventually, starting on the second run to give results closer to what I expected (9+ seconds run time). To me this is a scheduling question, maybe to do with initialization. Anybody knows?

Code
private void RunTest()
{
    for (int i = 0; i < 5; i++)
        ExecuteTests();
}

private void ExecuteTests()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    ExecParallel();
    Log("Parallel done", sw);
    ExecTasks();
    Log("Tasks done", sw);
    ExecThreads();
    Log("Threads done", sw);
    ExecThreadPool();
    Log("ThreadPool done", sw);
}

private static void ExecParallel()
{
    var result = Parallel.For(0, 10, (i) => { DoWork(i); });
}

private static void ExecTasks()
{
    var tasks = new Task[10];
    for (int i = 0 ; i < 10 ; i++)
    {
        tasks[i] = Task.Factory.StartNew(() => DoWork(i));
    }

    Task.WaitAll(tasks);
}

private static void ExecThreads()
{
    var threads = new Thread[10];
    for (int i = 0; i < 10; i++)
    {
        threads[i] = new Thread(() => DoWork(i));
        threads[i].Start();
    }

    foreach (var t in threads)
        t.Join();
}

private static void ExecThreadPool()
{
    var threads = new Thread[10];
    var doneEvents = new ManualResetEvent[10];

    for (int i = 0; i < 10; i++)
    {
        int j = i;
        doneEvents[j] = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem((object ctx) =>
                        {
                            DoWork(j);
                            doneEvents[j].Set();
                        });
    }

    foreach (var e in doneEvents) e.WaitOne();
    // WaitHandle.WaitAll(doneEvents); // in STA thread...
}


private static void DoWork(int i)
{
    Thread.Sleep(i * 1000);
}

private void Log(string s, Stopwatch sw)
{
    Console.Out.WriteLine("[Elapsed time: {0:##.00}s] {1}", sw.ElapsedMilliseconds / 1000f, s);
    sw.Restart();
}



结果



Results

[Elapsed time: 27.01s] Parallel done
[Elapsed time: 15.55s] Tasks done
[Elapsed time: 10.06s] Threads done
[Elapsed time: 9.00s] ThreadPool done

[Elapsed time: 9.00s] Parallel done
[Elapsed time: 10.00s] Tasks done
[Elapsed time: 10.05s] Threads done
[Elapsed time: 9.00s] ThreadPool done

[Elapsed time: 9.00s] Parallel done
[Elapsed time: 10.00s] Tasks done
[Elapsed time: 9.05s] Threads done
[Elapsed time: 9.00s] ThreadPool done

[Elapsed time: 9.00s] Parallel done
[Elapsed time: 10.00s] Tasks done
[Elapsed time: 10.06s] Threads done
[Elapsed time: 9.00s] ThreadPool done

[Elapsed time: 9.00s] Parallel done
[Elapsed time: 10.00s] Tasks done
[Elapsed time: 9.04s] Threads done
[Elapsed time: 9.00s] ThreadPool done


这篇关于奇怪的并行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆