代码改变世界

.NET 求和的效率

2011-07-14 13:32 鹤冲天 阅读(...) 评论(...) 编辑 收藏

昨日,写了篇文章《.NET 4:并行求和不爽》,得到大家的热心回复,受大家启发,逐步发现了并行求和效率不要的两个原因:

  1. 我用的是伪四核 CPU;
  2. .NET 求和(非并行)效率问题。

今天,就看下 .NET 求和的效率问题。这里不是要和 c、c++等其它语言进行对比,而是对 .NET 中各种求和方式的相对比较。

.NET 中求和有多种方式,可以用 Linq 中的 Sum 扩展方法,也可使用 foreach、for 等。

准备测试数据

先准备用来测试求和的数组:

1
2
3
4
var random = new Random();
var data = Enumerable.Range(1, 67108864)
    .Select(i => (long)random.Next(int.MaxValue))
    .ToArray();

一个很大的长整型数组,装满了随机数,生成大约用 5 秒。

数组求和

我想到的求和方法有下面 5 种,使用 Stopwatch 计时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Stopwatch watch = new Stopwatch();
//Enumerable.Sum
watch.Start();
var sum1 = data.Sum();
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
//foreach
watch.Restart();
var sum2 = 0L;
foreach (var d in data)
    sum2 += d;
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
//for Enumerable.Count
watch.Restart();
var sum3 = 0L;
for (int i = 0; i < data.Count(); i++) 
    sum3 += data[i];
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
//for Length
watch.Restart();
var sum4 = 0L;
for (int i = 0; i < data.Length; i++)
    sum4 += data[i];
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);
//for 常数 
watch.Restart();
var sum5 = 0L;
for (int i = 0; i < 67108864; i++)
    sum5 += data[i];
watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds);

分别是使用 Enumerable.Sum、foreach、for,for 循环又根据结束条件分成了三个。

以下是测试结果:

  1 2 3 4 5 6 7 8 9 10
Enumerable.Sum 651 706 640 652 640 644 645 669 649 652
foreach 406 431 400 412 403 410 406 409 418 405
for Enumerable.Count 16532 16883 17100 16668 16810 17636 17413 17012 17567 17655
for Length 341 338 356 349 341 338 304 338 339 337
for 常数 299 293 306 315 298 295 301 296 296 297

(单位:毫秒)

从上面表格中可以看出:

  • Enumerable.Count 方式效率最差
  • 另外两种 for 循环效率要高于 foreach, 远高于 Enumerable.Sum

列表求和

将前面的代码简单修改,即可用来测试列表 (List<T>)求和

得到的测试结果如下:

  1 2 3 4 5 6 7 8 9 10
Enumerable.Sum 766 693 703 974 704 971 699 966 978 751
foreach 698 707 693 682 741 706 686 697 700 709
for Enumerable.Count 1565 1570 1555 1562 1559 1700 1565 1684 1548 1752
for Length 759 772 761 750 749 788 786 774 747 762
for 常数 609 595 590 588 594 625 592 616 590 598

(单位:毫秒)

同样 Enumerable.Count 方式效率最差。

其他几中方式效率相差不太(相对于数组求和)。

总结

同是求和,不同和方式、不同的数据源有不同的效率。

这种效率差异在大规模计算时比较明显,少量计算则可忽略(毫秒级或微秒级的)。

 

说明:for + Enumerable.Count 的方式之以最慢,是因为每次循环都要调用 Enumerable.Count 方法(可从反编译的 IL 得知)。Enumerable.Count 方法本身效率不错,但经过近七千万次调用,落后 17 秒也是正常的,平均到每次也就 0.25 微秒(毫秒的千分之一)。常规使用,完全可以忽略,希望大家不要对 Enumerable.Count 产生误解。

有了这些测试数据,《.NET 4:并行求和不爽》一文中最后提出的问题也就很容易解答了。