我一直在测试 System.Threading.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();
更新:
考虑锁定:我尝试了以下代码片段。同样的结果,并行似乎完成得慢得多。
路径=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: 只是快速更新我的机器有四核处理器。所以并行有 4 个核心可用。
参考 Reed Copsey Jr 的 博客文章:
然而,Parallel.ForEach 有点复杂。使用通用 IEnumerable 时,处理所需的项目数量事先未知,必须在运行时发现。此外,由于我们无法直接访问每个元素,因此调度程序必须枚举集合来处理它。锁定和复制可能会使 Parallel.ForEach 花费更长的时间。 ForEach 的分区和调度程序也会影响并产生开销。我测试了你的代码并增加了每个任务的睡眠,然后结果更接近,但 ForEach 仍然较慢。由于 IEnumerable 不是线程安全的,因此它必须在枚举时锁定元素,为每个要处理的块创建临时集合,并将其调度出去。
[编辑 - 更多研究]
我将以下内容添加到执行循环中:
if (Thread.CurrentThread.ManagedThreadId > maxThreadId)
maxThreadId = Thread.CurrentThread.ManagedThreadId;
这在我的机器上显示的是,与当前设置的另一台机器相比,ForEach 减少了 10 个线程的使用。如果您想从 ForEach 中获得更多线程,则必须摆弄 ParallelOptions 和 Scheduler。参见
Parallel.For
,而您在
Thread
示例中使用 10 个线程。更多线程会更好地工作,因为您正在运行的任务(打印+短睡眠)是一个非常短的线程任务,并且与该任务相比,线程开销非常大,我几乎可以肯定,如果您在没有线程的情况下编写相同的代码它会工作得更快。您的两种方法的工作原理几乎相同,但如果您提前创建所有线程,您可以节省很多,因为
Parallel.For
使用任务池,这会增加一些移动开销。
int workerThreads;
int completionPortThreads;
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
ThreadPool.SetMinThreads(10, completionPortThreads);
这将是历史上第一次添加一层(或两层)代码来提高性能。当您使用便利库时,您应该付出代价。顺便说一句,你还没有发布数字。必须发布结果:-)
为了让 Parallel-s 的事情变得更加失败(或有偏见:-),请将列表转换为数组。
然后,为了让它们变得完全不公平,你可以自己分配工作,制作一系列只有 10 个项目的数组,并完全将动作喂给并行。当然,您现在正在做 Parallel-s 承诺为您做的工作,但这肯定是一个有趣的数字:-)
顺便说一句,我刚刚读了里德的博客。本题所用的分区是他所说的最简单、最幼稚的分区。
这确实是一个非常好的消除测试。您仍然需要检查零工作案例,只是为了知道它是否完全被冲洗。