当汇总为双精度时,从 ForEach 循环转换为 Parallel.ForEach 循环会减慢速度

2024-02-14

我有一段 C# 代码如下。此代码总结了 DataTable 中的一列“双精度”:

var data = this.Db.ExecuteRead(query, this.Score.Name);
var time = 0.0;
foreach (DataRow row in data.Rows)
{
    time += this.ParseDouble(row[0].ToString()) / MillisecondsPerMinute;
}

执行此代码需要 4 秒。我想加快速度,所以我将其并行化如下:

Parallel.ForEach(
                data.AsEnumerable(),
                row =>
                    {
                        time += this.ParseDouble(row[0].ToString()) / MillisecondsPerMinute;
                    });

执行此代码需要 3 秒。它还会导致碰撞。我不认为“双”线程安全。这是预料之中的。然后我添加了一个互斥体以使其线程安全:

Parallel.ForEach(
                data.AsEnumerable(),
                row =>
                    {
                        mut.WaitOne();
                        ptime += this.ParseDouble(row[0].ToString()) / MillisecondsPerMinute;
                        mut.ReleaseMutex();
                    });

这段代码要慢得多。执行需要 15 秒,但会产生准确的结果。我的问题是,我是否最好继续使用标准的“ForEach”,或者我可以以更好的方式实现多线程?

作为参考,这里是 ParseDouble 方法:

protected double ParseDouble(string text)
{
    double value;
    if (!double.TryParse(text, out value))
    {
        throw new DoubleExpectedException();
    }

    return value;
}

这里有一些方法。首先一个简单的Parallel.ForEach,减少保护区(lock https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement)到所需的绝对最小值(共享状态的更新)。这应该可以最大限度地减少对锁的争用。

DataTable data = this.Db.ExecuteRead(query, this.Score.Name);
double totalTime = 0.0;
Parallel.ForEach(data.AsEnumerable(), row =>
{
    double time = Double.Parse(row[0].ToString()) / MillisecondsPerMinute;
    lock (data) { totalTime += time; }
});

A PLINQ https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq方法。简单且安全,但可能不是最有效的:

double totalTime = data
    .AsEnumerable()
    .AsParallel()
    .Select(row => Double.Parse(row[0].ToString()) / MillisecondsPerMinute)
    .Sum();

的组合Parallel.ForEach and Partitioner.Create https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.partitioner.create应该提供最佳性能,因为它允许分块工作负载:

double totalTime = 0.0;
Parallel.ForEach(Partitioner.Create(0, data.Rows.Count), () => 0.0D,
    (range, state, accumulator) =>
{
    for (int i = range.Item1; i < range.Item2; i++)
    {
        DataRow row = data.Rows[i];
        accumulator += Double.Parse(row[0].ToString()) / MillisecondsPerMinute;
    }
    return accumulator;
}, accumulator =>
{
    lock (data) { totalTime += accumulator; }
});
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当汇总为双精度时,从 ForEach 循环转换为 Parallel.ForEach 循环会减慢速度 的相关文章

随机推荐