这篇文章中找了一个具体的算法,比较C++/node/python 的性能,并对结果给出解释。最后部分介绍了使用 C/C++ 技术提升node 性能的一个技术框架。
过去有一道经典的面试题,内容如下:一个台阶总共有N级,如果一次可以跳3级,也可以跳5级,求总共有多少种跳法。假设现在算 N = 123 时,计算总共多少种跳法。下面用代码给出具体实现
C++ 实现
#include "stdafx.h"
/*
Name:
Copyright:
Author:
Date:
Description: 跳台阶问题(递归)
题目:一个台阶总共有n级,如果一次可以跳3级,也可以跳5级,求总共有多少跳法
*/
using namespace std;
// 总共台阶数目
int n_;
// 工作数组
int *x_ = NULL;
// 记录总体数目
int v_ = 0;
void cfun(int t){
int sum = 0;
for(int i = 1; i < t; ++i)
sum += x_[i];
if(sum >= n_){
if(sum == n_){
++v_;
}else
return;
}else{
for(int i = 1;i <= 2; ++i){
if(1 == i)
x_[t] = 3;
else
x_[t] = 5;
cfun(t+1);
}
}
}
int _tmain(int argc, _TCHAR* argv[]) {
cout<<"N=";
cin>>n_;
DWORD bef = timeGetTime();
x_ = new int[n_+1];
cfun(1);
printf("花费时间 %d\n", timeGetTime() - bef);
cout<<v_<<"种"<<endl;
//del x_;
system("pause");
return 0;
}
Js 实现,可优化
'use strict';
var N = 123;
var x_ = new Array(N + 1);
var v_ = 0;
function cfun(t){
var sum = 0;
var i;
for(i = 1; i < t; ++i){
sum += x_[i];
}
if(sum >= N){
if(sum == N){
++v_;
}else
return;
}else{
for(i = 1; i <= 2; ++i){
if(1 == i)
x_[t] = 3;
else
x_[t] = 5;
cfun(t + 1);
}
}
}
var bec = new Date().getTime();
cfun(1);
console.log('花费时间(毫秒) ' + ((new Date()).getTime() - bec));
console.log(v_ + '种');
Js 实现,不可优化
'use strict';
var N = 123;
var x_ = new Array(N + 1);
var v_ = 0;
function cfun(t){
let sum = 0;
var i;
for(i = 1; i < t; ++i){
sum += x_[i];
}
if(sum >= N){
if(sum == N){
++v_;
}else
return;
}else{
for(i = 1; i <= 2; ++i){
if(1 == i)
x_[t] = 3;
else
x_[t] = 5;
cfun(t + 1);
}
}
}
var bec = new Date().getTime();
cfun(1);
console.log('花费时间(毫秒) ' + ((new Date()).getTime() - bec));
console.log(v_ + '种');
Python 实现
# encoding: utf-8
from random import *
from time import *
bef = int(time()*1000)
N = 123
x_ = [0 for i in range(N + 1)]
v_ = 0
def cfun(t):
global x_
global v_
sum = 0
i = 1
while i < t:
sum = sum + x_[i]
i += 1
if sum >= N:
if sum == N:
v_ += 1
else:
return
else:
i = 1
while i <= 2:
if i == 1:
x_[t] = 3
else:
x_[t] = 5
cfun(t + 1)
i += 1
return
cfun(1)
print(u'花费时间 ' + str(int(time()*1000) - bef))
print(v_)
N取 123,测试结果是 上面的这个图真实的记载了 N=123 时的运行时间,毫无疑问,C++ 代码最快,效率上秒杀脚本语言。Js 代码在开启优化和优化失败的时候,性能相差10倍。开启优化后,执行速度大概是 C++ 的五分之一。测试中,python 使用的是 2.7,最慢。
这次对比中,Python 的性能最差,这是在没有使用任何优化技术的情况下得出的。Python有一个超集 Cython ,可以将py源码编译成C 库,它支持确定类型,这种语法的扩展经过编译之后,其实就是静态语言编译为相应平台机器码的效率,性能已与C相当。
最有趣的是Js代码部分,使用如下命令 node --trace-opt --trace-deopt --prof
分别运行这两个文件,会发现一个可以优化,一个无法优化,信息如下
node --trace-opt --trace-deopt --prof jump.js
[marking 000002CBBBB6C4B9 <JS Function cfun (SharedFunctionInfo 0000029FB77F59C9)> for optimized recompi
lation, reason: hot and stable, ICs with typeinfo: 14/14 (100%), generic ICs: 0/14 (0%)]
[compiling method 000002CBBBB6C4B9 <JS Function cfun (SharedFunctionInfo 0000029FB77F59C9)> using Cranks
haft]
[optimizing 000002CBBBB6C4B9 <JS Function cfun (SharedFunctionInfo 0000029FB77F59C9)> - took 0.165, 0.28
1, 0.138 ms]
[completed optimizing 000002CBBBB6C4B9 <JS Function cfun (SharedFunctionInfo 0000029FB77F59C9)>]
node --trace-opt --trace-deopt --prof jump-no-opt.js
[marking 00000116B516C5E1 <JS Function cfun (SharedFunctionInfo 0000018DB6E759E9)> for optimized recompilation, reason: hot and stabl
e, ICs with typeinfo: 14/14 (100%), generic ICs: 0/14 (0%)]
[compiling method 00000116B516C5E1 <JS Function cfun (SharedFunctionInfo 0000018DB6E759E9)> using Crankshaft]
[disabled optimization for 0000018DB6E759E9 <SharedFunctionInfo cfun>, reason: Unsupported let compound assignment]
V8 使用一个叫 Crankshaft 的编译优化器处理代码,两份文件差别仅在于一处 let 关键字的使用。正是这一处let,导致后面的那个源码无法被优化。
从原理上看,typescript 是 js 的一个超集,但与cython是python超集不同,typescript 是把代码编译为js 给 V8 执行,而不是翻译成c编译成机器码。typescript 支持静态定义类型,因此理论上使用静态类型的typescript模块也可以翻译成 c 然后调用 c 编译器编译成.node。但是这样做必要性没有python 强,因为V8优化之后,代码执行速度已经比较接近 c++了。所以这里需要注意的是,不要因为某些关键字的使用,让自己的代码无法被优化。
node 适合I/O密集型的场景,我们所有的代码运行在main线程,因此它特别不适合计算量稍大,或者处理大块数据,尤其伴随Buffer和String 的转换的场景,频繁的GC会极大的拖慢系统,计算型任务也会阻塞主线程,使得CPU飚的很高,但任务吞吐量上不去。
c++ 代码操作内存,拷贝大块数据不存在GC的过程;多线程利用多核,可以解决主线程被计算型函数的阻塞的风险。因此可以考虑给 node 做一个多线程扩展,在 js 代码里创建并管理线程,将复杂任务委托给线程执行。因为操作系统存在线程执行的亲和性,node 主线程占用一个核心,可以考虑把其他核作为一个 IO 资源来用。node-threadobject是这样一个模块。
本文系原创,转载请注明出处~
一个let差这么多,还有理由坚持用吗?
😂 From Noder
用了let,const这类关键字的话,反而会慢吗?
代码中一处用 let 关键字代替var,导致V8无法优化代码
let、const主要在于开发友好度上面,没啥项目会受到这点性能影响吧
感觉pypy也应该拿进来测,有一次我也是测个性能问题,cpython跑起来超慢,但pypy比node快。
@gzhangzy 有理由, 以后V8会对let做优化的,根本不用担心
@Equim-chan 目前确实是,因为官方只是为了实现,而没有考虑优化
@gzhangzy 循环异步的时候有用,慢一点还是能忍的
测试了一下在node v6.9.4 下面优化和无法优化的版本,没有提示let无法被优化 无法优化版本先是用Crankshaft引擎优化,提示无法优化,然后V8检测到cFun这个函数频繁被调用,又尝试用TurboFan来优化代码,则提示成功了
两者相差2倍的时间
took 0.085, 0.204, 0.161 ms,这一行是表示什么呢?
应该V8版本对于let的优化还是有一定的支持,至于时间为什么还是会比var慢2倍时间,还请牛人解惑,难道是应为let是区块作用域在这种频繁的递归函数里,V8引擎没有办法复用隐藏类引起的吗?
看了这篇文章觉得有些思路,望牛人指点. Node.js背后的V8引擎优化技术
我使用的是 V-7 的版本,使用比较新的 7.10.0 仍然存在这个问题,此问题似乎还没有被注意到
似乎不是LET的问题? sum += x_[i]; 改成 sum = sum + x_[i]; 优化就成功了,具体是什么原因真搞不清楚,应该是+=效率更高啊.很多教程都是推崇+=的.
下图是修改版不可优化:
下图是可优化版:
觉得还是优化器没有做好,不过,也不差这点效率了,更多是IO 瓶颈要处理