JavaScript 是最常用的前端语言, 在后端也渐渐有所应用, 比如 NodeJS, 在C++应用中嵌入JavaScript 到底性能如何?
就拿最流行的 Mozilla SpiderMonkey 和 Google V8 做一个比较测试, 先以 SpiderMonkey 为例, 来执行一个一万个字串的数据排序和反转
1. 下载
https://people.mozilla.org/~sstangl/mozjs-31.2.0.rc0.tar.bz2
bunzip2 mozjs-31.2.0.rc0.tar.bz2
tar xvf mozjs-31.2.0.rc0.tar
2. 构建
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation
2.1 下载 autoconf2.13
http://ftp.gnu.org/gnu/autoconf/autoconf-2.13.tar.gz
tar xvfz autoconf-2.13.tar.gz
./configure --prefix=/usr --program-suffix=-2.13
make acdatadir=/usr/share/autoconf-2.13
make acdatadir=/usr/share/autoconf-2.13 install
2.2 编译步骤
cd js/src
autoconf-2.13
# This name should end with "_OPT.OBJ" to make the version control system ignore it.
mkdir build_OPT.OBJ
cd build_OPT.OBJ
../configure
# Use "mozmake" on Windows
make
make install
------------
../../dist/bin/nsinstall -t js-config /usr/local/bin
../../dist/bin/nsinstall -t libjs_static.a /usr/local/lib
mv -f /usr/local/lib/libjs_static.a /usr/local/lib/libmozjs-31.a
../../dist/bin/nsinstall -t libmozjs-31.dylib /usr/local/lib
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C shell install
../../../dist/bin/nsinstall -t js /usr/local/bin
------------
2.3 编译验证
# ./dist/bin/js
js> print("hello world");
hello world
js> quit();
3. 参考资料
*https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_User_Guide
4. 测试程序
#include "jsapi.h" #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <time.h> #include <unistd.h> #define TRACE_MINARGS 2 #define TIME_FMT_LEN 50 using namespace JS; // The class of the global object. static JSClass globalClass = { "global", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; long long current_timestamp(char arrTimeStr[TIME_FMT_LEN]) { struct timeval tv; struct tm* ptm; char time_string[40]; gettimeofday(&tv, NULL); // get current time if (arrTimeStr) { ptm = localtime(&tv.tv_sec); /* Format the date and time, down to a single second. */ strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", ptm); /* Compute milliseconds from microseconds. */ //snprintf(char * restrict str, size_t size, const char * restrict format, snprintf(arrTimeStr, TIME_FMT_LEN, "%s.%06d", time_string, tv.tv_usec); } long long total_us = tv.tv_sec * 1000000LL + tv.tv_usec ; // caculate milliseconds // printf("milliseconds: %lld\n", milliseconds); return total_us; } // [SpiderMonkey 24] Use JSBool instead of bool. static bool debug_trace(JSContext *cx, unsigned argc, jsval *vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); if (args.length() > 0) { char szTimeStr[TIME_FMT_LEN] = { '\0' }; current_timestamp(szTimeStr); JSString *str = args[0].toString(); printf("[%s] %s\n", szTimeStr, JS_EncodeString(cx, str)); } return true; } //typedef void (* JSErrorReporter)(JSContext *cx, const char *message, JSErrorReport *report); // The error reporter callback. void report_error(JSContext *cx, const char *message, JSErrorReport *report) { fprintf(stderr, "%s:%u:%s\n", report->filename ? report->filename : "[no filename]", (unsigned int) report->lineno, message); } int load_file_malloc(const char* szFile, char*& pBuffer, long* pBufSize = NULL) { FILE * pFile = NULL; long lSize = 0; size_t result = 0; pFile = fopen(szFile, "r"); if (pFile == NULL) { fputs("File open error", stderr); return 1; } // obtain file size: fseek(pFile, 0, SEEK_END); lSize = ftell(pFile); rewind(pFile); // allocate memory to contain the whole file: pBuffer = (char*) malloc(sizeof(char) * lSize); if (pBuffer == NULL) { fputs("Memory allocate error", stderr); fclose(pFile); return 2; } // copy the file into the buffer: result = fread(pBuffer, 1, lSize, pFile); if (result != lSize) { fputs("Reading file error", stderr); fclose(pFile); return 3; } if (pBufSize) *pBufSize = lSize; fclose(pFile); return 0; } int test(JSContext *cx, RootedObject* pGlobal, const char* pScript) { //RootedObject global = *pGlobal; JS::RootedValue rval(cx); JSAutoCompartment ac(cx, *pGlobal); JS_InitStandardClasses(cx, *pGlobal); const char *filename = "noname"; int lineno = 1; bool ok = JS_DefineFunction(cx, *pGlobal, "debug_trace", debug_trace, TRACE_MINARGS, 0); if (!ok) return 1; ok = JS_EvaluateScript(cx, *pGlobal, pScript, strlen(pScript), filename, lineno, &rval); if (!ok) return 2; //JSString *str = rval.toString(); //printf("%s\n", JS_EncodeString(cx, str)); return 0; } int run(JSContext *cx, const char* pScript) { // Enter a request before running anything in the context. JSAutoRequest ar(cx); // Create the global object and a new compartment. RootedObject global(cx); global = JS_NewGlobalObject(cx, &globalClass, nullptr, JS::DontFireOnNewGlobalHook); if (!global) return 1; // Enter the new global object's compartment. JSAutoCompartment ac(cx, global); // Populate the global object with the standard globals, like Object and // Array. if (!JS_InitStandardClasses(cx, global)) return 1; // Your application code here. This may include JSAPI calls to create your // own custom JS objects and run scripts. long long begin_time = current_timestamp(NULL); test(cx, &global, pScript); long long end_time = current_timestamp(NULL); printf("calling costs %lld microseconds\n", end_time - begin_time); return 0; } int main(int argc, const char *argv[]) { // Initialize the JS engine. if (!JS_Init()) return 1; // Create a JS runtime. JSRuntime *rt = JS_NewRuntime(8L * 1024L * 1024L, JS_USE_HELPER_THREADS); if (!rt) return 1; // Create a context. JSContext *cx = JS_NewContext(rt, 8192); if (!cx) return 1; //JS_SetErrorReporter(JSContext *cx, JSErrorReporter er); JS_SetErrorReporter(cx, report_error); int status = 0; if (argc > 1) { char* buffer = NULL; int ret = load_file_malloc(argv[1], buffer); if (ret != 0) { return ret; } status = run(cx, buffer); // free if (buffer) free(buffer); } else { const char *script = "'hello'+'world, it is '+new Date()"; status = run(cx, script); } // Shut everything down. JS_DestroyContext(cx); JS_DestroyRuntime(rt); JS_ShutDown(); return status; }
测试JavaScript 脚本
function random_str() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for( var i=0; i < 8; i++ ) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } var array = new Array(); for ( var i = 0; i < 10000; i++) { array[array.length] = random_str(); } debug_trace("begin sort and reverse array which length=" + array.length ); array.sort(); array.reverse(); debug_trace("done, first element=" + array[0]+ ", " + "last element=" + array[array.length-1] );
测试结果
$ ./test/SpiderMonkeyTest ./test/arraysort.js
[2015-05-07 21:07:29.762895] begin sort and reverse array which length=10000
[2015-05-07 21:07:29.766270] done, first element=zzjG0Pnh, last element=0000LZbe
calling costs 52492 microseconds
性能还凑合, 大约用了4 毫秒多, 下一步看看 Google V8 的表现如何