Why V8 in Node.JS is faster than in my native C++ addon?
Asked Answered
U

1

29

Why Google's V8 JavaScript engine in my C++ addon works significantly slower than in Node.JS?

I've tried to write some stupidly simple code for generating prime numbers in the JavaScript and ran it in V8 via my C++ addon and directly in Node.JS.

I was quite shocked, because both should be using the same JavaScript engine and both have executed the same code (time in milliseconds, less is better):

V8 in Node.JS:  495517
V8 in Node.JS C++ Addon:  623598

Here is the source of JavaScript module and source of C++ addon that runs same JavaScript code (and I think problem not in the interop, because measuring of time works directly in JS):

index.js:

var jsInNodeJsPrimeGeneratorBenchmark = require("./javascript.js");
var jsInNativePrimeGeneratorBenchmark = require("./native");

console.log("V8 in Node.JS: ", jsInNodeJsPrimeGeneratorBenchmark.primeGeneratorBenchmark());
console.log("V8 in Node.JS C++ Addon: ", jsInNativePrimeGeneratorBenchmark.primeGeneratorBenchmark());

javascript.js:

function primeGeneratorBenchmark() {
    var result, primeNumberCounter, i, j, isPrime, start, end;

    i = 3;
    primeNumberCounter = 1;

    start = Date.now();

    while (primeNumberCounter < 100000) {
        isPrime = true;
        for (j = 2; j < i; j++) {
            if (i % j === 0) {
                isPrime = false;
                break;
            }
        }

        if (isPrime) {
            result = i;
            primeNumberCounter++;
        }

        i++;
    }

    end = Date.now();

    return end - start;
}

exports.primeGeneratorBenchmark = primeGeneratorBenchmark;

native.cpp:

#include <node.h>

v8::Handle<v8::Value> primeGeneratorBenchmark(const v8::Arguments &arguments);
void registerModule(v8::Handle<v8::Object> target);

v8::Handle<v8::Value> primeGeneratorBenchmark(const v8::Arguments &arguments) {
    v8::HandleScope handleScope;

    v8::Local<v8::Context> context = arguments.Holder()->CreationContext();

    v8::Context::Scope scope(context);

    const char *sourceStringC =
        "var result, primeNumberCounter, i, j, isPrime, start, end, time;\n"
        "i = 3;\n"
        "primeNumberCounter = 1;\n"
        "start = Date.now();\n"
        "while (primeNumberCounter < 100000) {\n"
        "    isPrime = true;\n"
        "    for (j = 2; j < i; j++) {\n"
        "        if (i % j === 0) {\n"
        "            isPrime = false;\n"
        "            break;\n"
        "        }\n"
        "    }\n"
        "    if (isPrime) {\n"
        "        result = i;\n"
        "        primeNumberCounter++;\n"
        "    }\n"
        "    i++;\n"
        "}\n"
        "end = Date.now();\n"
        "time = end - start;\n";

    v8::Local<v8::String> sourceStringV8 = v8::String::New(sourceStringC);

    v8::Local<v8::Script> script = v8::Script::Compile(sourceStringV8);
    script->Run();

    v8::Local<v8::Value> timeResult = v8::Context::GetCurrent()->Global()->Get(v8::String::New("time"));

    return handleScope.Close(timeResult);
}

void registerModule(v8::Handle<v8::Object> target) {
    target->Set(v8::String::NewSymbol("primeGeneratorBenchmark"), v8::FunctionTemplate::New(primeGeneratorBenchmark)->GetFunction());
}

NODE_MODULE(native, registerModule);
Unclad answered 13/3, 2013 at 18:3 Comment(2)
Just an interesting thought: what are the results if you implement the same algorithm in C++ instead of asking V8 to parse and execute it in JS?Callipash
This looks like it's just using the JS compiler still without executing anything natively, right?Rahman
A
48

In the C++ version all variables declared in the script source (result, primeNumberCounter, i, j, isPrime, start, end, time) are global because script's top level scope is global scope.

For optimizing compiler it is easy to allocate local variables into machine registers (or spill slots on stack) and keep track of their type. Working with global variables on the other hand requires constant memory accesses and type checks because V8 does not (currently) perform a register promotion optimization.

If you wrap the source into an immediately called function difference should go away.

Albertoalberts answered 13/3, 2013 at 22:51 Comment(1)
Thank you, Vyacheslav, you've saved my time! I wrapped everything in anonymous function that immediately calls itself and C++ addon runs even faster sometimes. I just left variable time at global scope, to get my results without rewriting my C++ code.Unclad

© 2022 - 2024 — McMap. All rights reserved.