你觉得 .NET 性能低,可能只是因为你的能力低

by Conmajia


感恩,你们的赞助让我在抓耳挠腮写文章时不至于断了香烟。


var s = [{
name: '◎梦想起航◎',
amount: 6.66,
date: '2019-02-19'
}, {
name: 'tzhang',
amount: 0.01,
date: '2019-02-19'
}, {
name: 'Tim',
amount: 6.00,
date: '2019-02-19'
}, {
name: '李敏',
amount: 3.00,
date: '2019-02-20'
}, {
name: '倾听',
amount: 5.00,
date: '2019-02-20'
}, {
name: 'metoer',
amount: 0.1,
date: '2019-02-20'
}, ];
var cache = {};
for (var i = 0; i < s.length; i++) {
if (!cache[s[i].name]) cache[s[i].name] = {
name: s[i].name,
amount: s[i].amount
};
else cache[s[i].name].amount += s[i].amount;
}
var app4 = new Vue({
el: '#app',
data: {
sponsors: cache,
show: true
}
});
$('#app').removeClass();
initTooltip();

破除现代迷信,人人有责。.NET的源码点这里 Fork

奇了个怪

.NET作为开发平台来说,不可否认是优雅的、高效的。可是有些人就是看不到它极高的开发效率,极短的市场推出时间,极强的扩展能力,非得在性能上较劲,把一帮小萌新忽悠得一说起 .NET就觉着是“慢”、“卡”的代名词。

这些人总是存在一个思维误区,认为程序性能是和语言挂钩的。您没事儿吧?真要这样,《数据结构》、《算法》这些课早他妈该撤了好吧。汽车和飞机速度确实不一样,但就算给您一架私人飞机,您能给表演表演每天开飞机去买菜?任何语言的性能瓶颈,大半还是出在使用这语言的身上。新手司机开着十辆布加迪也不可能拼过驰骋秋名山的五菱宏光老司机。咱们不抬杠,今天就只说性能,不说开发效率、上市速度。非要跟我抬杠,算上这些,可能我的“低性能”产品已经出街了,你憋半天憋不出屁的“高性能”产品还在扑街,优越个 8呢。

.NET的开发语言一般用C#,后边我就不区分 .NET和C# 了。F# 其实也差不多,反正最后都编译成intermediate language,可以认为是等价的(函数式编程更多的是一种思想,这个话题我以后会写)。实话实说,C# 这门编程语言,一开始就处于一个挺尴尬的位置。性能上,有C/C++ 在前边拦着;应用上,有Java、Python在后边堵截,不上不下。.NET玩家心里总憋着股邪火,只好左右互搏,自娱自乐了。

语言的比较

这个问题的根源,很大一部分在于很多程序员只会一种语言,甚至连这唯一的语言也不够精通,所以才会要么看不起别的语言,要么埋怨自己这语言性能太弱。想要一招鲜吃遍天,梦里可以,醒来最好还是把这茬给忘了。而那些对 .NET一知半解就开始嘲讽的“别的语言”玩家,就更别指望他们能理解到其中的精髓了。

把自己限制于一种语言的程序员会经常错过其他地方提供的重要机会。这样的程序员绝对不会被他们的老板或客户看成是专业领域中的专家。

——Oliver Sturm

C# 虽然定位尴尬,但是它本身是高度优化的,要比性能,无非就C/C++ 或者汇编才配跟它比。Java、Python这些,大家都是IL解释的,本是同根生,相煎急锤子,都不一定能比过C#。C系玩家就算了,特性使然确实比不过,但是事实上优化之后性能也大差不差(我马上就能证明这一点)。最奇怪的就是好多J、P玩家居然好意思笑话C# 性能辣鸡,完全没意识到自己那惨样儿。就好像宋小宝去够姚明手里的球,结果潘长江在边儿上笑得挺欢,笑您 呢?和机器码比起来,任何使用中间语言虚拟机/runtime的语言都有损失,这话没人能反驳吧?Benchmark,一般都是用最耗资源的操作——比如图像处理——来比较。这无非就是测试磁盘、内存的IO性能呗。操作系统最终的性能在那卡着,assembly可以达到95%,C系8、90%,C# 可能亏点儿,6、70%的样子。然而,这都不是重点,重点在于,你有没有完全利用到语言的全部特性,以及那些你认为低效率的算法,非要用它们真的大丈夫?40米大刀拿来削苹果,可能它的性能真的很低吧。

我过去的试验

2012年的时候我就测试过用C# GDI+读写bitmap,一开始用的常规算法,很慢。但是经过优化后,性能提升了363倍,我也没用别的任何类库,还是 .NET 2.0原生。您说怎么判断C# 的性能到底是低还是高?

▲ 2012 年的测试,主要内容已经丢失,我在网上找到的爬虫文章

当时一帮哥们儿看了我的试验都手痒秀了一遍优化:
鼠标移到作者名字上可以查看作者信息,他们都是真·大神

▼ 各路大神和我试验的结果(4096×4096@24bpp反色)
编号作者耗时(毫秒)实现方法平台
a.laviewpbt25汇编 + PowerBasici3 380M/3GB/Win7 32-bit
b.兰征鹏12~19VC++.NET调用SSE指令i7 860/12GB/Win7 64-bit
c.胡飞33重写C# 图像库、unsafe指针优于 a
d.Conmajia46原生GDI+、unsafe指针同a

上面这张表,可以看到,几种语言的性能是接近的,相差最大20毫秒(C# vs 汇编)。除了b项C++ 加了buff(当时i7是顶级CPU),换到同平台,基本差不多的。当技巧和算法都优化到极致之后,其实C#、VB、C++ 的性能非常接近了,轻微的差异在大部分场景下几乎是可以忽略的。这一点,我以前这个试验就是很好的解释。计算机技术发展到今天,语言的运行效率差异早就可以忽略不计了,方法论和算法的才是核心。事实上,对现代C# 而言,提高性能其实挺简单的,通过下面的试验你就可以看出来。

重新再试验

有人说,跟程序员交流,不要说那么多话,要么秀代码,要么秀妹子。我只有请出互联网的first lady——瑞典模特Lena Soderberg(展示的样本经过了处理)。Lena的介绍可以看昨天的新闻:《花花公子》的封面女郎,计算机图像界的女神
Lena是瑞典拼法,Lenna是英文拼法。

▲ Lena/Lenna

测试图我用Lena原图,超高清3831×8192@24bpp,文件大小94.2MB。完整的 fuli可以在 lenna.org 下载。

▲ Playboy 1972 Miss November

拿DIP里最最简单的反色举例,同样用C#,一个萌新和一个大神分别实现,那最后的效率可差老了去了。不信咱来试试。(例子使用的方法仅限演示,不代表标准方法,也不是我这篇文章要讲述的主题——它只是个例子)

萌新的实现:单线程、managed

虽然有点儿欺负人,但它确实是实际存在的现象。新手玩家不管是熟练度还是连招都跟老油条没得比。

private void invert(Bitmap bmp) {
  Color sc = new Color();
  // 逐像素处理
  for (int i = 0; i < bmp.Width; i++) {
    for (int j = 0; j < bmp.Height; j++) {
      // managed operations
      sc = bmp.GetPixel(i, j);
      bmp.SetPixel(i, j, Color.FromArgb(0xff - sc.R, 0xff - sc.G, 0xff - sc.B));
    }
  }
}

这种做法理论上是没错的,也能实现效果,就是太慢了。于是萌新可能就开始骂街:C# 真辣鸡,.NET真辣鸡。可是这算法就他妈的辣鸡啊!这种单线程的managed语法,想快它也快不起来啊。

▲ 你来告诉我1个核上慢慢摇性能怎么提起来?

大神的实现:并行计算、unsafe

大神之所以能叫大神,因为他们可以利用语言以及计算机全部实力。思路上,他们能够选用最佳的合适算法;技巧上,他们可以挖掘电脑的所有潜力。同样的语言,不同的性能,我再次提出那个问题:你说瓶颈在语言还是在人

private void invert(Bitmap bmp) {
  BitmapData bdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
  Parallel.ForEach(Partitioner.Create(0, bdata.Height), (H) => {
    unsafe {
      int x, y, w, h, s;
      byte * s0, p;
      w = bdata.Width;
      h = bdata.Height;
      s = bdata.Stride;
      s0 = (byte * ) bdata.Scan0;
      // 逐像素处理
      for (y = H.Item1; y < H.Item2; y++) {
        p = s0 + y * s;
        // unsafe pointer write operations
        for (x = 0; x < w; x++) {
          * p = (byte)(0xff - * p);
          p++;* p = (byte)(0xff - * p);
          p++;* p = (byte)(0xff - * p);
          p++;
        }
      }
    }
  });
  bmp.UnlockBits(bdata);
}

注意这个实现用到了并行计算,没有用SetPixel而是直接用指针操作的,虽然有点像C/C++,但是依然属于原生C# 的范畴。可是我不会逐条讲解为什么用了这些技术性能就可以提高。我这文章的主题不是教你怎么用Parallel或者给你对比单线程和多线程的性能差,而是演示给你看初哥和大手子能轻易拉开多大的距离,以此反驳那些动不动觉得某某语言性能不行的门外汉。我可不负责科普教学,那是你自己解决的事儿。

萌新 vs 大神

▲ 同一种语言,同一个任务

看看结果吧!42466ms对上101ms,420倍的差距。谁跟我说 .NET性能差来着?请大点儿声,谢谢!🙏

完毕

其实就上面的例子来说,还能把性能再往上提,但这都是次要的。我想表达的意思已经表达完了:.NET的性能并不低,如果你觉得低,那可能是因为你的能力低

The End. \(\Box\)

僵尸兴奋地打开了你的头盖骨,又失望地走开了。旁边的屎壳郎眼前一亮。

posted @ 2019-02-19 15:52  Conmajia  阅读(...)  评论(...编辑  收藏