多线程 foreach 减慢主线程
Multithread foreach slows down main thread
编辑:根据评论中的讨论,我高估了多少线程会有所帮助,并以合理的 MaxDegreeOfParallelism
回到 Parallell.ForEach
,只需要等待它出。
我有一个二维数组数据结构,并对数据切片执行操作。同时处理所有数据只需要大约 1000 个线程。基本上所有 ~7000 个数据点大约有 1000“天”的数据,我想在新线程中并行处理每一天的数据。
我的问题是在子线程中工作显着 减慢了主线程启动它们的时间。如果我没有在子线程中完成任何工作,主线程基本上会立即启动它们。在我下面的例子中,只需要一点点工作,就需要大约 65 毫秒来启动所有线程。在我的实际用例中,工作线程将花费大约 5-10 秒来计算它们需要的所有内容,但我希望它们全部立即启动,否则,我基本上 运行 按顺序执行工作。我不明白为什么他们的工作会减慢启动它们的主线程。
数据的设置方式无关紧要(我希望如此)。它的设置方式可能看起来很奇怪,我只是在模拟我如何接收数据。重要的是,如果您在 DoThreadWork
方法中注释掉 foreach
循环,则启动线程所需的时间会更短。
我有 for (var i = 0; i < 4; i++)
循环只是为了 运行 多次模拟以查看 4 组计时结果,以确保它不仅仅是第一次慢。
这是模拟我的真实代码的代码片段:
public static void Main(string[] args)
{
var fakeData = Enumerable
.Range(0, 7000)
.Select(_ => Enumerable.Range(0, 400).ToArray())
.ToArray();
const int offset = 100;
var dataIndices = Enumerable
.Range(offset, 290)
.ToArray();
for (var i = 0; i < 4; i++)
{
var s = Stopwatch.StartNew();
var threads = dataIndices
.Select(n =>
{
var thread = new Thread(() =>
{
foreach (var fake in fakeData)
{
var sliced = new ArraySegment<int>(fake, n - offset, n - (n - offset));
DoThreadWork(sliced);
}
});
return thread;
})
.ToList();
foreach (var thread in threads)
{
thread.Start();
}
Console.WriteLine($"Before Join: {s.Elapsed.Milliseconds}");
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"After Join: {s.Elapsed.Milliseconds}");
}
}
private static void DoThreadWork(ArraySegment<int> fakeData)
{
// Commenting out this foreach loop will dramatically increase the speed
// in which all the threads start
var a = 0;
foreach (var fake in fakeData)
{
// Simulate thread work
a += fake;
}
}
使用 thread/task 池并将 thread/task 计数限制为最多 2*(CPU Cores)
。创建更多线程并不能神奇地完成更多工作,因为您需要 运行 个硬件“线程”(non-SMT CPU 的每个 CPU 核心 1 个,2每个内核用于 Intel HT,AMD 的 SMT 实现)。执行数百到数千个不必被动等待异步回调(即 I/O)的线程会使 运行 线程的效率大大降低,因为 CPU 与上下文切换不兼容原因。
编辑:根据评论中的讨论,我高估了多少线程会有所帮助,并以合理的 MaxDegreeOfParallelism
回到 Parallell.ForEach
,只需要等待它出。
我有一个二维数组数据结构,并对数据切片执行操作。同时处理所有数据只需要大约 1000 个线程。基本上所有 ~7000 个数据点大约有 1000“天”的数据,我想在新线程中并行处理每一天的数据。
我的问题是在子线程中工作显着 减慢了主线程启动它们的时间。如果我没有在子线程中完成任何工作,主线程基本上会立即启动它们。在我下面的例子中,只需要一点点工作,就需要大约 65 毫秒来启动所有线程。在我的实际用例中,工作线程将花费大约 5-10 秒来计算它们需要的所有内容,但我希望它们全部立即启动,否则,我基本上 运行 按顺序执行工作。我不明白为什么他们的工作会减慢启动它们的主线程。
数据的设置方式无关紧要(我希望如此)。它的设置方式可能看起来很奇怪,我只是在模拟我如何接收数据。重要的是,如果您在 DoThreadWork
方法中注释掉 foreach
循环,则启动线程所需的时间会更短。
我有 for (var i = 0; i < 4; i++)
循环只是为了 运行 多次模拟以查看 4 组计时结果,以确保它不仅仅是第一次慢。
这是模拟我的真实代码的代码片段:
public static void Main(string[] args)
{
var fakeData = Enumerable
.Range(0, 7000)
.Select(_ => Enumerable.Range(0, 400).ToArray())
.ToArray();
const int offset = 100;
var dataIndices = Enumerable
.Range(offset, 290)
.ToArray();
for (var i = 0; i < 4; i++)
{
var s = Stopwatch.StartNew();
var threads = dataIndices
.Select(n =>
{
var thread = new Thread(() =>
{
foreach (var fake in fakeData)
{
var sliced = new ArraySegment<int>(fake, n - offset, n - (n - offset));
DoThreadWork(sliced);
}
});
return thread;
})
.ToList();
foreach (var thread in threads)
{
thread.Start();
}
Console.WriteLine($"Before Join: {s.Elapsed.Milliseconds}");
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"After Join: {s.Elapsed.Milliseconds}");
}
}
private static void DoThreadWork(ArraySegment<int> fakeData)
{
// Commenting out this foreach loop will dramatically increase the speed
// in which all the threads start
var a = 0;
foreach (var fake in fakeData)
{
// Simulate thread work
a += fake;
}
}
使用 thread/task 池并将 thread/task 计数限制为最多 2*(CPU Cores)
。创建更多线程并不能神奇地完成更多工作,因为您需要 运行 个硬件“线程”(non-SMT CPU 的每个 CPU 核心 1 个,2每个内核用于 Intel HT,AMD 的 SMT 实现)。执行数百到数千个不必被动等待异步回调(即 I/O)的线程会使 运行 线程的效率大大降低,因为 CPU 与上下文切换不兼容原因。