使用 Parallel.For 搜索 minimum/maximum 值
Using Parallel.For for searching for minimum/maximum value
object _ = new object();
List<int> list = new List<int>(new int {1,25,3....18,255}); //random values
int bestIndex = 0;
int bestValue = int.MinValue;
Parallel.For(0, list.Length, (i) => {
if (list[i] > bestValue)
{
lock (_)
{
bestValue = list[i];
index = i;
}
}
});
我的问题是,这是否有意义。因为我怀疑只是在某些情况下会分配较低的值,即使它不应该分配。
您的代码不是线程安全的,因为您正在从多个线程读取和写入相同的值。如果修复此问题并将检查放在锁中,则不会获得任何并行性,更糟糕的是,在非常紧凑的循环中使用高度竞争的锁,这很可能会导致大量开销。我还建议不要使用 _
作为变量名,因为它在 c#7 中用于 Discard。
正确的解决方案是 运行 多个并行独立搜索,然后对每个线程的结果进行最终比较。
var globalMin = int.MaxValue;
var lockObj = new object();
Parallel.ForEach(list,
// LocalInit, runs once for each thread
() => int.MaxValue,
// The parallel body, runs on multiple threads
(value, _, localMin) =>
{
if (value < localMin)
{
return value;
}
return localMin;
},
// Local finally, runs once for each thread,
// given the final result produced on that thread
localMin =>
{
lock (lockObj)
{
if (localMin < globalMin)
{
globalMin = localMin;
}
}
});
return globalMin;
然而,这有点长且复杂,替代方法是使用 linq:
list.AsParallel().Min();
编辑:
我想补充一点,对这样一个简单的任务使用并行算法不太可能获得太多性能,至少对于基本类型而言是这样。 运行 并行处理在一次处理大量工作时最有用,因此同步开销只占整体工作的一小部分。您可以对列表进行一些手动分区,以确保每次迭代都做更多的工作来稍微提高性能。但是除非你有一个非常大的清单,否则通常不值得付出努力。
Parallel.For
不是为此类操作而构建的。这就是 PLINQ 的工作。您可以只用 :
替换您的代码
var min=list.AsParallel().Min();
这不需要任何锁。它将数据划分为与内核数量大致相同的分区,找到每个分区中的最小值,然后计算所有分区最小值中的最小值。
通过不与一个全局锁同步,这将导致接近最佳的性能
除了 JonasH 在他们的 , I would like to add that using the Parallel
class without specifying the MaxDegreeOfParallelism
through the ParallelOptions
is not a good idea. The default MaxDegreeOfParallelism
is -1
, which means unbounded parallelism, in practice bounded by the ThreadPool
availability. In other words calling any method of the Parallel
class results (by default) to the ThreadPool
becoming saturated for the whole duration of the parallel execution. Having a saturated ThreadPool
is problematic when there are other parallel/asynchronous operations also happening concurrently. As an example the Elapsed
event of the System.Timers.Timer
中所说的之外 class 在 ThreadPool
上被调用,所以这个事件在 Parallel
执行期间会很忙,因为有将没有可用的 ThreadPool
线程来及时为事件处理程序提供服务。
以下是如何配置 Parallel
循环的 MaxDegreeOfParallelism
,以缓解 ThreadPool
-饥饿问题:
var parallelOptions = new ParallelOptions()
{
MaxDegreeOfParallelism = Environment.ProcessorCount
};
Parallel.ForEach(list, parallelOptions, // ... It's OK now
在上面的示例中,Parallel.ForEach
循环将同时使用最多 Environment.ProcessorCount - 1
个线程池线程,加上当前线程。
此建议不适用于 PLINQ,它的默认最大并行度等于机器的逻辑处理器数。
object _ = new object();
List<int> list = new List<int>(new int {1,25,3....18,255}); //random values
int bestIndex = 0;
int bestValue = int.MinValue;
Parallel.For(0, list.Length, (i) => {
if (list[i] > bestValue)
{
lock (_)
{
bestValue = list[i];
index = i;
}
}
});
我的问题是,这是否有意义。因为我怀疑只是在某些情况下会分配较低的值,即使它不应该分配。
您的代码不是线程安全的,因为您正在从多个线程读取和写入相同的值。如果修复此问题并将检查放在锁中,则不会获得任何并行性,更糟糕的是,在非常紧凑的循环中使用高度竞争的锁,这很可能会导致大量开销。我还建议不要使用 _
作为变量名,因为它在 c#7 中用于 Discard。
正确的解决方案是 运行 多个并行独立搜索,然后对每个线程的结果进行最终比较。
var globalMin = int.MaxValue;
var lockObj = new object();
Parallel.ForEach(list,
// LocalInit, runs once for each thread
() => int.MaxValue,
// The parallel body, runs on multiple threads
(value, _, localMin) =>
{
if (value < localMin)
{
return value;
}
return localMin;
},
// Local finally, runs once for each thread,
// given the final result produced on that thread
localMin =>
{
lock (lockObj)
{
if (localMin < globalMin)
{
globalMin = localMin;
}
}
});
return globalMin;
然而,这有点长且复杂,替代方法是使用 linq:
list.AsParallel().Min();
编辑: 我想补充一点,对这样一个简单的任务使用并行算法不太可能获得太多性能,至少对于基本类型而言是这样。 运行 并行处理在一次处理大量工作时最有用,因此同步开销只占整体工作的一小部分。您可以对列表进行一些手动分区,以确保每次迭代都做更多的工作来稍微提高性能。但是除非你有一个非常大的清单,否则通常不值得付出努力。
Parallel.For
不是为此类操作而构建的。这就是 PLINQ 的工作。您可以只用 :
var min=list.AsParallel().Min();
这不需要任何锁。它将数据划分为与内核数量大致相同的分区,找到每个分区中的最小值,然后计算所有分区最小值中的最小值。
通过不与一个全局锁同步,这将导致接近最佳的性能
除了 JonasH 在他们的 Parallel
class without specifying the MaxDegreeOfParallelism
through the ParallelOptions
is not a good idea. The default MaxDegreeOfParallelism
is -1
, which means unbounded parallelism, in practice bounded by the ThreadPool
availability. In other words calling any method of the Parallel
class results (by default) to the ThreadPool
becoming saturated for the whole duration of the parallel execution. Having a saturated ThreadPool
is problematic when there are other parallel/asynchronous operations also happening concurrently. As an example the Elapsed
event of the System.Timers.Timer
中所说的之外 class 在 ThreadPool
上被调用,所以这个事件在 Parallel
执行期间会很忙,因为有将没有可用的 ThreadPool
线程来及时为事件处理程序提供服务。
以下是如何配置 Parallel
循环的 MaxDegreeOfParallelism
,以缓解 ThreadPool
-饥饿问题:
var parallelOptions = new ParallelOptions()
{
MaxDegreeOfParallelism = Environment.ProcessorCount
};
Parallel.ForEach(list, parallelOptions, // ... It's OK now
在上面的示例中,Parallel.ForEach
循环将同时使用最多 Environment.ProcessorCount - 1
个线程池线程,加上当前线程。
此建议不适用于 PLINQ,它的默认最大并行度等于机器的逻辑处理器数。