diff --git a/binding.gyp b/binding.gyp index 6288c43..5fa5930 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,4 +1,5 @@ { +"variables": { "library_files": ["lib/cgeohash_obj.js", "lib/cgeohash_fn.js", "lib/cgeohash_obj_speed_tests.js", "lib/cgeohash_fn_speed_tests.js"] }, "conditions": [ [ "OS=='win'", @@ -35,12 +36,15 @@ } ] ], + "include_dirs": ["src"], "sources": [ - "bindings.cpp", - "geohash.cpp", - "geohash_node_binding.cpp", - "geohash_node_binding_speed.cpp", - "geohash_obj.cpp" + "src/cgeohash_bindings.cpp", + "src/cgeohash.cpp", + "src/cgeohash_fn.cpp", + "src/cgeohash_fn_repeaters.cpp", + "src/cgeohash_obj.cpp", + "src/cgeohash_obj_repeaters.cpp", + "src/cgeohash_nanoseconds.cpp" ], "target_name": "cgeohash" } diff --git a/bindings.cpp b/bindings.cpp deleted file mode 100644 index ebaedc6..0000000 --- a/bindings.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -#include "geohash.hpp" -#include "geohash_node_binding.hpp" -#include "geohash_node_binding_speed.hpp" -#include "geohash_obj.hpp" - -void RegisterModule(v8::Handle target) { - GeoHashObject::Init(target); - geohash_Init(target); - geohash::test1m_Init(target); -} - -NODE_MODULE(cgeohash, RegisterModule); diff --git a/geohash.hpp b/geohash.hpp deleted file mode 100644 index 86ac9a3..0000000 --- a/geohash.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef _NODE_GEOHASH_HPP -#define _NODE_GEOHASH_HPP - -#include -#include - -namespace geohash { -struct DecodedBBox { - double minlat, minlon, maxlat, maxlon; -}; - -struct DecodedHash { - double latitude; - double longitude; - - double latitude_err; - double longitude_err; -}; - -uint64_t nanoseconds(); - -std::string encode(const double latitude, const double longitude, const unsigned long numberOfChars); - -DecodedHash decode(const std::string & hash_string); - -DecodedBBox decode_bbox(const std::string & hash_string); - - -/** - * direction [lat, lon], i.e. - * [1,0] - north - * [1,1] - northeast - * ... - */ -std::string neighbor(const std::string & hash_string, const int direction []); - -// Node.JS Hooks to GeoHash encoding -v8::Handle encode_js(const v8::Arguments& args); -v8::Handle decode_js(const v8::Arguments& args); -v8::Handle decode_bbox_js(const v8::Arguments& args); -v8::Handle neighbor_js(const v8::Arguments& args); -} - -#endif /* geohash.hpp */ diff --git a/geohash.js b/geohash.js deleted file mode 100644 index 083ada0..0000000 --- a/geohash.js +++ /dev/null @@ -1,18 +0,0 @@ -var cgeohash = require('./build/Release/cgeohash'); - -var GeoHashObject = new cgeohash.GeoHashObject(); - -module.exports = { - encode: cgeohash.encode_js, - decode: cgeohash.decode_js, - decode_bbox: cgeohash.decode_bbox_js, - neighbor: cgeohash.neighbor_js, - - test1m_encode: cgeohash.test1m_encode_js, - test1m_decode: cgeohash.test1m_decode_js, - test1m_decode_bbox: cgeohash.test1m_decode_bbox_js, - test1m_neighbor: cgeohash.test1m_neighbor_js, - - geohash_object: GeoHashObject, -} - diff --git a/geohash_node_binding.hpp b/geohash_node_binding.hpp deleted file mode 100644 index ff3766f..0000000 --- a/geohash_node_binding.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _NODE_GEOHASH_NODE_BINDINGS_HPP -#define _NODE_GEOHASH_NODE_BINDINGS_HPP - -#include -#include - -// Node.JS Hooks to GeoHash encoding -namespace geohash { -v8::Handle encode_js(const v8::Arguments& args); -v8::Handle decode_js(const v8::Arguments& args); -v8::Handle decode_bbox_js(const v8::Arguments& args); -v8::Handle neighbor_js(const v8::Arguments& args); - -} - -void geohash_Init(v8::Handle target); - -#endif /* geohash.hpp */ diff --git a/geohash_node_binding_speed.cpp b/geohash_node_binding_speed.cpp deleted file mode 100644 index 759fe94..0000000 --- a/geohash_node_binding_speed.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include "cvv8/detail/convert_core.hpp" - -#include "geohash.hpp" -#include "geohash_node_binding.hpp" - -#define _THROW_NODE_ERROR(MSG) ThrowException(v8::Exception::Error(v8::String::New(MSG))); - - -namespace geohash { - - // Node.JS Hooks to GeoHash encoding - v8::Handle test1m_encode_js(const v8::Arguments& args) { - v8::HandleScope scope; - - if (args.Length() < 2) { - return _THROW_NODE_ERROR("Takes 3 parameters: latitude, longitude, and numberOfChars"); - } - - const double latitude = cvv8::CastFromJS< double >(args[0]); - const double longitude = cvv8::CastFromJS< double >(args[1]); - const uint32_t numberOfChars = args.Length() == 3 - ? cvv8::CastFromJS< uint32_t >(args[2]) - : 9; // Default input - - const uint64_t _nanoseconds = nanoseconds(); - for(int i = 0, max = 1000*1000; i < max; i++) { - encode(latitude, longitude, numberOfChars); - } - const uint64_t _diff = nanoseconds() - _nanoseconds; - const double _seconds = ((double) _diff) / ((uint64_t) 1e9); - return scope.Close(cvv8::CastToJS(_seconds)); - } - - v8::Handle test1m_decode_js(const v8::Arguments& args) { - v8::HandleScope scope; - - if (args.Length() < 1) { - return _THROW_NODE_ERROR("Takes 1 parameters: hash_string"); - } - - const std::string hash_string = cvv8::CastFromJS(args[0]); - if (hash_string.empty()) { - return _THROW_NODE_ERROR("Parameter 0 must be a valid string"); - } - - const uint64_t _nanoseconds = nanoseconds(); - for(int i = 0, max = 1000*1000; i < max; i++) { - decode(hash_string); - } - const uint64_t _diff = nanoseconds() - _nanoseconds; - const double _seconds = ((double) _diff) / ((uint64_t) 1e9); - return scope.Close(cvv8::CastToJS(_seconds)); - } - - v8::Handle test1m_decode_bbox_js(const v8::Arguments& args) { - v8::HandleScope scope; - - if (args.Length() < 1) { - return _THROW_NODE_ERROR("Takes 1 parameters: hash_string"); - } - - const std::string hash_string = cvv8::CastFromJS(args[0]); - if (hash_string.empty()) { - return _THROW_NODE_ERROR("Parameter 0 must be a valid string"); - } - - const uint64_t _nanoseconds = nanoseconds(); - for(int i = 0, max = 1000*1000; i < max; i++) { - decode_bbox(hash_string); - } - const uint64_t _diff = nanoseconds() - _nanoseconds; - const double _seconds = ((double) _diff) / ((uint64_t) 1e9); - return scope.Close(cvv8::CastToJS(_seconds)); - } - - v8::Handle test1m_neighbor_js(const v8::Arguments& args) { - v8::HandleScope scope; - - if (args.Length() < 2) { - return _THROW_NODE_ERROR("Takes 2 parameters: hash_string, and direction []"); - } - - const std::string hash_string = cvv8::CastFromJS(args[0]); - if (hash_string.empty()) { - return _THROW_NODE_ERROR("Parameter 0 must be a valid string"); - } - - const std::list directions = cvv8::CastFromJS< std::list >(args[1]); - if (directions.size() != 2) { - return _THROW_NODE_ERROR("Parameter 1 must be an array with 2 numbers"); - } - - const int directions_array [] = { - directions.front(), // Only 2 elements - directions.back() // Only 2 elements - }; - - const uint64_t _nanoseconds = nanoseconds(); - for(int i = 0, max = 1000*1000; i < max; i++) { - neighbor(hash_string, directions_array); - } - const uint64_t _diff = nanoseconds() - _nanoseconds; - const double _seconds = ((double) _diff) / ((uint64_t) 1e9); - return scope.Close(cvv8::CastToJS(_seconds)); - } - - void test1m_Init(v8::Handle target) { - node::SetMethod(target, "test1m_encode_js", geohash::test1m_encode_js); - node::SetMethod(target, "test1m_decode_js", geohash::test1m_decode_js); - node::SetMethod(target, "test1m_decode_bbox_js", geohash::test1m_decode_bbox_js); - node::SetMethod(target, "test1m_neighbor_js", geohash::test1m_neighbor_js); - } - -} diff --git a/geohash_node_binding_speed.hpp b/geohash_node_binding_speed.hpp deleted file mode 100644 index c452b78..0000000 --- a/geohash_node_binding_speed.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _NODE_GEOHASH_BINDING_SPEED_HPP -#define _NODE_GEOHASH_BINDING_SPEED_HPP - -#include -#include - -// Node.JS Hooks to GeoHash encoding -namespace geohash { -// Used for performance testing!! -v8::Handle test1m_encode_js(const v8::Arguments& args); -v8::Handle test1m_decode_js(const v8::Arguments& args); -v8::Handle test1m_decode_bbox_js(const v8::Arguments& args); -v8::Handle test1m_neighbor_js(const v8::Arguments& args); - -void test1m_Init(v8::Handle target); - -} - -#endif /* geohash.hpp */ diff --git a/geohash_obj.cpp b/geohash_obj.cpp deleted file mode 100644 index 63cd6a4..0000000 --- a/geohash_obj.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include -#include "geohash_obj.hpp" - -#include "geohash.hpp" -#include "geohash_node_binding.hpp" -#include "geohash_node_binding_speed.hpp" - - -v8::Persistent GeoHashObject::constructor; - - -void GeoHashObject::Init(v8::Handle target) { - v8::HandleScope scope; - - v8::Local tpl = v8::FunctionTemplate::New(New); - v8::Local name = v8::String::NewSymbol("GeoHashObject"); - - constructor = v8::Persistent::New(tpl); - - // ObjectWrap uses the first internal field to store the wrapped pointer. - constructor->InstanceTemplate()->SetInternalFieldCount(1); - constructor->SetClassName(name); - - // Add all prototype methods, getters and setters here. - NODE_SET_PROTOTYPE_METHOD(constructor, "encode", Encode); - NODE_SET_PROTOTYPE_METHOD(constructor, "decode", Decode); - NODE_SET_PROTOTYPE_METHOD(constructor, "decode_bbox", DecodeBBox); - NODE_SET_PROTOTYPE_METHOD(constructor, "neighbor", Neighbor); - - // Used for performance testing!! - NODE_SET_PROTOTYPE_METHOD(constructor, "test1m_encode", Test1M_Encode); - NODE_SET_PROTOTYPE_METHOD(constructor, "test1m_decode", Test1M_Decode); - NODE_SET_PROTOTYPE_METHOD(constructor, "test1m_decode_bbox", Test1M_DecodeBBox); - NODE_SET_PROTOTYPE_METHOD(constructor, "test1m_neighbor", Test1M_Neighbor); - - - // This has to be last, otherwise the properties won't show up on the - // object in JavaScript. - target->Set(name, constructor->GetFunction()); -} - - -GeoHashObject::GeoHashObject() : node::ObjectWrap() { -} - - -v8::Handle GeoHashObject::New(const v8::Arguments& args) { - v8::HandleScope scope; - - if (!args.IsConstructCall()) { - return ThrowException( - v8::Exception::TypeError( - v8::String::New("Use the new operator to create instances of this object.")) - ); - } - - // Creates a new instance object of this type and wraps it. - GeoHashObject* obj = new GeoHashObject(); - obj->Wrap(args.This()); - - return args.This(); -} - - -v8::Handle GeoHashObject::Encode(const v8::Arguments& args) { - return geohash::encode_js(args); -} - - -v8::Handle GeoHashObject::Decode(const v8::Arguments& args) { - return geohash::decode_js(args); -} - - -v8::Handle GeoHashObject::DecodeBBox(const v8::Arguments& args) { - return geohash::decode_bbox_js(args); -} - - -v8::Handle GeoHashObject::Neighbor(const v8::Arguments& args) { - return geohash::neighbor_js(args); -} - - - -// Used for performance testing!! -v8::Handle GeoHashObject::Test1M_Encode(const v8::Arguments& args) { - return geohash::test1m_encode_js(args); -} - - -v8::Handle GeoHashObject::Test1M_Decode(const v8::Arguments& args) { - return geohash::test1m_decode_js(args); -} - - -v8::Handle GeoHashObject::Test1M_DecodeBBox(const v8::Arguments& args) { - return geohash::test1m_decode_bbox_js(args); -} - - -v8::Handle GeoHashObject::Test1M_Neighbor(const v8::Arguments& args) { - return geohash::test1m_neighbor_js(args); -} diff --git a/cvv8/ClassCreator.hpp b/includes/cvv8/ClassCreator.hpp similarity index 100% rename from cvv8/ClassCreator.hpp rename to includes/cvv8/ClassCreator.hpp diff --git a/cvv8/Makefile b/includes/cvv8/Makefile similarity index 100% rename from cvv8/Makefile rename to includes/cvv8/Makefile diff --git a/cvv8/NativeToJSMap.hpp b/includes/cvv8/NativeToJSMap.hpp similarity index 100% rename from cvv8/NativeToJSMap.hpp rename to includes/cvv8/NativeToJSMap.hpp diff --git a/cvv8/V8Shell.hpp b/includes/cvv8/V8Shell.hpp similarity index 100% rename from cvv8/V8Shell.hpp rename to includes/cvv8/V8Shell.hpp diff --git a/cvv8/XTo.hpp b/includes/cvv8/XTo.hpp similarity index 100% rename from cvv8/XTo.hpp rename to includes/cvv8/XTo.hpp diff --git a/cvv8/arguments.hpp b/includes/cvv8/arguments.hpp similarity index 100% rename from cvv8/arguments.hpp rename to includes/cvv8/arguments.hpp diff --git a/cvv8/convert.hpp b/includes/cvv8/convert.hpp similarity index 100% rename from cvv8/convert.hpp rename to includes/cvv8/convert.hpp diff --git a/cvv8/detail/Makefile b/includes/cvv8/detail/Makefile similarity index 100% rename from cvv8/detail/Makefile rename to includes/cvv8/detail/Makefile diff --git a/cvv8/detail/convert_core.hpp b/includes/cvv8/detail/convert_core.hpp similarity index 100% rename from cvv8/detail/convert_core.hpp rename to includes/cvv8/detail/convert_core.hpp diff --git a/cvv8/detail/doxygen_hack.hpp b/includes/cvv8/detail/doxygen_hack.hpp similarity index 100% rename from cvv8/detail/doxygen_hack.hpp rename to includes/cvv8/detail/doxygen_hack.hpp diff --git a/cvv8/detail/invocable_core.hpp b/includes/cvv8/detail/invocable_core.hpp similarity index 100% rename from cvv8/detail/invocable_core.hpp rename to includes/cvv8/detail/invocable_core.hpp diff --git a/cvv8/detail/invocable_generated.hpp b/includes/cvv8/detail/invocable_generated.hpp similarity index 100% rename from cvv8/detail/invocable_generated.hpp rename to includes/cvv8/detail/invocable_generated.hpp diff --git a/cvv8/detail/signature_core.hpp b/includes/cvv8/detail/signature_core.hpp similarity index 100% rename from cvv8/detail/signature_core.hpp rename to includes/cvv8/detail/signature_core.hpp diff --git a/cvv8/detail/signature_generated.hpp b/includes/cvv8/detail/signature_generated.hpp similarity index 100% rename from cvv8/detail/signature_generated.hpp rename to includes/cvv8/detail/signature_generated.hpp diff --git a/cvv8/detail/tmp.hpp b/includes/cvv8/detail/tmp.hpp similarity index 100% rename from cvv8/detail/tmp.hpp rename to includes/cvv8/detail/tmp.hpp diff --git a/cvv8/generator/Signature.hpp b/includes/cvv8/generator/Signature.hpp similarity index 100% rename from cvv8/generator/Signature.hpp rename to includes/cvv8/generator/Signature.hpp diff --git a/cvv8/invocable.hpp b/includes/cvv8/invocable.hpp similarity index 100% rename from cvv8/invocable.hpp rename to includes/cvv8/invocable.hpp diff --git a/cvv8/properties.hpp b/includes/cvv8/properties.hpp similarity index 100% rename from cvv8/properties.hpp rename to includes/cvv8/properties.hpp diff --git a/cvv8/signature.hpp b/includes/cvv8/signature.hpp similarity index 100% rename from cvv8/signature.hpp rename to includes/cvv8/signature.hpp diff --git a/cvv8/v8-convert.hpp b/includes/cvv8/v8-convert.hpp similarity index 100% rename from cvv8/v8-convert.hpp rename to includes/cvv8/v8-convert.hpp diff --git a/lib/cgeohash_fn.js b/lib/cgeohash_fn.js new file mode 100644 index 0000000..2bc2e75 --- /dev/null +++ b/lib/cgeohash_fn.js @@ -0,0 +1,8 @@ +var cgeohash = require('../build/Release/cgeohash'); + +module.exports = { + encode: cgeohash.encode_fn, + decode: cgeohash.decode_fn, + decode_bbox: cgeohash.decode_bbox_fn, + neighbor: cgeohash.neighbor_fn, +}; diff --git a/lib/cgeohash_fn_repeaters.js b/lib/cgeohash_fn_repeaters.js new file mode 100644 index 0000000..749d49e --- /dev/null +++ b/lib/cgeohash_fn_repeaters.js @@ -0,0 +1,65 @@ +// +// Map the cgeohash C++ functions to callbacks to repeat X-Many times +// +// Ex: +// var repeat_in_js = require('cgeohash_fn_repeaters').repeat_in_js +// var repeat_in_cpp = require('cgeohash_fn_repeaters').repeat_in_cpp +// var avg_seconds_per_decode = { +// looping_in_js: repeat_in_js.decode( 1000*1000, 'abcd'), +// looping_in_cpp: repeat_in_cpp.decode(1000*1000, 'abcd'), +// } +// +// console.log('Seconds per call Node <-> C++ = ' + looping_in_js); +// console.log('Seconds per call C++ <-> C++ = ' + looping_in_cpp); +// + +var cgeohash = require('../build/Release/cgeohash'); +var cgeohash_fn = require('./cgeohash_fn'); +var repeats_callback_wrapper = require('v8-profiler-table').repeats_callback_wrapper; + +// Alias the <...>_obj methods to the expected <...> method names +// Loop in JS and record the seconds / call +var cgeohash_fn_repeat_js = { + encode: function(num_times, latitude, longitude, numberOfChars) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_fn.encode(latitude, longitude, numberOfChars); + }); + return loop(); + }, + + decode: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_fn.decode(hash_string); + }); + return loop(); + }, + + decode_bbox: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_fn.decode_bbox(hash_string); + }); + return loop(); + }, + + neighbor: function(num_times, hash_string, direction) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_fn.neighbor(hash_string, direction); + }); + return loop(); + }, +}; + +// Alias the <...>_obj methods to the expected <...> method names +// Loop in CPP and record the seconds / call +var cgeohash_fn_repeat_cpp = { + encode: cgeohash.encode_fn_repeater, + decode: cgeohash.decode_fn_repeater, + decode_bbox: cgeohash.decode_bbox_fn_repeater, + neighbor: cgeohash.neighbor_fn_repeater, +}; + + +module.exports = { + repeat_in_js: cgeohash_fn_repeat_js, + repeat_in_cpp: cgeohash_fn_repeat_cpp, +}; diff --git a/lib/cgeohash_obj.js b/lib/cgeohash_obj.js new file mode 100644 index 0000000..bea9379 --- /dev/null +++ b/lib/cgeohash_obj.js @@ -0,0 +1,10 @@ +var cgeohash = require('../build/Release/cgeohash'); +var cgeohash_obj = new cgeohash.GeoHashObject(); + +// Alias the <...>_obj methods to the expected <...> method names +module.exports = { + encode: cgeohash_obj.encode_obj, + decode: cgeohash_obj.decode_obj, + decode_bbox: cgeohash_obj.decode_bbox_obj, + neighbor: cgeohash_obj.neighbor_obj, +}; diff --git a/lib/cgeohash_obj_repeaters.js b/lib/cgeohash_obj_repeaters.js new file mode 100644 index 0000000..6ca889a --- /dev/null +++ b/lib/cgeohash_obj_repeaters.js @@ -0,0 +1,66 @@ +// +// Map the cgeohash C++ Object to callbacks to repeat X-Many times +// +// Ex: +// var repeat_in_js = require('cgeohash_obj_repeaters').repeat_in_js +// var repeat_in_cpp = require('cgeohash_obj_repeaters').repeat_in_cpp +// var avg_seconds_per_decode = { +// looping_in_js: repeat_in_js.decode( 1000*1000, 'abcd'), +// looping_in_cpp: repeat_in_cpp.decode(1000*1000, 'abcd'), +// } +// +// console.log('Seconds per call Node <-> C++ = ' + looping_in_js); +// console.log('Seconds per call C++ <-> C++ = ' + looping_in_cpp); +// + +var cgeohash = require('../build/Release/cgeohash'); +var cgeohash_obj = require('./cgeohash_obj'); +var cgeohash_obj_repeater = new cgeohash.GeoHashObjectRepeater(); +var repeats_callback_wrapper = require('v8-profiler-table').repeats_callback_wrapper; + +// Alias the <...>_obj methods to the expected <...> method names +// Loop in JS and record the seconds / call +var cgeohash_fn_repeat_js = { + encode: function(num_times, latitude, longitude, numberOfChars) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_obj.encode(latitude, longitude, numberOfChars); + }); + return loop(); + }, + + decode: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_obj.decode(hash_string); + }); + return loop(); + }, + + decode_bbox: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_obj.decode_bbox(hash_string); + }); + return loop(); + }, + + neighbor: function(num_times, hash_string, direction) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_obj.neighbor(hash_string, direction); + }); + return loop(); + }, +}; + +// Alias the <...>_obj methods to the expected <...> method names +// Loop in CPP and record the seconds / call +var cgeohash_fn_repeat_cpp = { + encode: cgeohash_obj_repeater.encode_obj_repeater, + decode: cgeohash_obj_repeater.decode_obj_repeater, + decode_bbox: cgeohash_obj_repeater.decode_bbox_obj_repeater, + neighbor: cgeohash_obj_repeater.neighbor_obj_repeater, +}; + + +module.exports = { + repeat_in_js: cgeohash_fn_repeat_js, + repeat_in_cpp: cgeohash_fn_repeat_cpp, +}; diff --git a/tests/geohash_original.js b/lib/cgeohash_original.js similarity index 100% rename from tests/geohash_original.js rename to lib/cgeohash_original.js diff --git a/lib/cgeohash_original_repeaters.js b/lib/cgeohash_original_repeaters.js new file mode 100644 index 0000000..37b97e2 --- /dev/null +++ b/lib/cgeohash_original_repeaters.js @@ -0,0 +1,51 @@ +// +// Map the originan node-geohash JavaScript functions to callbacks to repeat X-Many times +// +// Ex: +// var repeat_in_js = require('geohash_original_repeaters').repeat_in_js +// var avg_seconds_per_decode = { +// looping_in_js: repeat_in_js.decode( 1000*1000, 'abcd'), +// } +// +// console.log('Seconds per call Node <-> JS = ' + looping_in_js); +// + +var cgeohash_original = require('./cgeohash_original'); +var repeats_callback_wrapper = require('v8-profiler-table').repeats_callback_wrapper; + +// Alias the <...>_obj methods to the expected <...> method names +// Loop in JS and record the seconds / call +var cgeohash_original_repeat_js = { + encode: function(num_times, latitude, longitude, numberOfChars) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_original.encode(latitude, longitude, numberOfChars); + }); + return loop(); + }, + + decode: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_original.decode(hash_string); + }); + return loop(); + }, + + decode_bbox: function(num_times, hash_string) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_original.decode_bbox(hash_string); + }); + return loop(); + }, + + neighbor: function(num_times, hash_string, direction) { + var loop = repeats_callback_wrapper(num_times, function() { + return cgeohash_original.neighbor(hash_string, direction); + }); + return loop(); + }, +}; + +module.exports = { + repeat_in_js: cgeohash_original_repeat_js, + repeat_in_cpp: undefined, // Not available +}; diff --git a/package.json b/package.json index 34d79ad..5f0063a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "chai": "1.6.1", "mocha": "1.11.0", "ngeohash": "0.2.0", - "sprintf": "*" + "sprintf": "*", + "v8-profiler-table": ">=1.2.0" }, "engines": { "node": ">=v0.8.14" diff --git a/geohash.cpp b/src/cgeohash.cpp similarity index 66% rename from geohash.cpp rename to src/cgeohash.cpp index a1c5a94..f37ce20 100644 --- a/geohash.cpp +++ b/src/cgeohash.cpp @@ -6,40 +6,75 @@ #include #include #include +#include + +#include "cgeohash.hpp" + + +namespace cgeohash { + +// Static array of 0-9, a-z +const char base32_codes[] = { + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'j', + 'k', + 'm', + 'n', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z' +}; + +// Build a map of characters -> index position from the above array +const std::map build_base32_indexes(); +const std::map base32_indexes = build_base32_indexes(); + +// Reverse map of characters --> index position +const std::map build_base32_indexes() { + std::map output; + + for(int i = 0, max = 36; i < max; i++) { + output.insert( std::pair(base32_codes[i], i) ); + } + + return output; +} + +// Convert the index position to the character in the array +char base32_codes_value_of(int index) { + return base32_codes[index]; +} -#include "geohash.hpp" - -// http://stackoverflow.com/a/17112198 -#ifdef __MACH__ -#include -#define CLOCK_REALTIME 0 -#define CLOCK_MONOTONIC 0 -int clock_gettime(int clk_id, struct timespec *t){ - mach_timebase_info_data_t timebase; - mach_timebase_info(&timebase); - uint64_t time; - time = mach_absolute_time(); - double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); - double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); - t->tv_sec = seconds; - t->tv_nsec = nseconds; - return 0; +// Convert the character to the index position in the array +int base32_codes_index_of(char c) { + return base32_indexes.find(c)->second; } -#else -#include -#endif - -namespace geohash { -#undef NANOSEC -#define NANOSEC ((uint64_t) 1e9) - - uint64_t nanoseconds() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec); - } - -std::string encode(const double latitude, const double longitude, unsigned long numberOfChars) { + +std::string encode(const double latitude, const double longitude, unsigned long precision) { // DecodedBBox for the lat/lon + errors DecodedBBox bbox; bbox.maxlat = 90; @@ -50,11 +85,12 @@ std::string encode(const double latitude, const double longitude, unsigned long bool islon = true; int num_bits = 0; int hash_index = 0; - const std::string base32_codes = "0123456789bcdefghjkmnpqrstuvwxyz"; - std::string hash_string; + // Pre-Allocate the hash string + std::string hash_string(precision, ' '); + unsigned int hash_string_length = 0; - while(hash_string.length() < numberOfChars) { + while(hash_string_length < precision) { if (islon) { mid = (bbox.maxlon + bbox.minlon) / 2; if(longitude > mid) { @@ -78,7 +114,11 @@ std::string encode(const double latitude, const double longitude, unsigned long ++num_bits; if (5 == num_bits) { - hash_string += base32_codes[hash_index]; + // Append the character to the pre-allocated string + // This gives us roughly a 2x speed boost + hash_string[hash_string_length] = base32_codes[hash_index]; + + hash_string_length++; num_bits = 0; hash_index = 0; } @@ -103,12 +143,10 @@ DecodedBBox decode_bbox(const std::string & _hash_string) { output.minlat = -90; output.minlon = -180; - int char_index = 0; bool islon = true; - const std::string base32_codes = "0123456789bcdefghjkmnpqrstuvwxyz"; for(int i = 0, max = hash_string.length(); i < max; i++) { - char_index = base32_codes.find( hash_string[i] ); + int char_index = base32_codes_index_of(hash_string[i]); for (int bits = 4; bits >= 0; --bits) { int bit = (char_index >> bits) & 1; @@ -155,4 +193,4 @@ std::string neighbor(const std::string & hash_string, const int direction []) { hash_string.length()); } -} // end namespace geohash +} // end namespace cgeohash diff --git a/src/cgeohash.hpp b/src/cgeohash.hpp new file mode 100644 index 0000000..83c85c1 --- /dev/null +++ b/src/cgeohash.hpp @@ -0,0 +1,38 @@ +#ifndef _NODE_CGEOHASH_HPP +#define _NODE_CGEOHASH_HPP + +#include +#include + +namespace cgeohash { + +struct DecodedBBox { + double minlat, minlon, maxlat, maxlon; +}; + +struct DecodedHash { + double latitude; + double longitude; + + double latitude_err; + double longitude_err; +}; + +// Encode a pair of latitude and longitude into geohash +std::string encode(const double latitude, const double longitude, const unsigned long precision); + +// Decode a hash string into pair of latitude and longitude +DecodedHash decode(const std::string & hash_string); + +// Decode hashstring into a bound box matches it +DecodedBBox decode_bbox(const std::string & hash_string); + +// Find neighbor of a geohash string in certain direction. +// Direction is a two-element array: +// Ex: [ 1, 0] == north +// Ex: [-1,-1] == southwest +std::string neighbor(const std::string & hash_string, const int direction []); + +} // end namespace cgeohash + +#endif /* end hpp */ diff --git a/src/cgeohash_bindings.cpp b/src/cgeohash_bindings.cpp new file mode 100644 index 0000000..d9a764d --- /dev/null +++ b/src/cgeohash_bindings.cpp @@ -0,0 +1,17 @@ +#include +#include + +#include "cgeohash.hpp" +#include "cgeohash_fn.hpp" +#include "cgeohash_fn_repeaters.hpp" +#include "cgeohash_obj.hpp" +#include "cgeohash_obj_repeaters.hpp" + +void RegisterModule(v8::Handle target) { + cgeohash::GeoHashObject::Init(target); + cgeohash::GeoHashObjectRepeater::Init(target); + cgeohash::register_node_fns(target); + cgeohash::register_node_fn_repeaters(target); +} + +NODE_MODULE(cgeohash, RegisterModule); diff --git a/geohash_node_binding.cpp b/src/cgeohash_fn.cpp similarity index 82% rename from geohash_node_binding.cpp rename to src/cgeohash_fn.cpp index bead322..5a1bf1f 100644 --- a/geohash_node_binding.cpp +++ b/src/cgeohash_fn.cpp @@ -1,17 +1,16 @@ #include #include -#include "cvv8/detail/convert_core.hpp" +#include "../includes/cvv8/detail/convert_core.hpp" -#include "geohash.hpp" -#include "geohash_node_binding.hpp" +#include "cgeohash.hpp" +#include "cgeohash_fn.hpp" #define _THROW_NODE_ERROR(MSG) ThrowException(v8::Exception::Error(v8::String::New(MSG))); - -namespace geohash { +namespace cgeohash { // Node.JS Hooks to GeoHash encoding - v8::Handle encode_js(const v8::Arguments& args) { + v8::Handle encode_fn(const v8::Arguments& args) { v8::HandleScope scope; if (args.Length() < 2) { @@ -28,7 +27,7 @@ namespace geohash { return scope.Close(cvv8::CastToJS(hash_string)); } - v8::Handle decode_js(const v8::Arguments& args) { + v8::Handle decode_fn(const v8::Arguments& args) { v8::HandleScope scope; if (args.Length() < 1) { @@ -60,7 +59,7 @@ namespace geohash { return scope.Close(output); } - v8::Handle decode_bbox_js(const v8::Arguments& args) { + v8::Handle decode_bbox_fn(const v8::Arguments& args) { v8::HandleScope scope; if (args.Length() < 1) { @@ -73,7 +72,7 @@ namespace geohash { } DecodedBBox decoded_bbox = decode_bbox(hash_string); - std::list list(4); + std::list list; list.push_back(decoded_bbox.minlat); list.push_back(decoded_bbox.minlon); list.push_back(decoded_bbox.maxlat); @@ -82,7 +81,7 @@ namespace geohash { return scope.Close(cvv8::CastToJS< std::list >(list)); } - v8::Handle neighbor_js(const v8::Arguments& args) { + v8::Handle neighbor_fn(const v8::Arguments& args) { v8::HandleScope scope; if (args.Length() < 2) { @@ -107,12 +106,13 @@ namespace geohash { return scope.Close(cvv8::CastToJS(neighbor_string)); } -} +void register_node_fns(v8::Handle target) { + node::SetMethod(target, "encode_fn", encode_fn); + node::SetMethod(target, "decode_fn", decode_fn); + node::SetMethod(target, "decode_bbox_fn", decode_bbox_fn); + node::SetMethod(target, "neighbor_fn", neighbor_fn); +} -void geohash_Init(v8::Handle target) { - node::SetMethod(target, "encode_js", geohash::encode_js); - node::SetMethod(target, "decode_js", geohash::decode_js); - node::SetMethod(target, "decode_bbox_js", geohash::decode_bbox_js); - node::SetMethod(target, "neighbor_js", geohash::neighbor_js); } + diff --git a/src/cgeohash_fn.hpp b/src/cgeohash_fn.hpp new file mode 100644 index 0000000..673ed9f --- /dev/null +++ b/src/cgeohash_fn.hpp @@ -0,0 +1,20 @@ +#ifndef _NODE_CGEOHASH_FN_HPP +#define _NODE_CGEOHASH_FN_HPP + +#include +#include + +namespace cgeohash { + +// Node.JS Hooks to GeoHash encoding +v8::Handle encode_fn(const v8::Arguments& args); +v8::Handle decode_fn(const v8::Arguments& args); +v8::Handle decode_bbox_fn(const v8::Arguments& args); +v8::Handle neighbor_fn(const v8::Arguments& args); + +// Register the above methods with node +void register_node_fns(v8::Handle target); + +} + +#endif /* end hpp */ diff --git a/src/cgeohash_fn_repeaters.cpp b/src/cgeohash_fn_repeaters.cpp new file mode 100644 index 0000000..c39dd0f --- /dev/null +++ b/src/cgeohash_fn_repeaters.cpp @@ -0,0 +1,116 @@ +#include +#include +#include "../includes/cvv8/detail/convert_core.hpp" + +#include "cgeohash.hpp" +#include "cgeohash_fn_repeaters.hpp" +#include "cgeohash_nanoseconds.hpp" + +#include + +#define _THROW_NODE_ERROR(MSG) ThrowException(v8::Exception::Error(v8::String::New(MSG))); + + +namespace cgeohash { + + // Node.JS Hooks to GeoHash encoding + v8::Handle encode_fn_repeater(const v8::Arguments& args) { + v8::HandleScope scope; + + for(int i = 0; i < 1e6; i++) { + encode(37.8324, 112.5584, 9); + } + + return scope.Close(v8::Undefined()); + } + + v8::Handle decode_fn_repeater(const v8::Arguments& args) { + v8::HandleScope scope; + + if (args.Length() < 2) { + return _THROW_NODE_ERROR("Takes 1 parameters: num_times, hash_string"); + } + + int i = 0; + const int num_times = cvv8::CastFromJS< int >(args[i++]); + const std::string hash_string = cvv8::CastFromJS(args[i++]); + if (hash_string.empty()) { + return _THROW_NODE_ERROR("Parameter 0 must be a valid string"); + } + + const uint64_t _nanoseconds = nanoseconds(); + for(int i = 0; i < num_times; i++) { + // std::cout << __FILE__ << ':' << __LINE__ << '=' << i << std::endl; + decode(hash_string); + } + return scope.Close(cvv8::CastToJS((nanoseconds() - _nanoseconds) / num_times)); + // const double _seconds = seconds_differience_of_nanoseconds(_nanoseconds) / num_times; + // return scope.Close(cvv8::CastToJS(_seconds)); + } + + v8::Handle decode_bbox_fn_repeater(const v8::Arguments& args) { + v8::HandleScope scope; + + if (args.Length() < 2) { + return _THROW_NODE_ERROR("Takes 1 parameters: num_times, hash_string"); + } + + int i = 0; + const int num_times = cvv8::CastFromJS< int >(args[i++]); + const std::string hash_string = cvv8::CastFromJS(args[i++]); + if (hash_string.empty()) { + return _THROW_NODE_ERROR("Parameter 1 must be a valid string"); + } + + const uint64_t _nanoseconds = nanoseconds(); + for(int i = 0; i < num_times; i++) { + // std::cout << __FILE__ << ':' << __LINE__ << '=' << i << std::endl; + decode_bbox(hash_string); + } + return scope.Close(cvv8::CastToJS((nanoseconds() - _nanoseconds) / num_times)); + // const double _seconds = seconds_differience_of_nanoseconds(_nanoseconds) / num_times; + // return scope.Close(cvv8::CastToJS(_seconds)); + } + + v8::Handle neighbor_fn_repeater(const v8::Arguments& args) { + v8::HandleScope scope; + + if (args.Length() < 3) { + return _THROW_NODE_ERROR("Takes 3 parameters: num_times, hash_string, and direction []"); + } + + int i = 0; + const int num_times = cvv8::CastFromJS< int >(args[i++]); + const std::string hash_string = cvv8::CastFromJS(args[i++]); + if (hash_string.empty()) { + return _THROW_NODE_ERROR("Parameter 1 must be a valid string"); + } + + const std::list directions = cvv8::CastFromJS< std::list >(args[i++]); + if (directions.size() != 2) { + return _THROW_NODE_ERROR("Parameter 2 must be an array with 2 numbers"); + } + + const int directions_array [] = { + directions.front(), // Only 2 elements + directions.back() // Only 2 elements + }; + + const uint64_t _nanoseconds = nanoseconds(); + for(int i = 0; i < num_times; i++) { + // std::cout << __FILE__ << ':' << __LINE__ << '=' << i << std::endl; + neighbor(hash_string, directions_array); + } + return scope.Close(cvv8::CastToJS((nanoseconds() - _nanoseconds) / num_times)); + // const double _seconds = seconds_differience_of_nanoseconds(_nanoseconds) / num_times; + // return scope.Close(cvv8::CastToJS(_seconds)); + } + + void register_node_fn_repeaters(v8::Handle target) { + node::SetMethod(target, "encode_fn_repeater", encode_fn_repeater); + node::SetMethod(target, "decode_fn_repeater", decode_fn_repeater); + node::SetMethod(target, "decode_bbox_fn_repeater", decode_bbox_fn_repeater); + node::SetMethod(target, "neighbor_fn_repeater", neighbor_fn_repeater); + } + +} diff --git a/src/cgeohash_fn_repeaters.hpp b/src/cgeohash_fn_repeaters.hpp new file mode 100644 index 0000000..96afbdc --- /dev/null +++ b/src/cgeohash_fn_repeaters.hpp @@ -0,0 +1,20 @@ +#ifndef _NODE_CGEOHASH_FN_REPEATERS_HPP +#define _NODE_CGEOHASH_FN_REPEATERS_HPP + +#include +#include + +namespace cgeohash { + +// Node.JS Hooks to GeoHash encoding +v8::Handle encode_fn_repeater(const v8::Arguments& args); +v8::Handle decode_fn_repeater(const v8::Arguments& args); +v8::Handle decode_bbox_fn_repeater(const v8::Arguments& args); +v8::Handle neighbor_fn_repeater(const v8::Arguments& args); + +// Register the above methods with node +void register_node_fn_repeaters(v8::Handle target); + +} + +#endif /* end hpp */ diff --git a/src/cgeohash_nanoseconds.cpp b/src/cgeohash_nanoseconds.cpp new file mode 100644 index 0000000..123b73e --- /dev/null +++ b/src/cgeohash_nanoseconds.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cgeohash_nanoseconds.hpp" + +// http://stackoverflow.com/a/17112198 +#ifdef __MACH__ +#include + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 +int clock_gettime(int clk_id, struct timespec *t){ + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + uint64_t time; + time = mach_absolute_time(); + double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); + double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); + t->tv_sec = seconds; + t->tv_nsec = nseconds; + return 0; +} + +#else + +#include + +#endif + + +// #if TARGET_OS_IPHONE +// /* see: http://developer.apple.com/library/mac/#qa/qa1398/_index.html */ +// uint64_t uv_hrtime() { +// uint64_t time; +// uint64_t enano; +// static mach_timebase_info_data_t sTimebaseInfo; +// +// time = mach_absolute_time(); +// +// if (0 == sTimebaseInfo.denom) { +// (void)mach_timebase_info(&sTimebaseInfo); +// } +// +// enano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; +// +// return enano; +// } +// #else +// uint64_t uv_hrtime() { +// uint64_t time; +// Nanoseconds enano; +// time = mach_absolute_time(); +// enano = AbsoluteToNanoseconds(*(AbsoluteTime *)&time); +// return (*(uint64_t *)&enano); +// } +// #endif +// + + + + + +namespace cgeohash { +#undef NANOSEC +#define NANOSEC ((uint64_t) 1e9) + + uint64_t nanoseconds() { + return uv_hrtime(); + // struct timespec ts; + // clock_gettime(CLOCK_MONOTONIC, &ts); + // return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec); + } + + double seconds_differience_of_nanoseconds(const uint64_t _nanoseconds) { + const uint64_t _diff = nanoseconds() - _nanoseconds; + const double _seconds = ((double) _diff) / ((double) NANOSEC); + return _seconds; + } + +} // end namespace cgeohash diff --git a/src/cgeohash_nanoseconds.hpp b/src/cgeohash_nanoseconds.hpp new file mode 100644 index 0000000..258dcb5 --- /dev/null +++ b/src/cgeohash_nanoseconds.hpp @@ -0,0 +1,16 @@ +#ifndef _NODE_CGEOHASH_NANOSECONDS_HPP +#define _NODE_CGEOHASH_NANOSECONDS_HPP + +#include +#include + +namespace cgeohash { + +// Compute nanoseconds at current time +uint64_t nanoseconds(); + +double seconds_differience_of_nanoseconds(const uint64_t _nanoseconds); + +} // end namespace cgeohash + +#endif /* end hpp */ diff --git a/src/cgeohash_obj.cpp b/src/cgeohash_obj.cpp new file mode 100644 index 0000000..adede58 --- /dev/null +++ b/src/cgeohash_obj.cpp @@ -0,0 +1,77 @@ +#include +#include + +#include "cgeohash_fn.hpp" +#include "cgeohash_obj.hpp" + +#define _THROW_NODE_ERROR(MSG) ThrowException(v8::Exception::Error(v8::String::New(MSG))); + +namespace cgeohash { + +v8::Persistent GeoHashObject::constructor; + + +void GeoHashObject::Init(v8::Handle target) { + v8::HandleScope scope; + + v8::Local tpl = v8::FunctionTemplate::New(New); + v8::Local name = v8::String::NewSymbol("GeoHashObject"); + + constructor = v8::Persistent::New(tpl); + + // ObjectWrap uses the first internal field to store the wrapped pointer. + constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->SetClassName(name); + + // Add all prototype methods, getters and setters here. + NODE_SET_PROTOTYPE_METHOD(constructor, "encode_obj", Encode); + NODE_SET_PROTOTYPE_METHOD(constructor, "decode_obj", Decode); + NODE_SET_PROTOTYPE_METHOD(constructor, "decode_bbox_obj", DecodeBBox); + NODE_SET_PROTOTYPE_METHOD(constructor, "neighbor_obj", Neighbor); + + + // This has to be last, otherwise the properties won't show up on the + // object in JavaScript. + target->Set(name, constructor->GetFunction()); +} + + +GeoHashObject::GeoHashObject() : node::ObjectWrap() { +} + + +v8::Handle GeoHashObject::New(const v8::Arguments& args) { + v8::HandleScope scope; + + if (!args.IsConstructCall()) { + return _THROW_NODE_ERROR("Use the new operator to create instances of this object."); + } + + // Creates a new instance object of this type and wraps it. + GeoHashObject* obj = new GeoHashObject(); + obj->Wrap(args.This()); + + return args.This(); +} + + +v8::Handle GeoHashObject::Encode(const v8::Arguments& args) { + return encode_fn(args); +} + + +v8::Handle GeoHashObject::Decode(const v8::Arguments& args) { + return decode_fn(args); +} + + +v8::Handle GeoHashObject::DecodeBBox(const v8::Arguments& args) { + return decode_bbox_fn(args); +} + + +v8::Handle GeoHashObject::Neighbor(const v8::Arguments& args) { + return neighbor_fn(args); +} + +} \ No newline at end of file diff --git a/geohash_obj.hpp b/src/cgeohash_obj.hpp similarity index 58% rename from geohash_obj.hpp rename to src/cgeohash_obj.hpp index 716d614..79ca207 100644 --- a/geohash_obj.hpp +++ b/src/cgeohash_obj.hpp @@ -1,7 +1,10 @@ -#ifndef _NODE_GEOHASH_OBJ_HPP -#define _NODE_GEOHASH_OBJ_HPP +#ifndef _NODE_CGEOHASH_OBJ_HPP +#define _NODE_CGEOHASH_OBJ_HPP #include +#include + +namespace cgeohash { class GeoHashObject : public node::ObjectWrap { public: @@ -16,12 +19,7 @@ class GeoHashObject : public node::ObjectWrap { static v8::Handle Decode(const v8::Arguments& args); static v8::Handle DecodeBBox(const v8::Arguments& args); static v8::Handle Neighbor(const v8::Arguments& args); - - // Used for performance testing!! - static v8::Handle Test1M_Encode(const v8::Arguments& args); - static v8::Handle Test1M_Decode(const v8::Arguments& args); - static v8::Handle Test1M_DecodeBBox(const v8::Arguments& args); - static v8::Handle Test1M_Neighbor(const v8::Arguments& args); }; +} -#endif +#endif /* geohash.hpp */ diff --git a/src/cgeohash_obj_repeaters.cpp b/src/cgeohash_obj_repeaters.cpp new file mode 100644 index 0000000..279536d --- /dev/null +++ b/src/cgeohash_obj_repeaters.cpp @@ -0,0 +1,76 @@ +#include +#include + +#include "cgeohash_obj_repeaters.hpp" +#include "cgeohash_fn_repeaters.hpp" + +#define _THROW_NODE_ERROR(MSG) ThrowException(v8::Exception::Error(v8::String::New(MSG))); + +namespace cgeohash { + +v8::Persistent GeoHashObjectRepeater::constructor; + + +void GeoHashObjectRepeater::Init(v8::Handle target) { + v8::HandleScope scope; + + v8::Local tpl = v8::FunctionTemplate::New(New); + v8::Local name = v8::String::NewSymbol("GeoHashObjectRepeater"); + + constructor = v8::Persistent::New(tpl); + + // ObjectWrap uses the first internal field to store the wrapped pointer. + constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->SetClassName(name); + + // Add all prototype methods, getters and setters here. + NODE_SET_PROTOTYPE_METHOD(constructor, "encode_obj_repeater", Encode); + NODE_SET_PROTOTYPE_METHOD(constructor, "decode_obj_repeater", Decode); + NODE_SET_PROTOTYPE_METHOD(constructor, "decode_bbox_obj_repeater", DecodeBBox); + NODE_SET_PROTOTYPE_METHOD(constructor, "neighbor_obj_repeater", Neighbor); + + // This has to be last, otherwise the properties won't show up on the + // object in JavaScript. + target->Set(name, constructor->GetFunction()); +} + + +GeoHashObjectRepeater::GeoHashObjectRepeater() : node::ObjectWrap() { +} + + +v8::Handle GeoHashObjectRepeater::New(const v8::Arguments& args) { + v8::HandleScope scope; + + if (!args.IsConstructCall()) { + return _THROW_NODE_ERROR("Use the new operator to create instances of this object."); + } + + // Creates a new instance object of this type and wraps it. + GeoHashObjectRepeater* obj = new GeoHashObjectRepeater(); + obj->Wrap(args.This()); + + return args.This(); +} + + +v8::Handle GeoHashObjectRepeater::Encode(const v8::Arguments& args) { + return encode_fn_repeater(args); +} + + +v8::Handle GeoHashObjectRepeater::Decode(const v8::Arguments& args) { + return decode_fn_repeater(args); +} + + +v8::Handle GeoHashObjectRepeater::DecodeBBox(const v8::Arguments& args) { + return decode_bbox_fn_repeater(args); +} + + +v8::Handle GeoHashObjectRepeater::Neighbor(const v8::Arguments& args) { + return neighbor_fn_repeater(args); +} + +} \ No newline at end of file diff --git a/src/cgeohash_obj_repeaters.hpp b/src/cgeohash_obj_repeaters.hpp new file mode 100644 index 0000000..a7998d6 --- /dev/null +++ b/src/cgeohash_obj_repeaters.hpp @@ -0,0 +1,27 @@ +#ifndef _NODE_CGEOHASH_OBJ_REPEATERS_HPP +#define _NODE_CGEOHASH_OBJ_REPEATERS_HPP + +#include +#include + +namespace cgeohash { + +class GeoHashObjectRepeater : public node::ObjectWrap { +public: + static v8::Persistent constructor; + static void Init(v8::Handle target); + +protected: + GeoHashObjectRepeater(); + + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Encode(const v8::Arguments& args); + static v8::Handle Decode(const v8::Arguments& args); + static v8::Handle DecodeBBox(const v8::Arguments& args); + static v8::Handle Neighbor(const v8::Arguments& args); + + }; + +} + +#endif /* geohash.hpp */ diff --git a/tests/cgeohash_fn_test.js b/tests/cgeohash_fn_test.js new file mode 100644 index 0000000..516fac0 --- /dev/null +++ b/tests/cgeohash_fn_test.js @@ -0,0 +1,4 @@ +var shared_spec = require('./shared_spec'); +var cgeohash_fn = require('../lib/cgeohash_fn.js'); + +shared_spec('GeoHash Functions in C++', cgeohash_fn); diff --git a/tests/cgeohash_obj_test.js b/tests/cgeohash_obj_test.js new file mode 100644 index 0000000..7d6cecb --- /dev/null +++ b/tests/cgeohash_obj_test.js @@ -0,0 +1,4 @@ +var cgeohash_shared_spec = require('./shared_spec'); +var cgeohash_obj = new require('../lib/cgeohash_obj.js'); + +cgeohash_shared_spec('GeoHash Object in C++', cgeohash_obj); diff --git a/tests/geohash_js_test.js b/tests/geohash_js_test.js deleted file mode 100644 index 201b3e5..0000000 --- a/tests/geohash_js_test.js +++ /dev/null @@ -1,4 +0,0 @@ -var geohash_shared_spec = require('./geohash_shared_spec'); -var geohash = require('../geohash.js'); - -geohash_shared_spec('GeoHash Functions in C++', geohash); diff --git a/tests/geohash_obj_test.js b/tests/geohash_obj_test.js deleted file mode 100644 index 2699928..0000000 --- a/tests/geohash_obj_test.js +++ /dev/null @@ -1,4 +0,0 @@ -var geohash_shared_spec = require('./geohash_shared_spec'); -var geohash = new require('../geohash.js').geohash_object; - -geohash_shared_spec('GeoHash Object in C++', geohash); diff --git a/tests/geohash_original_test.js b/tests/geohash_original_test.js index e37d13a..2924d5f 100644 --- a/tests/geohash_original_test.js +++ b/tests/geohash_original_test.js @@ -1,30 +1,4 @@ -var geohash_shared_spec = require('./geohash_shared_spec'); -var geohash = require('./geohash_original.js'); +var shared_spec = require('./shared_spec'); +var geohash_js = require('../lib/cgeohash_original'); - -geohash.test1m_encode = function(a, b, c) { - var last = undefined; - for(var i = 0, max = 1000*1000; i < max; i++) { - last = geohash.encode(a, b, c); - } - return last; -} - -geohash.test1m_decode = function(a) { - var last = undefined; - for(var i = 0, max = 1000*1000; i < max; i++) { - last = geohash.decode(a); - } - return last; -} - -geohash.test1m_neighbor = function(a, b) { - var last = undefined; - for(var i = 0, max = 1000*1000; i < max; i++) { - last = geohash.neighbor(a, b); - } - return last; -} - - -geohash_shared_spec('GeoHash Functions in C++', geohash); +shared_spec('GeoHash Functions in C++', geohash_js); diff --git a/tests/geohash_speed_test.js b/tests/geohash_speed_test.js deleted file mode 100644 index ea8f1ab..0000000 --- a/tests/geohash_speed_test.js +++ /dev/null @@ -1,195 +0,0 @@ -// -// Required Libraries -// -var path = require('path'); -var mocha = require('mocha'); -var chai = require('chai'); -var should = chai.should(); - -// -// Constants -// -var longitude = 112.5584; -var latitude = 37.8324; -var geostr = 'ww8p1r4t8'; - -// -// Helpers -// -function seconds_per_call(callback) { - var time = process.hrtime(); // Start - callback(); // Execute - var diff = process.hrtime(time); // Stop - var seconds = diff[0].valueOf() + diff[1].valueOf() / (1000 * 1000 * 1000); - - return seconds / (1000 * 1000); -}; - -function wrap_loop_1M_in_js(callback, validates) { - return function() { - var last = null; - for (var i = 0, max = 1000 * 1000; i < max; i++) { - last = callback(); - } - return validates(last); - }; -} - -function seconds_per_call_loop_1M(callback, validates) { - return seconds_per_call(wrap_loop_1M_in_js(callback, validates)); -} - -function encode_latitude_and_longitude_as_string(geohash) { - return geohash.encode(latitude, longitude, 9); -} - -function decodes_string_to_latitude(geohash) { - return geohash.decode(geostr) - .latitude; -} - -function decodes_string_to_longitude(geohash) { - return geohash.decode(geostr) - .longitude; -} - -function finds_neighbor_to_the_north(geohash) { - return geohash.neighbor('dqcjq', [1, 0]); -} - -function finds_neighbor_to_the_south_west(geohash) { - return geohash.neighbor('dqcjq', [-1, - 1]); -} - -// How many times faster is the new vs old times? -function x_faster(new_time, old_time) { - return Math.round((old_time / new_time) * 100) / 100; -}; - - -// -// Geo Hash Libraries for testing -// -var geohash_c_object = new require('../geohash.js') - .geohash_object; -var geohash_c_functions = require('../geohash.js'); -var geohash_original = require('./geohash_original.js'); - -geohash_original.test1m_encode = function(a, b, c) { - var last = undefined; - for (var i = 0, max = 1000 * 1000; i < max; i++) { - last = geohash_original.encode(a, b, c); - } - return last; -} - -geohash_original.test1m_decode = function(a) { - var last = undefined; - for (var i = 0, max = 1000 * 1000; i < max; i++) { - last = geohash_original.decode(a); - } - return last; -} - -geohash_original.test1m_neighbor = function(a, b) { - var last = undefined; - for (var i = 0, max = 1000 * 1000; i < max; i++) { - last = geohash_original.neighbor(a, b); - } - return last; -} - -// -// Test Suite -// -describe('Speed Tests', function() { - this.timeout(0); - - function compare_ratios(tag, src, js, cpp, validates) { - describe(tag, function() { - var seconds = { - // Javascript <-> Javascript (original version) - js_vs_src: seconds_per_call_loop_1M(src, validates), - // Javascript <-> C++ Function - js_vs_c_function: seconds_per_call_loop_1M(js, validates), - // Javascript <-> C++ Object - js_vs_c_object: seconds_per_call(cpp), - // C++ Only - cpp_vs_c_object: cpp(), - }; - var times_faster = { - js_vs_src: 1.0, // Ratio to self is always 1 - js_vs_c_function: x_faster(seconds.js_vs_c_function , seconds.js_vs_src), - js_vs_c_object: x_faster(seconds.js_vs_c_object , seconds.js_vs_src), - cpp_vs_c_object: x_faster(seconds.cpp_vs_c_object , seconds.js_vs_src), - } - - var seconds_s = JSON.stringify(seconds); - var times_faster_to_s = JSON.stringify(times_faster); - if (process.env.VERBOSE) { - console.log(tag + ': times_faster=' + times_faster_to_s); - console.log(tag + ': seconds=' + seconds_s); - } - - describe('[X times Faster]', function() { - it('JS <-> JS Original Version compared to self should be 1.0', function() { - chai.assert.ok(times_faster.js_vs_src == 1.0, times_faster_to_s); - }); - - it('JS <-> Node Function Wrapping-C++ is slower than Original Version', function() { - chai.assert.ok(Math.round(times_faster.js_vs_c_function) <= 1, times_faster_to_s); - }); - - it('JS <-> Node Object Wrapping-C++ is slower than Original Version', function() { - chai.assert.ok(Math.round(times_faster.js_vs_c_object) <= 1, times_faster_to_s); - }); - - // seconds.cpp_vs_c_object is null if it was too fast to measure - it('Pure-C++ function is 100x+ faster than JS Original Version', function() { - if (seconds.cpp_vs_c_object) { - chai.assert.ok(Math.round(times_faster.cpp_vs_c_object) > 100, times_faster_to_s); - } - }); - }); - - }); - }; - - compare_ratios('encodes latitude & longitude as string', - function() { return encode_latitude_and_longitude_as_string(geohash_original); }, - function() { return encode_latitude_and_longitude_as_string(geohash_c_functions); }, - function() { return geohash_c_object.test1m_encode(latitude, longitude, 9) / 1000000000; }, - function(data) { data.should.equal(geostr); }); - - compare_ratios('decodes string to latitude', - function() { return decodes_string_to_latitude(geohash_original); }, - function() { return decodes_string_to_latitude(geohash_c_functions); }, - function() { return geohash_c_object.test1m_decode(geostr) / 1000000000; }, - function(data) { - var diff = Math.abs(latitude - data) < 0.0001 - var msg = 'Expected ' + latitude + '-' + data + ' to be very close' - chai.assert.ok(diff, msg); - }); - - compare_ratios('decodes string to longitude', - function() { return decodes_string_to_longitude(geohash_original); }, - function() { return decodes_string_to_longitude(geohash_c_functions); }, - function() { return geohash_original.test1m_decode(geostr) / 1000000000; }, - function(data) { - var diff = Math.abs(longitude - data) < 0.0001 - var msg = 'Expected ' + longitude + '-' + data + ' to be very close' - chai.assert.ok(diff, msg); - }); - - compare_ratios('finds neighbor to the north', - function() { return finds_neighbor_to_the_north(geohash_original); }, - function() { return finds_neighbor_to_the_north(geohash_c_functions); }, - function() { return geohash_c_object.test1m_neighbor('dqcjq', [1, 0]) / 1000000000; }, - function(data) { data.should.equal('dqcjw'); }); - - compare_ratios('finds neighbor to the south-west', - function() { return finds_neighbor_to_the_south_west(geohash_original); }, - function() { return finds_neighbor_to_the_south_west(geohash_c_functions); }, - function() { return geohash_c_object.test1m_neighbor('dqcjq', [-1, - 1]) / 1000000000; }, - function(data) { data.should.equal('dqcjj'); }); -}); diff --git a/tests/geohash_shared_spec.js b/tests/shared_spec.js similarity index 76% rename from tests/geohash_shared_spec.js rename to tests/shared_spec.js index 29601c8..3db9b6d 100644 --- a/tests/geohash_shared_spec.js +++ b/tests/shared_spec.js @@ -39,6 +39,10 @@ function encode_latitude_and_longitude_as_string(geohash) { return geohash.encode(latitude, longitude, 9); } +function decodes_string_to_bounded_box(geohash) { + return geohash.decode_bbox('ww8p1r4t8'); +} + function decodes_string_to_latitude(geohash) { return geohash.decode(geostr) .latitude; @@ -65,6 +69,27 @@ module.exports = function(tag, geohash) { .should.equal(geostr); }); + it('decodes string to bounded box', function() { + var expected = [ + 37.83236503601074, + 112.55836486816406, + 37.83240795135498, + 112.5584077835083]; + + var bbox = decodes_string_to_bounded_box(geohash); + + // Round the numbers to integers and compare them + var rounding_match = function(a, b) { + Math.round(a) + .should.equal( + Math.round(b), "expected=" + JSON.stringify(expected, undefined, 2) + ", bbox=" + JSON.stringify(bbox, undefined, 2)); + } + rounding_match(bbox[0], expected[0]); + rounding_match(bbox[1], expected[1]); + rounding_match(bbox[2], expected[2]); + rounding_match(bbox[3], expected[3]); + }); + it('decodes string to latitude', function() { var latlon_latitude = decodes_string_to_latitude(geohash); var diff = Math.abs(latitude - latlon_latitude) < 0.0001 diff --git a/tests/speed_test.js b/tests/speed_test.js new file mode 100644 index 0000000..139174c --- /dev/null +++ b/tests/speed_test.js @@ -0,0 +1,134 @@ +// +// Required Libraries +// +var path = require('path'); +var mocha = require('mocha'); +var chai = require('chai'); +var should = chai.should(); +var v8_profiler_table = require('v8-profiler-table'); + +// +// Helpers +// + +function compare_ratios(tag, timings) { + describe(tag, function() { + // Clear the profiles + v8_profiler_table.reset_profiles(); + + // Log the run-times of the Original and C++ versions + v8_profiler_table.record_profile('Original JS Version', timings.original_in_js); + v8_profiler_table.record_profile('All C++ Version', timings.fn_in_cpp); + + console.log(v8_profiler_table.stringify()); + + it ('Original JS Version', function() { + var profile = v8_profiler_table.profiles()['Original JS Version']; + + // Sanity checks + profile.title.should.equal('Original JS Version'); + profile.ratio_to_base.should.equal(1); + chai.assert.ok(profile.total_seconds <= 1.00, JSON.stringify(profile, undefined, 2)); + }); + + it ('All C++ Version is at least 1.5x as fast', function() { + var profile = v8_profiler_table.profiles()['All C++ Version']; + + // Sanity checks + profile.title.should.equal('All C++ Version'); + chai.assert.ok(profile.ratio_to_base >= 1.50, JSON.stringify(profile, undefined, 2)); + chai.assert.ok(profile.total_seconds <= 0.50, JSON.stringify(profile, undefined, 2)); + }); + }); +}; + + +// +// Geo Hash Libraries for testing +// +var fn_repeaters = require('../lib/cgeohash_fn_repeaters.js'); +var obj_repeaters = require('../lib/cgeohash_obj_repeaters.js'); +var orig_repeaters = require('../lib/cgeohash_original_repeaters.js'); + + +// +// Test Suite +// +describe('Speed Tests', function() { + this.timeout(0); + + var num_runs = 1000*1000; + + compare_ratios('encodes latitude & longitude as string', { + original_in_js: function() { + return orig_repeaters.repeat_in_js.encode(num_runs, 37.8324, 112.5584, 9); + }, + fn_in_cpp: function() { + return fn_repeaters.repeat_in_cpp.encode(num_runs, 37.8324, 112.5584, 9); + }, + // obj_in_js: function() { + // return obj_repeaters.repeat_in_js.encode(num_runs, 37.8324, 112.5584, 9); + // }, + // obj_in_cpp: function() { + // return obj_repeaters.repeat_in_cpp.encode(num_runs, 37.8324, 112.5584, 9); + // }, + // fn_in_js: function() { + // return fn_repeaters.repeat_in_js.encode(num_runs, 37.8324, 112.5584, 9); + // }, + }); + + compare_ratios('decodes string to latitude & longitude', { + original_in_js: function() { + return orig_repeaters.repeat_in_js.decode(num_runs, 'ww8p1r4t8'); + }, + fn_in_cpp: function() { + return fn_repeaters.repeat_in_cpp.decode(num_runs, 'ww8p1r4t8'); + }, + // obj_in_js: function() { + // return obj_repeaters.repeat_in_js.decode(num_runs, 'ww8p1r4t8'); + // }, + // obj_in_cpp: function() { + // return obj_repeaters.repeat_in_cpp.decode(num_runs, 'ww8p1r4t8'); + // }, + // fn_in_js: function() { + // return fn_repeaters.repeat_in_js.decode(num_runs, 'ww8p1r4t8'); + // }, + }); + + compare_ratios('finds neighbor to the north', { + original_in_js: function() { + return orig_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [1, 0]); + }, + fn_in_cpp: function() { + return fn_repeaters.repeat_in_cpp.neighbor(num_runs, 'dqcjq', [1, 0]); + }, + // obj_in_js: function() { + // return obj_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [1, 0]); + // }, + // obj_in_cpp: function() { + // return obj_repeaters.repeat_in_cpp.neighbor(num_runs, 'dqcjq', [1, 0]); + // }, + // fn_in_js: function() { + // return fn_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [1, 0]); + // }, + }); + + compare_ratios('finds neighbor to the south-west', { + original_in_js: function() { + return orig_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [-1, - 1]); + }, + fn_in_cpp: function() { + return fn_repeaters.repeat_in_cpp.neighbor(num_runs, 'dqcjq', [-1, - 1]); + }, + // obj_in_js: function() { + // return obj_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [-1, - 1]); + // }, + // obj_in_cpp: function() { + // return obj_repeaters.repeat_in_cpp.neighbor(num_runs, 'dqcjq', [-1, - 1]); + // }, + // fn_in_js: function() { + // return fn_repeaters.repeat_in_js.neighbor(num_runs, 'dqcjq', [-1, - 1]); + // }, + }); + +});