11

我一直在测试 System.Threading.Parallel 与 Threading 的性能,我很惊讶地看到 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 似乎完成得慢得多。

路径 = 1; 雪灵= 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

4 回答 4

4

参考 Reed Copsey Jr的博客文章

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

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

[编辑-更多研究]

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

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

这在我的机器上显示的是,与使用当前设置的另一个线程相比,它使用 ForEach 使用的线程少了 10 个。如果您想从 ForEach 中获得更多线程,则必须使用 ParallelOptions 和调度程序。

请参阅Parallel.ForEach 是否限制活动线程的数量?

于 2010-08-26T07:00:22.587 回答
3

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

您的两种方法的工作原理几乎相同,但是如果您提前创建所有线程,则可以节省很多,因为Parallel.For使用任务池会增加一些移动开销。

于 2010-08-26T07:46:47.977 回答
0

这是合乎逻辑的:-)

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

为了让 Parallel-s 的事情变得更加失败(或有偏见:-),请将列表转换为数组。

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

顺便说一句,我刚刚阅读了 Reed 的博客。这个问题中使用的分区是他所说的最简单、最幼稚的分区。这确实使它成为一个非常好的消除测试。您仍然需要检查零工作案例,以了解它是否完全被冲洗。

于 2010-08-26T12:12:49.377 回答
0

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


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

于 2010-08-26T09:14:50.670 回答