线程对比 Parallel.For 性能
Thread vs Parallel.For performance
我很难理解 threads 和 Parallel.For 之间的区别。我创建了两个函数,一个使用 Parallel.For 其他调用的线程。调用 10 个线程 似乎 更快 ,谁能解释一下? 线程会使用系统中可用的多个处理器(以并行执行)还是只是时间切片 参考 CLR?
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 10, x =>
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(Thread1));
t.Start();
if (i == 9)
t.Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1()
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", 0,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
当调用下面的方法时,Parallel.For 花费的时间是线程的两倍。
Algo.ParallelThread(); //took 3 secs
Algo.ParallelProcess(); //took 6 secs
Parallel
使用底层调度程序提供的线程数量,这将是开始时线程池线程的最小 数量。
最小 线程池线程数默认设置为处理器数。随着时间的推移和基于许多不同的因素,例如当前所有线程都处于忙碌状态,调度程序可能会决定生成 更多 个线程并高于最小计数。
所有这些都是为您管理的,以停止不必要的资源使用。您的第二个示例通过手动生成线程来规避所有这些。如果您显式设置线程池线程数,例如ThreadPool.SetMinThreads(100, 100)
,你会看到即使是并行的也需要 3 秒,因为它立即有更多线程可用。
用最简单的术语来说,使用 Thread
class 保证在操作系统级别创建线程,但使用 Parallel.For
CLR 会三思而后行生成 OS 级线程。如果觉得是在 OS 级创建线程的好时机,它会继续,否则它会使用可用的线程池。 TPL 被编写为针对多核环境进行优化。
您的代码段不相同。这是 ParallelThread
的一个版本,它与 ParallelProcess
的功能相同,但会启动新线程:
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = new Thread[10];
for (int i = 0; i < 10; i++)
{
int x = i;
threads[i] = new Thread(() => Thread1(x));
threads[i].Start();
}
for (int i = 0; i < 10; i++)
{
threads[i].Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
在这里,我确保等待所有线程。而且,我确保匹配控制台输出。 OP 代码不做的事情。
但是时差还是有的
让我告诉你是什么造成了差异,至少在我的测试中是这样:顺序。 运行 ParallelProcess
在 ParallelThread
之前,它们都需要 3 秒才能完成(忽略初始的 运行s,因为编译需要更长的时间)。我真的无法解释。
我们可以修改上面的代码进一步使用 ThreadPool
,这也导致 ParallelProcess
在 3 秒内完成(即使我没有修改那个版本)。这是 ParallelThread
和 ThreadPool
我想出的版本:
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var events = new ManualResetEvent[10];
for (int i = 0; i < 10; i++)
{
int x = i;
events[x] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem
(
_ =>
{
Thread1(x);
events[x].Set();
}
);
}
for (int i = 0; i < 10; i++)
{
events[i].WaitOne();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
注意:我们可以在事件上使用 WaitAll
,但在 STAThread
.
上会失败
您有 Thread.Sleep(3000)
,这是我们看到的 3 秒。这意味着我们并没有真正测量任何这些方法的开销。
所以,我决定进一步研究这个问题,为此,我提高了一个数量级(从 10 到 100)并删除了 Console.WriteLine
(无论如何都会引入同步)。
这是我的代码清单:
void Main()
{
ParallelThread();
ParallelProcess();
}
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 100, x =>
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var events = new ManualResetEvent[100];
for (int i = 0; i < 100; i++)
{
int x = i;
events[x] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem
(
_ =>
{
Thread1(x);
events[x].Set();
}
);
}
for (int i = 0; i < 100; i++)
{
events[i].WaitOne();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
}
ParallelThread
需要 6 秒,ParallelProcess
需要 9 秒。即使在颠倒顺序后,情况仍然如此。这让我更加确信这是对开销的真实衡量。
添加 ThreadPool.SetMinThreads(100, 100);
可将 ParallelThread
(请记住此版本使用的是 ThreadPool
)和 ParallelProcess
的时间缩短为 3 秒。意味着这个开销来自线程池。现在,我可以回到生成新线程的版本(修改为生成 100 并带有 Console.WriteLine
注释):
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = new Thread[100];
for (int i = 0; i < 100; i++)
{
int x = i;
threads[i] = new Thread(() => Thread1(x));
threads[i].Start();
}
for (int i = 0; i < 100; i++)
{
threads[i].Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
}
我从这个版本得到一致的 3 秒(这意味着时间开销可以忽略不计,因为正如我之前所说,Thread.Sleep(3000)
是 3 秒),但是我想指出它会留下更多垃圾收集比使用 ThreadPool
或 Parallel.For
。另一方面,使用 Parallel.For
仍然与 ThreadPool
相关联。顺便说一句,如果你想降低它的性能,减少最小线程数是不够的,你还必须降低最大线程数(例如ThreadPool.SetMaxThreads(1, 1);
)。
总而言之,请注意 Parallel.For
更容易使用,更不容易出错。
Invoking 10 threads would appear to be faster, can anyone please explain?
产生线程的速度很快。虽然,它会导致更多的垃圾。另外,请注意你的测试不是很好。
Would threads use multiple processors available in the system (to get executed in parallel) or does it just do time slicing in reference to CLR?
是的,他们会的。它们映射到底层操作系统线程,可以被它抢占,并根据它们的亲和力 运行 在任何内核中(参见 ProcessThread.ProcessorAffinity
)。需要明确的是,它们不是 fibers 也不是协同程序。
你这里有很多错误。
(1) 不要使用 sw.Elapsed.Seconds
这个值是一个 int
并且(显然)t运行 包含时间的小数部分。更糟糕的是,如果您有一个需要 61 秒才能完成的过程,这将报告 1
,因为它就像时钟上的秒针。您应该改用 sw.Elapsed.TotalSeconds
,它报告为 double
,它显示总秒数,无论多少分钟或几小时等。
(2) Parallel.For
使用线程池。这显着减少(甚至消除)创建线程的开销。每次调用 new Thread(() => ...)
时,您都会分配超过 1MB 的 RAM 并在进行任何处理之前消耗宝贵的资源。
(3) 您正在使用 Thread.Sleep(3000);
人为地加载线程,这意味着您掩盖了创建大量睡眠线程所需的实际时间。
(4) 默认情况下,Parallel.For
受限于 CPU 上的内核数量。因此,当您 运行 10 个线程时,工作被分成两步 - 这意味着 Thread.Sleep(3000);
被连续 运行 两次,因此它是 运行 的 6 秒宁。 new Thread
方法是 运行 一次连接所有线程,这意味着它只需要 3 秒多一点,但是 Thread.Sleep(3000);
再次淹没了线程启动时间。
(5) 您还在处理 CLR JIT 问题。第一次 运行 您的代码的启动成本是巨大的。让我们更改代码以删除睡眠并正确加入线程:
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 10, x =>
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x, Thread.CurrentThread.ManagedThreadId));
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.TotalMilliseconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = Enumerable.Range(0, 10).Select(x => new Thread(new ThreadStart(Thread1))).ToList();
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.TotalMilliseconds));
return true;
}
private static void Thread1()
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", 0, Thread.CurrentThread.ManagedThreadId));
}
现在,为了摆脱 CLR/JIT 启动时间,我们 运行 代码如下:
ParallelProcess();
ParallelThread();
ParallelProcess();
ParallelThread();
ParallelProcess();
ParallelThread();
我们得到的时间是这样的:
Time in secs 3.8617
Time in secs 4.7719
Time in secs 0.3633
Time in secs 1.6332
Time in secs 0.3551
Time in secs 1.6148
与第二和第三次 运行 相比,开始的 运行 时间非常糟糕,后者更加一致。
结果是 运行ning Parallel.For
比调用 new Thread
.
快 4 到 5 倍
我很难理解 threads 和 Parallel.For 之间的区别。我创建了两个函数,一个使用 Parallel.For 其他调用的线程。调用 10 个线程 似乎 更快 ,谁能解释一下? 线程会使用系统中可用的多个处理器(以并行执行)还是只是时间切片 参考 CLR?
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 10, x =>
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(Thread1));
t.Start();
if (i == 9)
t.Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1()
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", 0,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
当调用下面的方法时,Parallel.For 花费的时间是线程的两倍。
Algo.ParallelThread(); //took 3 secs
Algo.ParallelProcess(); //took 6 secs
Parallel
使用底层调度程序提供的线程数量,这将是开始时线程池线程的最小 数量。
最小 线程池线程数默认设置为处理器数。随着时间的推移和基于许多不同的因素,例如当前所有线程都处于忙碌状态,调度程序可能会决定生成 更多 个线程并高于最小计数。
所有这些都是为您管理的,以停止不必要的资源使用。您的第二个示例通过手动生成线程来规避所有这些。如果您显式设置线程池线程数,例如ThreadPool.SetMinThreads(100, 100)
,你会看到即使是并行的也需要 3 秒,因为它立即有更多线程可用。
用最简单的术语来说,使用 Thread
class 保证在操作系统级别创建线程,但使用 Parallel.For
CLR 会三思而后行生成 OS 级线程。如果觉得是在 OS 级创建线程的好时机,它会继续,否则它会使用可用的线程池。 TPL 被编写为针对多核环境进行优化。
您的代码段不相同。这是 ParallelThread
的一个版本,它与 ParallelProcess
的功能相同,但会启动新线程:
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = new Thread[10];
for (int i = 0; i < 10; i++)
{
int x = i;
threads[i] = new Thread(() => Thread1(x));
threads[i].Start();
}
for (int i = 0; i < 10; i++)
{
threads[i].Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
在这里,我确保等待所有线程。而且,我确保匹配控制台输出。 OP 代码不做的事情。
但是时差还是有的
让我告诉你是什么造成了差异,至少在我的测试中是这样:顺序。 运行 ParallelProcess
在 ParallelThread
之前,它们都需要 3 秒才能完成(忽略初始的 运行s,因为编译需要更长的时间)。我真的无法解释。
我们可以修改上面的代码进一步使用 ThreadPool
,这也导致 ParallelProcess
在 3 秒内完成(即使我没有修改那个版本)。这是 ParallelThread
和 ThreadPool
我想出的版本:
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var events = new ManualResetEvent[10];
for (int i = 0; i < 10; i++)
{
int x = i;
events[x] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem
(
_ =>
{
Thread1(x);
events[x].Set();
}
);
}
for (int i = 0; i < 10; i++)
{
events[i].WaitOne();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(3000);
}
注意:我们可以在事件上使用 WaitAll
,但在 STAThread
.
您有 Thread.Sleep(3000)
,这是我们看到的 3 秒。这意味着我们并没有真正测量任何这些方法的开销。
所以,我决定进一步研究这个问题,为此,我提高了一个数量级(从 10 到 100)并删除了 Console.WriteLine
(无论如何都会引入同步)。
这是我的代码清单:
void Main()
{
ParallelThread();
ParallelProcess();
}
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 100, x =>
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var events = new ManualResetEvent[100];
for (int i = 0; i < 100; i++)
{
int x = i;
events[x] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem
(
_ =>
{
Thread1(x);
events[x].Set();
}
);
}
for (int i = 0; i < 100; i++)
{
events[i].WaitOne();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
}
ParallelThread
需要 6 秒,ParallelProcess
需要 9 秒。即使在颠倒顺序后,情况仍然如此。这让我更加确信这是对开销的真实衡量。
添加 ThreadPool.SetMinThreads(100, 100);
可将 ParallelThread
(请记住此版本使用的是 ThreadPool
)和 ParallelProcess
的时间缩短为 3 秒。意味着这个开销来自线程池。现在,我可以回到生成新线程的版本(修改为生成 100 并带有 Console.WriteLine
注释):
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = new Thread[100];
for (int i = 0; i < 100; i++)
{
int x = i;
threads[i] = new Thread(() => Thread1(x));
threads[i].Start();
}
for (int i = 0; i < 100; i++)
{
threads[i].Join();
}
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.Seconds));
return true;
}
private static void Thread1(int x)
{
/*Console.WriteLine(string.Format("Printing {0} thread = {1}", x,
Thread.CurrentThread.ManagedThreadId));*/
Thread.Sleep(3000);
}
我从这个版本得到一致的 3 秒(这意味着时间开销可以忽略不计,因为正如我之前所说,Thread.Sleep(3000)
是 3 秒),但是我想指出它会留下更多垃圾收集比使用 ThreadPool
或 Parallel.For
。另一方面,使用 Parallel.For
仍然与 ThreadPool
相关联。顺便说一句,如果你想降低它的性能,减少最小线程数是不够的,你还必须降低最大线程数(例如ThreadPool.SetMaxThreads(1, 1);
)。
总而言之,请注意 Parallel.For
更容易使用,更不容易出错。
Invoking 10 threads would appear to be faster, can anyone please explain?
产生线程的速度很快。虽然,它会导致更多的垃圾。另外,请注意你的测试不是很好。
Would threads use multiple processors available in the system (to get executed in parallel) or does it just do time slicing in reference to CLR?
是的,他们会的。它们映射到底层操作系统线程,可以被它抢占,并根据它们的亲和力 运行 在任何内核中(参见 ProcessThread.ProcessorAffinity
)。需要明确的是,它们不是 fibers 也不是协同程序。
你这里有很多错误。
(1) 不要使用 sw.Elapsed.Seconds
这个值是一个 int
并且(显然)t运行 包含时间的小数部分。更糟糕的是,如果您有一个需要 61 秒才能完成的过程,这将报告 1
,因为它就像时钟上的秒针。您应该改用 sw.Elapsed.TotalSeconds
,它报告为 double
,它显示总秒数,无论多少分钟或几小时等。
(2) Parallel.For
使用线程池。这显着减少(甚至消除)创建线程的开销。每次调用 new Thread(() => ...)
时,您都会分配超过 1MB 的 RAM 并在进行任何处理之前消耗宝贵的资源。
(3) 您正在使用 Thread.Sleep(3000);
人为地加载线程,这意味着您掩盖了创建大量睡眠线程所需的实际时间。
(4) 默认情况下,Parallel.For
受限于 CPU 上的内核数量。因此,当您 运行 10 个线程时,工作被分成两步 - 这意味着 Thread.Sleep(3000);
被连续 运行 两次,因此它是 运行 的 6 秒宁。 new Thread
方法是 运行 一次连接所有线程,这意味着它只需要 3 秒多一点,但是 Thread.Sleep(3000);
再次淹没了线程启动时间。
(5) 您还在处理 CLR JIT 问题。第一次 运行 您的代码的启动成本是巨大的。让我们更改代码以删除睡眠并正确加入线程:
public static bool ParallelProcess()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.For(0, 10, x =>
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", x, Thread.CurrentThread.ManagedThreadId));
});
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.TotalMilliseconds));
return true;
}
public static bool ParallelThread()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var threads = Enumerable.Range(0, 10).Select(x => new Thread(new ThreadStart(Thread1))).ToList();
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
sw.Stop();
Console.WriteLine(string.Format("Time in secs {0}", sw.Elapsed.TotalMilliseconds));
return true;
}
private static void Thread1()
{
Console.WriteLine(string.Format("Printing {0} thread = {1}", 0, Thread.CurrentThread.ManagedThreadId));
}
现在,为了摆脱 CLR/JIT 启动时间,我们 运行 代码如下:
ParallelProcess();
ParallelThread();
ParallelProcess();
ParallelThread();
ParallelProcess();
ParallelThread();
我们得到的时间是这样的:
Time in secs 3.8617 Time in secs 4.7719 Time in secs 0.3633 Time in secs 1.6332 Time in secs 0.3551 Time in secs 1.6148
与第二和第三次 运行 相比,开始的 运行 时间非常糟糕,后者更加一致。
结果是 运行ning Parallel.For
比调用 new Thread
.