Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.JS performance vs native C++ addon when populating an Int32Array

I've been experimenting with Node.JS and C++ addons and found that populating an Int32Array is considerably slower when using the C++ addon rather than directly doing so in Node.JS / JavaScript.

Node.JS: 133 ~ ms
C++: 1103 ~ ms

Does anyone know why this is? My test code consists of a fairly large array and for loops containing if statements.

I suspect I'm populating the array incorrectly in my C++ addon. (?)

JavaScript:

var testArray = new Int32Array(36594368);

var i = 0;
for (var xi = 0; xi < 332; xi++) {
    for (var yi = 0; yi < 332; yi++) {
        for (var zi = 0; zi < 332; zi++) {
            if ((xi + yi + zi) % 62 == 0) testArray[i] = 2;
            else if (yi < 16) testArray[i] = 2;
            else if (yi == 16) testArray[i] = 1;
            else testArray[i] = 0;

            i++;
        }
    }
}

C++ Addon:

Local<Int32Array> testArray = Int32Array::New(ArrayBuffer::New(isolate, 4 * 36594368), 0, 36594368);

int i = 0;
for (int xi = 0; xi < 332; xi++) {
    for (int yi = 0; yi < 332; yi++) {
        for (int zi = 0; zi < 332; zi++) {
            if ((xi + yi + zi) % 62 == 0) testArray->Set(i, Integer::New(isolate, 2));
            else if (yi < 16) testArray->Set(i, Integer::New(isolate, 2));
            else if (yi == 16) testArray->Set(i, Integer::New(isolate, 1));
            else testArray->Set(i, Integer::New(isolate, 0));

            i++;
        }
    }
}

EDIT: Just to add, the functions I'm using in my C++ code are V8 functions and weren't defined by myself. Is there another way to set values in an Int32Array without using these?

like image 499
Joey Morani Avatar asked Feb 13 '15 20:02

Joey Morani


1 Answers

Maxing out Typed Array Performance using C++

I'm not surprised this is slow as written, but there is a lot you can do to speed it up. The critical insight is that when dealing with JavaScript typed arrays in node, you can gain access to the memory buffer and operate on that directly.

The main source of slowness

Though when dealing with normal JavaScript arrays/objects, the following are necessary

Integer::New(isolate,value)

and

testArray->Set(value)

So for example the following line

testArray->Set(i, Integer::New(isolate, 0));

creates a new Number object, converts the integer 0 to a double since all JavaScript numbers are double, calls Set with the Number object, then converts the double back to an integer because it's storing the value in a Int32 typed array, and then destructs the Number object. This happens 3 million times.

An improvement

But typed arrays are different, and the call GetIndexedPropertiesExternalArrayData gives one access to the underlying buffer, which for a Int32Array is a buffer of int. This allows the C++ function to be re-written to avoid all of those allocations and casts:

void doMkArray(const FunctionCallbackInfo<Value> &args)
{
   v8::Isolate *I = v8::Isolate::GetCurrent();
   Local<Int32Array> testArray = Int32Array::New(ArrayBuffer::New(I, 4 * 36594368),0,36594368);
   int *dptr = (int*)testArray->GetIndexedPropertiesExternalArrayData();

   int i = 0;
   for (int xi = 0; xi < 332; xi++)
   {
      for (int yi = 0; yi < 332; yi++)
      {
         for (int zi = 0; zi < 332; zi++)
         {
            if ((xi + yi + zi) % 62 == 0) dptr[i] = 2;
            else if (yi < 16) dptr[i] = 2;
            else if (yi == 16) dptr[i] = 1;
            else dptr[i] = 0;

            i++;
         }
      }
   }

   args.GetReturnValue().Set(testArray);
}

Some measurements

Replacing with the above makes things faster, but how much faster needs a test. The following package can be cloned and when run (using node 0.12.5) results in the following

  Performance Tests
    ✓ via javascript (169ms)
    ✓ via c++ (141ms)

So that alone makes using C++ faster, but maybe not all that amazing, but if both the Javascript and the C++ loops (see the src) are commented out, and when only the array allocation is included:

    void doMkArray(const FunctionCallbackInfo<Value> &args)
    {
       v8::Isolate *I = v8::Isolate::GetCurrent();
       Local<Int32Array> testArray = Int32Array::New(ArrayBuffer::New(I, 4 
/*
...

Then the time changes to

  Performance Tests
    ✓ via javascript (62ms)
    ✓ via c++ (80ms)

In other words, simply allocating the array takes approximately 60ms in JavaScript, and 80ms in the C++ module. But that means that the rest of the time is the time spent in the loop, which is approximately 60ms in C++ and 110ms in Javascript. And so for actions that are predominately loops and calculations using direct buffer access, C++ is preferred.

like image 187
waTeim Avatar answered Oct 03 '22 19:10

waTeim