Nodejs 用c++模块写node的疑惑

Nodejs 用c++模块写node的疑惑

自从node出现,感觉事情一切都变的很美好,依靠node的异步和事件驱动做流程,抵御I/O的大并发,很多人攻击js做cpu密集型操作似乎有性能问题,于是我们可以利用c++做node模块来处理cpu密集型的操作,当然这样看上去似乎我们找到了通吃大部分情况的完美解决方案,而且node配合C++,多牛B多装B的组合,说出去特有面子。

可喜可贺的是我们用了斐波那契算法排名第二的语言开发应用,而且开发效率绝对比第一的C要高很多,斐波那契算法排名地址,感谢苏千的测试: http://fengmk2.github.com/blog/2011/fibonacci/nodejs-python-php-ruby-lua.html

但是在我近一个月的c++模块和v8引擎摸索后,我发现事情似乎不如想象中那么美好(当然只能是业余时间,有写错望大牛们指正)。

先上一个不是很完美的c++模块,用来做验证的,虽然有时候他的确比node本身要快: https://github.com/DoubleSpout/node-hvalidator 你也可以 npm install hvalidator 进行安装。

我们来看下replace操作的测试结果: 我们利用hvalidator模块中的toXss函数来替换掉可能xss注入攻击的字符串,使用方法如下:

var hv = require('hvalidator');
hv.toXss(str);//Remove common XSS attack

然后我们同样构建js的代码:

function toxss(text){
 text = text.replace(/&/g, "&");
 text = text.replace(/"/g, """);
 text = text.replace(/</g, "<");
 text = text.replace(/>/g, ">");
 text = text.replace(/'/g, "&#146;");
 return  text;
}

ok,我们构建测试字符串:

var t='<body><div id="{$userid}">{$username}</div><div id="userid">{$usersex}</div></body></html>';

然后我们分别执行1000次,看下测试结果:

nodejs: 19ms
c++: 9ms

是不是感觉结果正常,然后我们加大到10000次的匹配,看下测试结果:

nodejs: 50ms
c++: 62ms

是不是感觉很疑惑,为什么c++的速度要开始稍稍落后于node了,我个人感觉就3种可能: a、V8引擎太牛X了,性能直逼c++。 b、本人水平太差,写出了比js性能更差的c++代码。 c、因为频繁的v8接口调用和数据类型转换,造成的消耗已经超过了c++代码本身执行的时间。

接下来的测试,可能会让大家感到吃惊,我们把字符串的长度加长到100倍,也就是说上面的变量t被加长100次,然后执行100次匹配,我看下测试结果:

nodejs: 24ms
c++: 506ms

这让我直接怀疑我写的C++是不是真的很渣,由于字符串的长度变的很长,所以导致c++性能直线下降,而node却还是保持着一个很好的性能。

当然这其中的原因可能跟我在对v8::local<string>类型转换时发生的,也可能是c++的replace函数写的有问题,但是这也发出了一个信号,并不是说写了C++模块肯定是优于node本身的代码性能。

测试代码地址

(如果有C++大牛方便解答下我的问题,c++的replace函数在多次对大字符串替换的时候性能很差,有什么办法吗?特别是当我对字符串加长到原来1000倍以上时,C++代码可能溢出了,卡死在那里。)

replace函数的c++代码地址

我们再来看下hvalidator对比node的其他性能: 下面是利用hvalidator对比一些格式验证的测试,执行1000次:

nodejs: 73ms
c++: 36ms

这些验证都是很简单的一些验证,直接调用v8::value 类下面的一些 比如 IsArray这类的方法,然后返回bool值给node,在小并发情况下c++的验证速度要优于node,但是随着执行次数的增加比如我们增加到10W次,我们再来看结果:

nodejs: 2549ms
c++: 1918ms

他们之间的差距在逐渐缩小,这时候C++函数的调用成为了比计算更为消耗的地方。 测试代码地址

说了这么多就是想找一个合适的场景来为node写c++模块,究竟什么样的情况下我们需要c++模块,而不是为了写c++模块而写。目前我也没有答案,当我充满信心的去写C++模块后,hvalidator模块的性能测试结果让我充满失败感,希望有大牛能指点一二。


18 回复

Nodejs 用c++模块写node的疑惑

自从Node.js出现以来,一切都变得非常美好。借助Node.js的异步和事件驱动模型,可以轻松应对大量的I/O并发请求。尽管有些人批评JavaScript在CPU密集型任务上的性能不足,但通过编写C++模块,我们可以在Node.js中高效地处理这些任务。这种结合似乎为我们提供了一种几乎无所不能的解决方案,而且Node.js与C++的组合听起来非常强大。

然而,在我近一个月的探索过程中,我发现实际情况并不如预期的那样美好。以下是一些关于如何使用C++模块来增强Node.js性能的经验分享。

示例:使用C++模块进行字符串替换

首先,我们来看一个简单的C++模块示例,该模块用于执行字符串替换操作。假设我们有一个需要频繁进行XSS攻击防护的场景,可以使用C++模块来加速这一过程。

安装C++模块

你可以使用npm install hvalidator命令来安装hvalidator模块。

使用C++模块

var hv = require('hvalidator');
hv.toXss(str); // Remove common XSS attack

JavaScript版本的字符串替换

function toXss(text) {
    text = text.replace(/&/g, "&amp;");
    text = text.replace(/"/g, "&quot;");
    text = text.replace(/</g, "&lt;");
    text = text.replace(/>/g, "&gt;");
    text = text.replace(/'/g, "&#39;");
    return text;
}

测试字符串

var t = '<body><div id="{$userid}">{$username}</div><div id="userid">{$usersex}</div></body></html>';

性能测试

我们分别执行1000次和10000次的替换操作,并比较Node.js和C++模块的性能:

nodejs: 19ms
c++: 9ms
nodejs: 50ms
c++: 62ms

可以看到,在较小的数据量下,C++模块确实表现得更快。但是,当数据量增大时,C++模块的表现反而不如Node.js:

nodejs: 24ms
c++: 506ms

分析与结论

从测试结果可以看出,C++模块在处理大规模数据时,其性能并不一定优于Node.js。这可能是因为:

  1. V8引擎的强大性能:V8引擎在处理某些操作时,性能甚至可以媲美C++。
  2. C++代码的优化不足:可能我的C++代码并没有很好地优化,导致性能不佳。
  3. 频繁的接口调用和数据类型转换:频繁地在V8和C++之间进行数据交换会带来额外的开销。

适用场景

总的来说,C++模块更适合处理那些CPU密集型的任务,例如复杂的数学运算或图像处理等。对于简单的字符串操作或小规模数据处理,Node.js本身可能就已经足够高效。

希望上述经验分享对你有所帮助。如果你有更多的疑问或者想了解更多细节,欢迎进一步讨论。


如果不是你c++写的太渣,估计就是调用、转换的损耗太大。

强势围观,坐等解答

哎,replace代码也是google比较了好久从网上拷来的,真不至于这样吧~不明白呀

坐等大牛解答~~,一般来说,够用的话,不建议用c++写扩展~~

哈哈,问题是不知道什么时候不够用啊~

“调用、转换的损耗太大”

估计是这个。 用C++模块简单查询一次MYSQL,耗时高达0.005-0.006秒。 比PHPmyadmin高出10倍

一个是调用模块,一个是使用内置模块,调用的底层消耗偏大

头2个测试中,匹配增加10倍,C++耗时增加7倍,V8只增加2.5倍,但是V8也是C++写的;更可见这个中间消耗是比较大的

需要用数百数千行代码解决的问题用C++模块比原生好

同意你的观点,感觉是调用的消耗要大于执行的消耗。复杂的数据计算,不用频繁的互相调用的情况估计C++性能会更好。

c++代码没什么问题,很标准的replace函数,函数调用应该也没那么大的内耗(毕竟进程起来之后模块是已经在内存里了,每次调用也就是一个寻址),从你的数据来看nodeJS应该是对匹配结果做了缓存,因为你每次匹配的都是同一个字符串,如果有缓存的话你匹配多少次node消耗都是很低的,否则1000次改成10000次消耗绝对不会是2.5倍的时间成本,而且缓存是V8很惯用的方法,chrome浏览器的DOM结构就缓存很凶,内存基本抱着不释放。 你可以试下对样本做下处理,比如塞几个随机数进去看看结果还是不是这样

嗯,你说的太对了,果然是缓存问题,我在新的测试用例这样写了:

nodejs代码:

nv('sfsd@fsfas'+i+'.com').isEmail()
nv('http://bbs.csdn.net/topics/270080323'+i+'/').isUrl()

c++代码:

hv.isEmail('sfsd@fsfas'+j+'.com')
hv.isUrl('http://bbs.csdn.net/topics/270080323'+j+'/')

如果不加循环数i,则执行1W次,测试结果:

nodejs: 386ms
c++: 200ms

同样1W次,加上循环数i和j,测试结果:

nodejs: 920ms
c++: 276ms

可见v8的缓存太给力了!这样正则匹配还是用C++要快不少啊

很好的文章,受教了

或者再加上自己去写KMP之类的呢?

赞。 缓存就是屌。

网上很多框架的数据库查询的测试都是测试缓存,只有第一次去取数据,后面的都是一样的,所以测试用例里面每次都应该生成随机id去查询

学习了。我一开始还以为楼主写的c++太烂,原来是缓存的问题

象connect解析http头的部分可以写成c++

在Node.js中使用C++模块来编写性能敏感的部分确实可以带来性能上的提升,但实际效果取决于具体的应用场景。你提到的情况表明,对于某些特定的操作,JavaScript的性能甚至可以与C++相媲美,尤其是在处理大规模数据时。

示例:使用C++模块进行字符串替换

假设我们要实现一个高效的字符串替换功能。下面是一个简单的C++模块示例:

addon.cc

#include <node.h>
#include <v8.h>

using namespace v8;

void ReplaceString(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  String::Utf8Value input(isolate, args[0]);
  String::Utf8Value search(isolate, args[1]);
  String::Utf8Value replacement(isolate, args[2]);

  std::string output;
  size_t start_pos = 0;
  while(start_pos != std::string::npos) {
    size_t pos = (*input).find(*search, start_pos);
    if(pos == std::string::npos) {
      output += (*input).substr(start_pos);
      break;
    } else {
      output += (*input).substr(start_pos, pos - start_pos);
      output += *replacement;
      start_pos = pos + search->length();
    }
  }

  args.GetReturnValue().Set(String::NewFromUtf8(isolate, output.c_str()).ToChecked());
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "replaceString", ReplaceString);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

index.js

const addon = require('./build/Release/addon');

const str = '<body><div id="{$userid}">{$username}</div><div id="userid">{$usersex}</div></body></html>';
const search = '{$userid}';
const replacement = 'user123';

const startTime = Date.now();
for (let i = 0; i < 10000; i++) {
  addon.replaceString(str, search, replacement);
}
console.log(`Time taken for 10000 replacements: ${Date.now() - startTime} ms`);

解释

  • C++模块addon.cc 中实现了 ReplaceString 函数,该函数接受三个参数:原始字符串、要搜索的子串以及替换后的字符串。它通过遍历并替换所有匹配项来实现。
  • Node.js调用index.js 中调用 C++ 模块的 replaceString 函数,并测量执行10000次替换所需的时间。

注意点

  1. 频繁的V8接口调用:频繁地从JavaScript到C++之间切换会带来额外开销,特别是在循环中频繁调用C++函数时。
  2. 字符串处理:处理大规模字符串时,确保字符串处理逻辑是高效的,避免不必要的内存分配和复制。
  3. 模块优化:根据具体的使用场景优化C++模块,例如批量处理数据或减少接口调用频率。

总的来说,使用C++模块来编写Node.js插件确实可以在某些情况下提供性能优势,但需要仔细考虑应用场景和优化策略。

回到顶部