为什么我写的 C++ Addons 善后这么卡?
发布于 10 年前 作者 XadillaX 4538 次浏览 最后一次编辑是 8 年前 来自 问答

https://github.com/XadillaX/thmclrx/tree/fb1fc3e69c8fa7dac7037ba86e923ce0c8da9ab1/src

这个就是我写的 Addon,以及在

https://github.com/XadillaX/thmclrx/blob/fb1fc3e69c8fa7dac7037ba86e923ce0c8da9ab1/test/test.js

是测试代码。

我指的慢不是执行的时候慢,而是“善后”,我也不知道怎么解释——

总之效果就是 console 已经有东西打印出来了,但是要过好一段时间之后才能开始下一步操作。

比如把

for(var i = 0; i < 35500; i++) {
    rgb.push({
        r   : Number.random(0, 255),
        g   : Number.random(0, 255),
        b   : Number.random(0, 255)
    });
}

里面的 35500 改成一个很小的数字,那么处理完 console 出来之后秒退,但是如果改成更大的话,console 之后要等非常就之后程序才结束。

照理说都已经 console 出来了,说明由 C++ 处理的那段程序段已经结束了,但是为什么还是要等那么久呢?

在线等!急!

17 回复

因为gc时间到了,js主线程必须卡住 :D

@wenbob 确定是这个原因吗 0. 0 所以说在 C++ 里面的闭包中 scope 里面产生的东西都要被回收,要回收的东西太多了然后卡住了吗?

@wenbob 通常 GC 的话应该只是回收 Handle<...> 或者 Local<...> 之类的东西吧?我的代码里面没有什么比较大的东西是这两种类型的,基本上是由我自己去处理生命周期的,为什么会卡呢?

你改用Buffer传参数,全部测完再释放,就知道区别了

实际上node.js程序只要遇到大数组、大JSON的计算,都会很卡,没有例外的。计算方面的事情,我就不建议把数据保存在js这一侧。如果全部是在native代码里存储和计算,情况会好得多。需要数据的时候,再包装成js对象返给js线程就行了,一次不要返太多数据,免得序列化的时候又卡住了

@wenbob 问题是我用纯 js 来写没这么卡啊。

我计算的时候是传进去逐像素信息,计算出来的是主题色信息。

var rgb = [];
for(var i = 0; i < 35500; i++) {
    rgb.push({
        r   : Number.random(0, 255),
        g   : Number.random(0, 255),
        b   : Number.random(0, 255)
    });
}
var result = thmclrx.cpp.mindifferGet(rgb);
console.log(result);
result = thmclrx.cpp.mindifferGet(rgb);

上面的测试代码,照理说 rgb 在第一次计算完之后不会被 GC 吧?但是上面的测试代码还是会再第一次处理完之后 console.log 出来之后卡很久才开始第二次计算。

@xadillax 在js与native交互的时候, native 那一侧也产生了大量的gc,这就是问题了。

@wenbob Buffer 不会有这个问题吗?

传rgb数组到native的时候,会再复制一次这个超大的数组,你说怎么不会有gc

我认为Buffer不会,如果这个也要复制内存块的话,那就太对不起这个名字了。没测过,我之前遇到这类问题后,就果断把数据结构全部在Addon里实现了,js这边并不拥有任何大数组什么的

@wenbob 现在的问题是这样的——我能通过 node.js 很方便地获取各种不同类型图片的像素信息,而且有些图片是在线获取的。总不能说连着 HTTP 请求也交给 C++ 吧,如果是这样我直接用 C++ 写不用 node 了。

另一个处理方式是使用stream之类的机制来做大量数据的传递。无论如何,不要把一个很大的对象传给 C++ addon,大对象必然会卡住js线程。

@wenbob 好吧,我再想想别的办法看。

感觉还是不对啊,我做了个实验:

C++ 文件

#include <v8.h>
#include <node.h>
using namespace v8;

Handle<Value> Test1(const Arguments& args)
{
    HandleScope scope;

    Local<Value> arg = args[0];
    Local<Array> arr = Local<Array>::Cast(arg);

    return scope.Close(arr);
}

void Init(Handle<Object> exports)
{
    exports->Set(String::NewSymbol("test1"),
            FunctionTemplate::New(Test1)->GetFunction());
}

NODE_MODULE(test, Init);

Node 文件

var tester = require("./build/Release/test.node");
var object1 = [];
var object2;

for(var i = 0; i < 1000000; i++) {
    object1.push({
        r: i,
        g: i,
        b: i
    });
}

console.log(tester.test1(object1)[0]);
console.log(tester.test1(object1)[0]);

这里的两次调用 tester.test1 几乎无间隔,而且跑完马上就退出了,没有很明显的所谓 GC 的感觉。

不知何解?

@wenbob 我做了个实验在下面一楼,好像不是这里的问题啊?

好吧问题我自己找到了。

C++ 层面上写内存池为了贪省力直接用了 stl。用什么不好,而且我还用了 list,然后就卡住了。

现在还是贪省力,不过改用了 queue 然后就解决了。

回到顶部