我一直在测试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 回答
我想我可以回答你的问题 . 首先,您没有编写系统拥有的核心数 . 如果你正在运行双核,那么当你在Thread示例中使用10个线程时,只有4个线程可以使用Parallel.For . 更多线程将更好地工作,因为您正在运行的任务(打印短暂睡眠)是一个非常短的线程任务,并且与任务相比,线程开销非常大,我几乎可以肯定,如果您编写相同的代码而没有线程它会更快 .
你的方法的工作方式几乎相同,但是如果你事先创建了所有的线程,你就可以将它们保存为Parallel.For使用任务池,这会增加一些移动开销 .
参考Reed Copsey Jr的blog post:
锁定和复制可能使Parallel.ForEach需要更长时间 . 此外,分区和ForEach的调度程序可能会影响并提供开销 . 我测试了你的代码并增加了每个任务的睡眠时间,然后结果更接近,但ForEach仍然较慢 .
[Edit - more research]
我在执行循环中添加了以下内容:
在我的机器上显示的是,与使用当前设置的另一个相比,它使用ForEach少了10个线程 . 如果你想要更多的线程来自ForEach,你将不得不摆弄ParallelOptions和Scheduler .
见Does Parallel.ForEach limits the number of active threads?
关于Threading.Parallel的比较不是很公平 . 你告诉你的自定义线程池,它需要10个线程 . Threading.Parallel不知道它需要多少线程,所以它尝试在运行时调整,考虑到当前CPU负载和其他事情 . 由于测试中的迭代次数足够小,因此可以对此线程数进行自适应惩罚 . 为Threading.Parallel提供相同的提示将使其运行得更快:
这是合乎逻辑的:-)
这将是历史上第一次增加一(或两)层代码来提高性能 . 当您使用便利库时,您应该支付价格 . 顺便说一句,你没有发布数字 . 得发布结果:-)
为了使Parallel-s更加失败(或有偏见:-),将列表转换为数组 .
然后为了使它们完全不公平,自己拆分工作,制作一个只有10个项目的数组,并完全对Parallel进行勺子动作 . 你当然正在完成Parallel-s承诺为你做的工作,但这肯定是一个有趣的数字:-)
顺便说一下,我刚读过里德的博客 . 在这个问题中使用的分区是他所说的最简单和天真的分区 . Which makes it a very good elimination test indeed. 你仍然需要检查零工作情况,只是为了知道它是否完全被冲洗了 .