首页 文章

C#Parallel Vs.线程代码性能

提问于
浏览
10

我一直在测试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();

Update:

考虑锁定:我尝试了下面的代码片段 . 同样的结果,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();
    }

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

4 回答

  • 3

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

    你的方法的工作方式几乎相同,但是如果你事先创建了所有的线程,你就可以将它们保存为Parallel.For使用任务池,这会增加一些移动开销 .

  • 3

    参考Reed Copsey Jr的blog post

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

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

    [Edit - more research]

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

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

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

    Does Parallel.ForEach limits the number of active threads?

  • 0

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

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

    这是合乎逻辑的:-)

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

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

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

    顺便说一下,我刚读过里德的博客 . 在这个问题中使用的分区是他所说的最简单和天真的分区 . Which makes it a very good elimination test indeed. 你仍然需要检查零工作情况,只是为了知道它是否完全被冲洗了 .

相关问题