【发布时间】:2019-09-27 22:43:48
【问题描述】:
我发现Task.Run和plinq结合起来非常慢,所以我做了一个简单的实验:
int scale = 32;
Enumerable.Range( 0, scale ).AsParallel().ForAll( i => {
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ ) { }
} );
} );
plinq 内的 plinq 运行良好,在 14 毫秒内完成
int scale = 32;
Task[] tasks = Enumerable.Range( 0, scale ).Select( i => Task.Run( async () =>
{
Task[] _tasks = Enumerable.Range( 0, scale ).Select( j => Task.Run( () =>
{
for ( int k = 0; k < scale; k++ ) { }
} ) ).ToArray();
await Task.WhenAll( _tasks );
} ) ).ToArray();
await Task.WhenAll( tasks );
任务内部的任务也以 14 毫秒结束,但如果我将 Task.Run 内部替换为 plinq,如下所示:
int scale = 32;
Task[] tasks = Enumerable.Range( 0, scale ).Select( i => Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ ) { }
} );
} ) ).ToArray();
await Task.WhenAll( tasks );
执行需要 29 秒。如果scale 变量更大,情况会变得更糟。
谁能解释一下这个案例发生了什么?
编辑:
我又做了一个实验:
static async Task Main( string[] args )
{
Stopwatch stopwatch = Stopwatch.StartNew();
int scale = 8;
Task[] tasks = Enumerable.Range( 0, scale ).Select( id => Run( scale, id ) ).ToArray();
await Task.WhenAll( tasks );
Console.WriteLine( $"ElapsedTime={stopwatch.ElapsedMilliseconds}ms" );
}
static Task Run( int scale, int id )
{
return Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ )
{
}
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} for loop {j} end" );
} );
} );
}
这是结果的一部分:
[1557475215796]Task 0 for loop 6 end
[1557475215796]Task 0 for loop 7 end
[1557475216776]Task 4 for loop 0 end
[1557475216776]Task 4 for loop 1 end
[1557475216777]Task 4 for loop 2 end
[1557475216777]Task 4 for loop 3 end
[1557475216778]Task 4 for loop 4 end
[1557475216778]Task 4 for loop 5 end
[1557475216779]Task 4 for loop 6 end
[1557475216780]Task 4 for loop 7 end
[1557475217774]Task 5 for loop 0 end
[1557475217774]Task 5 for loop 1 end
[1557475217775]Task 5 for loop 2 end
查看每个任务之间的时间戳,你会发现每次移动到下一个任务时都有一个神秘的 1000 毫秒延迟。我猜 plinq 或任务中有一种机制会在某些情况下暂停一秒钟,这会显着减慢进程。
感谢@StephenCleary 的解释,现在我明白延迟来自线程的创建。我再次调整我的实验,发现ForAll 方法会阻塞任务,直到不同任务中的所有其他ForAll 方法完成。
static Task Run( int scale, int id )
{
return Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ )
{
}
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} for loop {j} end, thread count = {Process.GetCurrentProcess().Threads.Count}" );
} );
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} finished" );
} );
}
结果是:
[1557478553656]Task 6 for loop 6 end, thread count = 19
[1557478553657]Task 6 for loop 7 end, thread count = 19
[1557478554645]Task 7 for loop 0 end, thread count = 20
[1557478554647]Task 7 for loop 1 end, thread count = 20
[1557478554649]Task 7 for loop 2 end, thread count = 20
[1557478554651]Task 7 for loop 3 end, thread count = 20
[1557478554653]Task 7 for loop 4 end, thread count = 20
[1557478554655]Task 7 for loop 5 end, thread count = 20
[1557478554657]Task 7 for loop 6 end, thread count = 20
[1557478554659]Task 7 for loop 7 end, thread count = 20
[1557478555644]Task 1 finished
[1557478555644]Task 0 finished
[1557478555644]Task 3 finished
[1557478555644]Task 2 finished
[1557478555644]Task 4 finished
[1557478555644]Task 6 finished
[1557478555644]Task 5 finished
[1557478555644]Task 7 finished
我希望ForAll 方法应该立即返回。为什么会阻塞任务和线程?
【问题讨论】:
-
我想这会回答你的问题:stackoverflow.com/questions/19102966/….
-
我不确定
-
这里的差异是由于在一组
Task.Run的情况下使用异步,这将处理内部Task.Run,但在Task.Run内部使用PLinq时,它不是做同样的蜂巢是可行的,因为你阻塞了每一个外部任务,虽然你可能想使用异步包装器而不是同步,但是会付出很多努力,并不值得 -
两者都不慢。这段代码虽然有问题。首先,在
PLINQ或Parallel.ForEach中使用Task.Run是没有意义的。所有 CPU 内核已经用于处理输入数据。使用Task.Run生成的任何新任务都必须等到操作系统调度程序挂起 PLINQ 线程并开始执行新任务 -
在前两个 sn-ps 中,嵌套循环会损害性能。 CPU 不能同时运行比核心更多的任务,因此额外的任务必须等待调度。 PLINQ 和
Parallel.ForEach用于数据并行,您希望所有内核并行工作以处理大量数据。 PLINQ 通过创建与内核一样多的任务、对输入数据进行分区并将每个分区提供给工作任务来实现这一点。
标签: c# task-parallel-library plinq