JavaScript重箱の隅  新JavaScript入門  JavaScript,Neo-Generation  掲示板  表紙
11. 文字列(3)  13. 配列(1) 
JavaScript 重箱の隅
Written 5/19/02
文字列(4)
ここでは文字列の処理を速くする例をいくつか挙げてみましょう。
1e-6 <= n < 1 なる n を指数表示するプログラムを考えます。
例えば、0.0012345という数値があったとして、 3文字目から0でない文字を探して、 その文字("1")と残りの文字("2345")から小数部("1.2345")を作り、 文字が見つかった位置(pos=4)で指数部("e-3")を求めます。
    //ループ1
    var start_t = new Date();
    var str;
    var m = 0.000001;
    for(var j = 200000; j; j--) {
        str = "" + j * m;
        for(var pos = 2; ; pos++) {
            if(str.charAt(pos) != "0") {
                str = str.charAt(pos) + "."
                        + str.substring(pos + 1) + "e" + (1 - pos);
                break;
            }
        }
    }
    return (new Date() - start_t) / 1000;
    
    //ループ2
    var start_t = new Date();
    var str;
    var m = 0.000001;
    var reg = /([1-9])(\d+)/;
    for(var j = 200000; j; j--) {
        str = "" + j * m;
        var pos = str.search(reg);
        str = RegExp.$1 + "." + RegExp.$2 + "e" + (1 - pos);
    }
    return (new Date() - start_t) / 1000;
    
    scripts/bench063a.js
    scripts/bench063b.js
    t0 : 22083817.142192855
    ループ1 : 3.9
    ループ2 : 3.35
    ループ1 - ループ2 : 0.55 - 0.56
 
正規表現を使ったほうはサブマッチした文字列を小数部に使っています。
しかし、あまり速くならないですね。
文字列の結合だけで1秒くらいかかるので、 これを減らすように replace を使ってみましょう。
    //ループ3
    var start_t = new Date();
    var str;
    var m = 0.000001;
    var reg_pos = /[1-9]/;
    var reg = /^[0\.]+([1-9])(\d+)/;
    for(var j = 200000; j; j--) {
        str = "" + j * m;
        var pos = str.search(reg_pos);
        str = str.replace(reg, "$1.$2") + "e" + (1 - pos);
    }
    return (new Date() - start_t) / 1000;
    scripts/bench064a.js
    scripts/bench064b.js
    t0 : -1.743355688092649
    ループ2 : 3.3
    ループ3 : 3.32
    かかった時間に差があるとはいえない
 
これだけ違うコードなのに同じというのは偶然というか。 環境によって違うんでしょうけど。
字句解析などで文字を1つずつ見ていくとき、 charCodeAt がかなり有効です。
次の例は自然数の足し算を表す文字列を評価するプログラムです。
  
テキストボックスに"1+3+99"などと入れてボタンを押すと、 計算結果が返ってきます。 構文エラーだと0が返ってきます。
    //ループ4
    var str = "1+23+456";
    for(var j = 20000; j; j--)
        add(str);
    
    function add(str) {
        var len = str.length;
        var mode = 0;
        var value = 0;
        var tmp;
        for(var i = 0; i < len; i++) {
            var c = str.charAt(i);
            if(mode == 0) {
                switch(c) {
                case '1': case '2': case '3': case '4': case '5':
                case '6': case '7': case '8': case '9':
                    tmp = c - 0;
                    mode = 1;
                    break;
                default:
                    return 0;
                }
            }
            else {
                switch(c) {
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    tmp = tmp * 10 + (c - 0);
                    break;
                case '+':
                    value += tmp;
                    mode = 0;
                    break;
                default:
                    return 0;
                }
            }
        }
        
        if(mode == 0)
            return 0;
        return value + tmp;
    }
    
    //ループ5
    var str = "1+23+456";
    for(var j = 20000; j; j--)
        add2(str);
    
    function add2(str) {
        ...
        for(var i = 0; i < len; i++) {
            var c = str.charCodeAt(i);
            ...
            switch(c) {
            case 48: case 49: case 50: case 51: case 52:
            case 53: case 54: case 55: case 56: case 57:
                tmp = tmp * 10 + c - 48;
                break;
            case 43:        //'+'
            ...
    }
    scripts/bench059a.js
    scripts/bench059b.js
    t0 : 34.108932228576535
    ループ4 : 0.91
    ループ5 : 0.55
    ループ4 - ループ5 : 0.34 - 0.38
 
ここで switch を使うのはどうかという話もありますが、 数値に直して処理すると速いということです。
ただし、charCodeAt が使えるのはJScript5以上なので、 条件コンパイルを使いましょう。
first, prev, next, exit