为什么 C++ 只比 VBA 快 4倍?

用C++和VBA分别写了一段用随机数字测试\pi 的值的算法,用500万个随机数,发现VBA需要570毫秒,而C++则需要150毫秒。这也太让人失望了把,C++写起来那么麻烦,起码也要快个几个数量级吧?python就更别说了,居然要7秒钟。求各位大神帮我看看是不是我的代码没优化好? (平台是Visual Studio,已经开了Release模式) 计算方法:生成a,b两个在0 到 r 之间的随机数,数一数这些数字里面有多少个落在了半径为r的1/4圆的扇形里面,用…
关注者
1870
被浏览
145827
这是一个非常好的问题。我们面对的是一个非常模糊的问题,下面主要说如何利用工具来寻找线索/原因,进而找到解决方法——这也是高级码农的基本修养。

我首先在VS2013, Release模式下面跑了一下C++代码,时间是208ms。基本重现了C++部分的结果。这个速度的确不能算快。在我们推测具体原因以前,一定一定要先profile,看看每一行所花费的时间是多少(不成熟的优化是万恶之源)。在VS里这个非常简单(要求VS Ultimate,但一般订阅了MSDN AA的学校都有免费的VS Ultimate)。只要点一个按钮就好了,如下图所示。


出来的结果类似:


我们可以看到,如 @姚冬 所说,rand()占用了56.3%的时间,是性能的瓶颈。此外,整数的减法也占用了33.4%的时间。

知道了原因以后就好办了。第一步,放狗搜索c++ slow rand。出来一大坨结果。其中一个是
Need a fast random generator for c++
里面提供了一个快速的rand()实现。把代码粘进去。
unsigned int g_seed = 0;
unsigned int fastrand() {
	g_seed = (214013 * g_seed + 2531011);
	return (g_seed >> 16) & 0x7FFF;
}
瞬间提速1倍到了99ms,结果不变。再profile得到:
一个诡异的地方是这是个整数运算应该极其快,而且不应该有类型转换在里面。但是profiler说50%的时间都在float到long的类型转换上面(注意图片右上角)。进一步检查发现,rand_max怎么是double。改成int,结果不变,时间变成43ms,进一步提速2倍。再profile得到:

这时候我们可以看到时间非常平均,也没有明显瓶颈了。优化结束。
考虑到我们机器的不同,我的43ms大约相当于你的机器的31ms,相比于vba的570ms有了18倍的性能差异,感觉还是比较合理的。

几点讨论:
  • 其实在这个回答里,核心并不是程序优化的具体技巧,而是拿到一个问题如何思考和利用工具的通用方法。比如即使我们不知道profiler这个东西,通过搜索"代码 每一行 时间"也可以很快知道有这样的工具叫做profiler,并且学会怎么使用。即使不知道rand这个函数怎么加速,通过搜索引擎也可以找到别人写好的现成代码。另一方面是发现瓶颈之后也不要着急自己修复,如果不是特别一目了然的话,先看看别人是怎么做的。站在巨人的肩膀上,事半功倍。所以关键在于时刻知道自己想要的是什么,和分析-调研-实验的思维习惯。
  • 具体关于程序优化,我们绝大多数人没有 @姚冬 那么牛的经验,一眼就能看出问题在哪里。所以遇到性能问题,第一反应应当是用profiler看看瓶颈到底在哪。而且一个经验是这个瓶颈往往是很难猜的——比如这个例子直接看代码第一反应往往是用代数和工程方法去优化算半径的那部分。但就算这部分做到极致,rand速度提不上去,最多也只能把时间降到原来的一半,事倍功半。以前我写代码也会在写的时候用各种奇技淫巧提升速度,但后来发现总体上程序的速度并没有得到提升。因为程序80%的时间其实花在20%的代码里,剩下80%的代码就算花个两个月优化到速度无穷快,也还是白瞎。所以一个兼顾开发和运行效率的方法是,先怎么方便开发怎么写,然后用profiler找到瓶颈再有针对性地优化。
  • 前两点不仅可以节省时间,可能更重要的是,如果你面对的不仅是一个工程,而且是老板,你要说服老板你这么做的原因。这些profile的结果、别人的讨论、你自己的实验结果,都会1) 说服老板你这么做是对的,2) 给老板留下深刻印象:你干了很多事,脑子清楚。以后升迁啥的都有帮助。
  • 不懂的问题上知乎问!这也是非常重要的一部分。
  • 上面用的是Windows平台的VS,方便好用但也非常贵。如果是Linux平台下可以用gprof(不晓得有没有GUI版本的,望指教)。Mac下可以用XCode。但基本思路都是一样的。