C#Parallel Vs.线程代码性能

时间:2010-08-26 06:32:45

标签: c# parallel-processing pfx

我一直在测试System.Threading.Parallel和线程的性能,我很惊讶看到Parallel需要更长的时间来完成任务而不是线程。我确定这是因为我对Parallel的了解有限,我刚刚开始阅读。

我以为我会分享一些片段,如果有人能指出我,并行代码运行速度比线程代码慢。还尝试运行相同的比较来查找素数,并发现并行代码比线程代码更晚完成。

public class ThreadFactory
{
    int workersCount;
    private List<Thread> threads = new List<Thread>();

    public ThreadFactory(int threadCount, int workCount, Action<int, int, string> action)
    {
        workersCount = threadCount;

        int totalWorkLoad = workCount;
        int workLoad = totalWorkLoad / workersCount;
        int extraLoad = totalWorkLoad % workersCount;

        for (int i = 0; i < workersCount; i++)
        {
            int min, max;
            if (i < (workersCount - 1))
            {
                min = (i * workLoad);
                max = ((i * workLoad) + workLoad - 1);
            }
            else
            {
                min = (i * workLoad);
                max = (i * workLoad) + (workLoad - 1 + extraLoad);
            }
            string name = "Working Thread#" + i; 

            Thread worker = new Thread(() => { action(min, max, name); });
            worker.Name = name;
            threads.Add(worker);
        }
    }

    public void StartWorking()
    {
        foreach (Thread thread in threads)
        {
            thread.Start();
        }

        foreach (Thread thread in threads)
        {
            thread.Join();
        }
    }
}

以下是该计划:

Stopwatch watch = new Stopwatch();
watch.Start();
int path = 1;

List<int> numbers = new List<int>(Enumerable.Range(0, 10000));

if (path == 1)
{
    Parallel.ForEach(numbers, x =>
    {
        Console.WriteLine(x);
        Thread.Sleep(1);

    });
}
else
{
    ThreadFactory workers = new ThreadFactory(10, numbers.Count, (min, max, text) => {

        for (int i = min; i <= max; i++)
        {
            Console.WriteLine(numbers[i]);
            Thread.Sleep(1);
        }
    });

    workers.StartWorking();
}

watch.Stop();
Console.WriteLine(watch.Elapsed.TotalSeconds.ToString());

Console.ReadLine();

更新

考虑锁定:我尝试了以下代码段。同样的结果,Parallel似乎完成得慢得多。

path = 1;         cieling = 10000000;

    List<int> numbers = new List<int>();

    if (path == 1)
    {
        Parallel.For(0, cieling, x =>
        {
            lock (numbers)
            {
                numbers.Add(x);    
            }

        });
    }

    else
    {
        ThreadFactory workers = new ThreadFactory(10, cieling, (min, max, text) =>
        {

            for (int i = min; i <= max; i++)
            {
                lock (numbers)
                {
                    numbers.Add(i);    
                }                       

            }
        });

        workers.StartWorking();
    }

更新2: 只是快速更新我的机器有四核处理器。所以Parallel有4个核心可用。

4 个答案:

答案 0 :(得分:3)

参考Reed Copsey Jr的blog post

  

Parallel.ForEach有点复杂。使用通用IEnumerable时,事先不知道处理所需的项目数,必须在运行时发现。此外,由于我们无法直接访问每个元素,因此调度程序必须枚举集合以对其进行处理。 由于IEnumerable不是线程安全的,因此它必须锁定元素,因为它枚举,为每个要处理的块创建临时集合,并安排出来

锁定和复制可能会使Parallel.ForEach花费更长时间。此外,分区和ForEach的调度程序可能会影响并提供开销。我测试了你的代码,增加了每个任务的睡眠时间,然后结果更接近,但ForEach仍然较慢。

[编辑 - 更多研究]

我在执行循环中添加了以下内容:

if (Thread.CurrentThread.ManagedThreadId > maxThreadId)
   maxThreadId = Thread.CurrentThread.ManagedThreadId;

在我的机器上显示的是,与使用当前设置的另一个相比,它使用ForEach少了10个线程。如果你想要更多的ForEach线程,你将不得不摆弄ParallelOptions和Scheduler。

请参阅Does Parallel.ForEach limits the number of active threads?

答案 1 :(得分:3)

我想我可以回答你的问题。首先,您没有编写系统拥有的核心数。如果您正在运行双核,则在Parallel.For示例中使用10个线程时,只有4个线程可以使用Thread。更多线程将更好地工作,因为您正在运行的任务(Printing + Short sleep)是一个非常短的线程任务,并且与任务相比,线程开销非常大,我几乎可以肯定,如果您编写相同的代码而没有线程它会更快地运作。

你的方法的工作方式几乎相同,但是如果你事先创建了所有的线程,你会节省很多,因为Parallel.For使用了任务池,增加了一些移动开销。

答案 2 :(得分:0)

关于Threading.Parallel的比较不是很公平。你告诉你的自定义线程池,它需要10个线程。 Threading.Parallel不知道它需要多少线程,所以它试图在运行时适应考虑当前CPU负载和其他事情。由于测试中的迭代次数足够小,因此可以对此线程数进行自适应惩罚。为Threading.Parallel提供相同的提示将使其运行得更快:


int workerThreads;
int completionPortThreads;
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
ThreadPool.SetMinThreads(10, completionPortThreads);

答案 3 :(得分:0)

这是合乎逻辑的: - )

这将是历史上第一次增加一(或两)层代码来提高性能。当您使用便利库时,您应该支付价格。顺便说一句,你没有发布数字。要发布结果: - )

为了使Parallel-s更加失败(或有偏见:-),将列表转换为数组。

然后为了使它们完全不公平,自己拆分工作,制作一个只有10个项目的数组,并完全将勺子进给行动到Parallel。你当然正在完成Parallel-s承诺为你做的工作,但这肯定是一个有趣的数字: - )

顺便说一下,我刚读过里德的博客。在这个问题中使用的分区是他所说的最简单和天真的分区。 这确实是一个非常好的淘汰测试。你仍然需要检查零工作情况,只是为了知道它是否完全被冲洗。