介绍一次 c/python/node的性能对比
发布于 8 年前 作者 classfellow 14200 次浏览 来自 分享

这篇文章中找了一个具体的算法,比较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是这样一个模块。

本文系原创,转载请注明出处~

14 回复

一个let差这么多,还有理由坚持用吗?

用了let,const这类关键字的话,反而会慢吗?

代码中一处用 let 关键字代替var,导致V8无法优化代码

let、const主要在于开发友好度上面,没啥项目会受到这点性能影响吧

感觉pypy也应该拿进来测,有一次我也是测个性能问题,cpython跑起来超慢,但pypy比node快。

https://zhuanlan.zhihu.com/p/23290611?refer=alsotang

@gzhangzy 有理由, 以后V8会对let做优化的,根本不用担心

@Equim-chan 目前确实是,因为官方只是为了实现,而没有考虑优化

@gzhangzy 循环异步的时候有用,慢一点还是能忍的

测试了一下在node v6.9.4 下面优化和无法优化的版本,没有提示let无法被优化 无法优化版本先是用Crankshaft引擎优化,提示无法优化,然后V8检测到cFun这个函数频繁被调用,又尝试用TurboFan来优化代码,则提示成功了 untitled1.png

两者相差2倍的时间

untitled3.png

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]; 优化就成功了,具体是什么原因真搞不清楚,应该是+=效率更高啊.很多教程都是推崇+=的.

下图是修改版不可优化: untitled2.png

下图是可优化版: untitled3.png

觉得还是优化器没有做好,不过,也不差这点效率了,更多是IO 瓶颈要处理

三段代码,我也跑了一遍,node反而比C++快,啥情况- - image.png image.png

回到顶部