Duktapeを使ってC++コードとECMAscriptをブリッジさせる
導入
% wget http://duktape.org/duktape-1.5.1.tar.xz % tar xvfJ duktape-1.5.1.tar.xz % make -f Makefile.cmdline % ./duk -e "print('Hello duk')" Hello duk
機能テスト
Ecmascriptのアルゴリズムや機能をDuktapeで利用する事ができる。
% ./duk test_src/t.js 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
実行したテストコードは以下。*1
"use strict"; function prime(x){ var max=x+1,prime=new Array(x),ret=new Array(); for(var i=1; i<max; ++i){prime[i]=true;} for(var i=2; i<max; ++i){ if(prime[i]){ ret.push(i); for(var j=i;j*i<=max; ++j){ prime[j*i]=false } } } return ret; } print(prime(100));
ブリッジ
パッケージに元から付いているサンプルコードでブリッジさせてみる。
% cat examples/hello/hello.c /* * Very simple example program */ #include "duktape.h" int adder(duk_context *ctx) { int i; int n = duk_get_top(ctx); /* #args */ double res = 0.0; for (i = 0; i < n; i++) { res += duk_to_number(ctx, i); } duk_push_number(ctx, res); return 1; /* one return value */ } int main(int argc, char *argv[]) { duk_context *ctx = duk_create_heap_default(); (void) argc; (void) argv; /* suppress warning */ duk_eval_string(ctx, "print('Hello world!');"); duk_push_global_object(ctx); duk_push_c_function(ctx, adder, DUK_VARARGS); duk_put_prop_string(ctx, -2, "adder"); duk_pop(ctx); /* pop global */ duk_eval_string(ctx, "print('2+3=' + adder(2, 3));"); duk_pop(ctx); /* pop eval result */ duk_destroy_heap(ctx); return 0; }
動かす。
% gcc -std=c99 -Isrc/ src/duktape.c examples/hello/hello.c -lm && ./a.out Hello world! 2+3=5
しかし、他言語を用いているのだからC/C++で出来ない事をやらなければブリッジの意味がない。
例えば、正規表現を用いて、HTMLへ文字列を置換させる。*2
% cat process.js // process.js function processLine(line) { return line.trim() .replace(/[<>&"'\u0000-\u001F\u007E-\uFFFF]/g, function(x) { // escape HTML characters return '&#' + x.charCodeAt(0) + ';' }) .replace(/\*(.*?)\*/g, function(x, m) { // automatically bold text between stars return '<b>' + m + '</b>'; }); }
% cat processlines.c /* processlines.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "duktape.h" int main(int argc, const char *argv[]) { duk_context *ctx = NULL; char line[4096]; char idx; int ch; ctx = duk_create_heap_default(); if (!ctx) { printf("Failed to create a Duktape heap.\n"); exit(1); } if (duk_peval_file(ctx, "process.js") != 0) { printf("Error: %s\n", duk_safe_to_string(ctx, -1)); goto finished; } duk_pop(ctx); /* ignore result */ memset(line, 0, sizeof(line)); idx = 0; for (;;) { if (idx >= sizeof(line)) { printf("Line too long\n"); exit(1); } ch = fgetc(stdin); if (ch == 0x0a) { line[idx++] = '\0'; duk_push_global_object(ctx); duk_get_prop_string(ctx, -1 /*index*/, "processLine"); duk_push_string(ctx, line); if (duk_pcall(ctx, 1 /*nargs*/) != 0) { printf("Error: %s\n", duk_safe_to_string(ctx, -1)); } else { printf("%s\n", duk_safe_to_string(ctx, -1)); } duk_pop(ctx); /* pop result/error */ idx = 0; } else if (ch == EOF) { break; } else { line[idx++] = (char) ch; } } finished: duk_destroy_heap(ctx); exit(0); }
% gcc -std=c99 -Isrc/ src/duktape.c processlines.c -lm && ./a.out "I like *Sam & Max*." "I like <b>Sam & Max</b>."
ECMAscriptからC/C++コードを呼ぶ
C/C++では基本的には*3不可能な処理をjavascriptで動かしたが逆も然り。
ECMAscriptで不可能/非効率的/時間のかかる処理をC/C++に任せる。
例題として素数演算を再度取り上げる。
% cat prime.js // prime.js // Pure Ecmascript version of low level helper function primeCheckEcmascript(val, limit) { for (var i = 2; i <= limit; i++) { if ((val % i) == 0) { return false; } } return true; } // Select available helper at load time var primeCheckHelper = (this.primeCheckNative || primeCheckEcmascript); // Check 'val' for primality function primeCheck(val) { if (val == 1 || val == 2) { return true; } var limit = Math.ceil(Math.sqrt(val)); while (limit * limit < val) { limit += 1; } return primeCheckHelper(val, limit); } // Find primes below one million ending in '9999'. function primeTest() { var res = []; print('Have native helper: ' + (primeCheckHelper !== primeCheckEcmascript)); for (var i = 1; i < 1000000; i++) { if (primeCheck(i) && (i % 10000) == 9999) { res.push(i); } } print(res.join(' ')); }
% cat primecheck.c /* primecheck.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "duktape.h" static duk_ret_t native_prime_check(duk_context *ctx) { int val = duk_require_int(ctx, 0); int lim = duk_require_int(ctx, 1); int i; for (i = 2; i <= lim; i++) { if (val % i == 0) { duk_push_false(ctx); return 1; } } duk_push_true(ctx); return 1; } int main(int argc, const char *argv[]) { duk_context *ctx = NULL; ctx = duk_create_heap_default(); if (!ctx) { printf("Failed to create a Duktape heap.\n"); exit(1); } duk_push_global_object(ctx); duk_push_c_function(ctx, native_prime_check, 2 /*nargs*/); duk_put_prop_string(ctx, -2, "primeCheckNative"); if (duk_peval_file(ctx, "prime.js") != 0) { printf("Error: %s\n", duk_safe_to_string(ctx, -1)); goto finished; } duk_pop(ctx); /* ignore result */ duk_get_prop_string(ctx, -1, "primeTest"); if (duk_pcall(ctx, 0) != 0) { printf("Error: %s\n", duk_safe_to_string(ctx, -1)); } duk_pop(ctx); /* ignore result */ finished: duk_destroy_heap(ctx); exit(0); }
処理時間を計測する。*4
% gcc -std=c99 -Isrc/ src/duktape.c primecheck.c -lm && time ./a.out Have native helper: true 49999 59999 79999 139999 179999 199999 239999 289999 329999 379999 389999 409999 419999 529999 599999 619999 659999 679999 769999 799999 839999 989999 ./a.out 4.20s user 0.01s system 98% cpu 4.276 total
では、C/C++のヘルパを無効にする。
// Select available helper at load time var primeCheckHelper = primeCheckEcmascript;
処理時間を計測する。
% gcc -std=c99 -Isrc/ src/duktape.c primecheck.c -lm && time ./a.out Have native helper: false 49999 59999 79999 139999 179999 199999 239999 289999 329999 379999 389999 409999 419999 529999 599999 619999 659999 679999 769999 799999 839999 989999 ./a.out 15.14s user 0.04s system 98% cpu 15.449 total
ほとんどの実行時間が素数判定に費やされるため、C/C++に任せる事でECMAScriptに比べて高速な処理が可能である。
総括
とても便利。しかしCライブラリなので、ラッピングしたい。
それと結果的に内容がざっくりとした翻訳エントリになってしまった気が、しないでもない。