Rokiのチラ裏

学生による学習のログ

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*."    
&#34;I like <b>Sam &#38; Max</b>.&#34;

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ライブラリなので、ラッピングしたい。
それと結果的に内容がざっくりとした翻訳エントリになってしまった気が、しないでもない。

参照
Duktape Programmer's Guide

*1:エラトステネスの篩

*2:std::regexや生文字リテラルなど、C++11からは正規表現が柔軟に記述できるようになっているが、charCodeAtのような指定位置の文字の文字コードを取得できる機能C/C++標準ライブラリには存在しない。glibなどの他ライブラリを用いる事で同等の機能を実装する事は可能。unicode - C++ equivalent of JS .charCodeAt() - Stack Overflow

*3:標準ライブラリには搭載されていないという意味で基本的にとしている

*4:当然だが一概にこのタイムが計測されるわけではない