Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/github/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 多通道GroupBy()如何比单通道更快?_C#_Ienumerable_Linq To Objects - Fatal编程技术网

C# 多通道GroupBy()如何比单通道更快?

C# 多通道GroupBy()如何比单通道更快?,c#,ienumerable,linq-to-objects,C#,Ienumerable,Linq To Objects,我无法理解GroupBy()在多过程ResultSelector中的执行速度如何比在单过程版本中更快 鉴于这一类别: public class DummyItem { public string Category { get; set; } public decimal V1 { get; set; } public decimal V2 { get; set; } } 我创建了一个包含100000个条目和一些随机数据的数

我无法理解GroupBy()在多过程ResultSelector中的执行速度如何比在单过程版本中更快

鉴于这一类别:

    public class DummyItem
    {
        public string Category { get; set; }
        public decimal V1 { get; set; }
        public decimal V2 { get; set; }
    }
我创建了一个包含100000个条目和一些随机数据的数组,然后迭代以下查询:

方法1:类别总数的多次通过

var q = randomData.GroupBy(
   x => x.Category,
   (k, l) => new DummyItem
   {
      Category = k,
      V1 = l.Sum(x => x.V1), // Iterate the items for this category
      V2 = l.Sum(x => x.V2), // Iterate them again
    }
);
var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => l.Aggregate( // Iterate the inner list once per category
            new decimal[2], 
            (t,d) => 
            {
                t[0] += d.V1;
                t[1] += d.V2;
                return t;
            },
            t => new DummyItem{ Category = k, V1=t[0], V2=t[1] }
    )
);
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
var result = randomData.Aggregate(new DummyItem(), (t, x) => 
{ 
     t.V1 += x.V1; 
     t.V2 += x.V2; 
     return t; 
});
它似乎在双重处理内部枚举,其中对每个类别的V1和V2求和

因此,我将以下备选方案放在一起,假设这将通过在一次传递中计算类别总数来提供更好的性能

方法2:类别合计单次通过

var q = randomData.GroupBy(
   x => x.Category,
   (k, l) => new DummyItem
   {
      Category = k,
      V1 = l.Sum(x => x.V1), // Iterate the items for this category
      V2 = l.Sum(x => x.V2), // Iterate them again
    }
);
var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => l.Aggregate( // Iterate the inner list once per category
            new decimal[2], 
            (t,d) => 
            {
                t[0] += d.V1;
                t[1] += d.V2;
                return t;
            },
            t => new DummyItem{ Category = k, V1=t[0], V2=t[1] }
    )
);
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
var result = randomData.Aggregate(new DummyItem(), (t, x) => 
{ 
     t.V1 += x.V1; 
     t.V2 += x.V2; 
     return t; 
});
相当典型的结果:

'Multiple pass': iterations=5 average=2,961 ms each
'Single pass': iterations=5 average=5,146 ms each
令人难以置信的是,方法2所用的时间是方法1的两倍。我已经运行了许多基准测试,这些测试改变了V*属性的数量、不同类别的数量和其他因素。虽然性能差异的大小不同,但方法2始终比方法1慢得多

我是不是错过了一些基本的东西?方法1如何比方法2更快

(我感觉到一个掌心人来了……)


*更新*

在@Jirka的回答之后,我认为应该从图片中删除GroupBy(),看看大型列表上的简单聚合是否按预期执行。这项任务只是简单地计算100000个随机行的同一列表中两个十进制变量的总数

结果继续令人惊讶:

SUM:ForEach

decimal t1 = 0M;
decimal t2 = 0M;
foreach(var item in randomData)
{
    t1 += item.V1;
    t2 += item.V2;
}
基线。我相信获得所需输出的最快方法

SUM:Multipass

var q = randomData.GroupBy(
   x => x.Category,
   (k, l) => new DummyItem
   {
      Category = k,
      V1 = l.Sum(x => x.V1), // Iterate the items for this category
      V2 = l.Sum(x => x.V2), // Iterate them again
    }
);
var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => l.Aggregate( // Iterate the inner list once per category
            new decimal[2], 
            (t,d) => 
            {
                t[0] += d.V1;
                t[1] += d.V2;
                return t;
            },
            t => new DummyItem{ Category = k, V1=t[0], V2=t[1] }
    )
);
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
var result = randomData.Aggregate(new DummyItem(), (t, x) => 
{ 
     t.V1 += x.V1; 
     t.V2 += x.V2; 
     return t; 
});
SUM:Singlepass

var q = randomData.GroupBy(
   x => x.Category,
   (k, l) => new DummyItem
   {
      Category = k,
      V1 = l.Sum(x => x.V1), // Iterate the items for this category
      V2 = l.Sum(x => x.V2), // Iterate them again
    }
);
var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => l.Aggregate( // Iterate the inner list once per category
            new decimal[2], 
            (t,d) => 
            {
                t[0] += d.V1;
                t[1] += d.V2;
                return t;
            },
            t => new DummyItem{ Category = k, V1=t[0], V2=t[1] }
    )
);
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
var result = randomData.Aggregate(new DummyItem(), (t, x) => 
{ 
     t.V1 += x.V1; 
     t.V2 += x.V2; 
     return t; 
});
结果如下:

'SUM: ForEach': iterations=10 average=1,793 ms each
'SUM: Multipass': iterations=10 average=2,030 ms each
'SUM: Singlepass': iterations=10 average=5,714 ms each
令人惊讶的是,它揭示了这个问题与GroupBy无关。该行为通常与数据聚合一致。我认为在一次过程中进行数据聚合更好的假设是完全错误的(可能是我的数据库根的遗留问题)

(脸掌)

正如@Jirka所指出的,多程进近的明显的内延,意味着它只比基线“ForEach”稍微慢一点。我天真地尝试优化一次传球,但速度慢了近3倍


在处理内存中的列表时,无论您希望对列表中的项执行什么操作,都可能比迭代开销对性能的影响更大。

聚合必须在过程中创建99999条激活记录(对于不可内联的方法调用)。这抵消了单程的优势


将计数、总和、平均数等视为聚合在一般情况下可以做什么的优化特例。

谢谢@Jirka。否该数组仅分配一次作为要聚合的种子。对于我的一些测试,这只有四次(即只有四个类别)。当迭代每个类别的可枚举项时,数组只会被更新。@degorolls-你说得对,我很抱歉疏忽了。我更正了我的答案。太棒了!谢谢@Jirka。我已经纠正了一个相当基本的误解……可能是使用Sum()更好地使用处理器寄存器、缓存等吗?就像当你计算几个多路径的总数时,它可能是一个处理器操作码,但是当你进行一次单次计算时,你必须添加到一个summ,store,获取其他summ,Add,store?@Alexander-结构DummyItem可能有大约40字节长,这排除了理论上可用于汇总紧密压缩数据的SSE优化(.NET可能无论如何都做不到)。数据可能有点太大,无法放入二级缓存中,这使单次传递算法具有优势。但是,我假设总和计算主要由读取输入控制,而聚合计算则由堆栈操作控制;堆栈操作包括缓存命中,同时输入未命中。感谢分享您的其他观察结果。不要放弃您的直觉。单次传递算法对于超过1 MB的数据确实具有性能优势。但在这里,这种优势与最内部(瓶颈)循环中发生的方法调用相比相形见绌。