Mariya Davydova
Mariya Davydova

Reputation: 1393

NodeJS: Why nodejs c++ addon is so slow?

I'm trying to write a very simple nodejs c++ addon and compare its usage with usual nodejs module usage (written on CoffeeScript). I wrote a test that creates a million object instances of AddonClass from addon and CoffeeClass from module. The source code is:

addon.cpp

#define BUILDING_NODE_EXTENSION
#include <node.h>
#include "addon_class.hpp"
using namespace v8;
static void InitAll(Handle<Object> target) {
  Addon::Init(target);
}
NODE_MODULE(addon, InitAll)

addon_class.hpp

#ifndef _ADDON_CLASS_HPP_
#define _ADDON_CLASS_HPP_

#include <node.h>

class Addon : public node::ObjectWrap {
 public:
  static void Init(v8::Handle<v8::Object> target);
 private:
  Addon(int value) : value_(value) {};
  ~Addon() {};
  static v8::Handle<v8::Value> New(const v8::Arguments& args);
  int value_;
};

#endif

addon_class.cpp

#define BUILDING_NODE_EXTENSION
#include <cstdio>
#include <node.h>
#include "addon_class.hpp"
using namespace v8;

Persistent<FunctionTemplate> tpl;

void Addon::Init(Handle<Object> target) {
  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  tpl = Persistent<FunctionTemplate>::New(t);
  tpl->SetClassName(String::NewSymbol("AddonClass"));
  tpl->InstanceTemplate()->SetInternalFieldCount(1);
  target->Set(String::NewSymbol("AddonClass"), tpl->GetFunction());
}

Handle<Value> Addon::New(const Arguments& args) {
  HandleScope scope;
  Addon* obj = new Addon(0);
  if (args[0]->IsNumber()) {
    obj->value_ = args[0]->NumberValue();
  }
  obj->Wrap(args.This());
  return args.This();
}

CoffeeClass.coffee

class CoffeeClass
  value: undefined

  constructor: (@value) ->
    if !@value? then @value = 0

exports.CoffeeClass = CoffeeClass 

benchmark.coffee

CoffeeClass = (require './CoffeeClass.coffee').CoffeeClass
AddonClass = (require './build/Release/addon.node').AddonClass

N = 1e6

calculateDiff = (d1, d2) ->
  h = parseInt d2.getHours()
  h -= parseInt d1.getHours()
  m = parseInt d2.getMinutes() + h * 60
  m -= parseInt d1.getMinutes()
  s = parseInt d2.getSeconds() + m * 60
  s -= parseInt d1.getSeconds()
  ms = parseInt d2.getMilliseconds() + s * 1000
  ms -= parseInt d1.getMilliseconds()
  return ms

testCreate = (LC) ->
  d1 = new Date()
  for i in [1..N]
    l = new LC
  d2 = new Date()
  console.log LC.name, calculateDiff(d1, d2), "ms"

testCreate(CoffeeClass)
testCreate(AddonClass)

The result of this benchmark is very, very strange for me:

CoffeeClass 34 ms
AddonClass 487 ms

So, the question is: why is this simple addon so slow? And, more importantly, is it possible to do something with it or not?

Upvotes: 3

Views: 2240

Answers (2)

Jared G
Jared G

Reputation: 1638

Your test case here is so small that the overhead of interfacing between V8 and your addon is greater than any performance savings of the compiled c++.

A good use for an addon is something that is not chatty, for example performing a complex and optimized algorithm on the input parameter or interacting with a complex c++ data structure. You want to minimize the frequency of calls to the addon and maximize the work that it performs with each call. You may even consider batching what would be many separate calls together into an array.

Upvotes: 3

marchaos
marchaos

Reputation: 3444

Going between V8 C++ and JavaScript is expensive. I would imagine that if you moved the for loop into the C++ class and the CoffeeScript class, you'd probably see that C++ outperforms the CoffeeScript.

Upvotes: 2

Related Questions