この記事は、JavaScript全置換(正規表現使わない)の速度比較 - Qiita の続きです。
続き
先の記事のコメント欄で、@mpywさんに、教えていただきました。
コストとしては,漠然としたイメージですが
機械語レベルでのループ処理: 1
正規表現のコンパイル処理: 10
VM・インタプリタレベルでのループ処理: 100
のような感じで考えていいと思います。コンパイラ系では普通にループ処理を書く → 1
正規表現を書く → 1+10=11
となるため前者のほうが速く,VM・インタプリタ系では普通にループ処理を書く → 100
正規表現を書く → 1+10=11
となるため後者のほうが速いイメージでしょうか。
まぢかよ!正規表現の方がはやいなんて!!!びっくりだぜ!
ということで、積極的に正規表現を使っていくことにしました。
あ、嘘です。可読性が低いので、正規表現使いたくないです。Perlとか思い出してしまいます。あまり使ったことないですが。
が、まあ、高速なら使わない手もないと思いまして、前回のreplaceAllを正規表現を使って、実現したく思いました。
replaceAllは汎用的に使いたいので、置き換え前の文字に正規表現が入ってしまうと誤動作してしまうので、正規表現をエスケープしてから置き換えします。
正規表現にとても詳しいわけではないので、正規表現文字のエスケープは次のページを参考にしました。
ソースコード
下記のコードで、前回の replaceAll2 と 今回の replaceAll3 は同じ仕様です。
var replaceAll2 = function (str, before, after) {
return str.split(before).join(after);
};
var replaceAll3 = function (str, before, after) {
return str.replace(new RegExp(before
.replace(/\\/g, '\\\\')
.replace(/\*/g, '\\*')
.replace(/\+/g, '\\+')
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace(/\{/g, '\\{')
.replace(/\}/g, '\\}')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/\[/g, '\\[')
.replace(/\]/g, '\\]')
.replace(/\^/g, '\\^')
.replace(/\$/g, '\\$')
.replace(/\|/g, '\\|')
, 'g'), after);
};
うわぁ...
正規表現エスケープするのに、こんだけリプレース必要なのか....
なんか、1機能で正規表現エスケープとかできないんですかね。
できる方法あったら、誰か教えてくださいな。
で、これをベンチマークしてみました。
仕様が同じようになっている、ということも、check関数で動作確認もしています。
なんか正規表現エスケープで漏れがあったりしたら、コメント欄などで教えてください
全コード
var alert = alert || function (message) {
console.log(message);
};
var main = function (testName) {
'use strict';
var check = function (a, b) {
if (a !== b) {
var message =
'A != B' + '\n' +
'A = ' + a + '\n' +
'B = ' + b;
alert(message);
}
};
var replaceAll2 = function (str, before, after) {
return str.split(before).join(after);
};
var replaceAll3 = function (str, before, after) {
return str.replace(new RegExp(before
.replace(/\\/g, '\\\\')
.replace(/\*/g, '\\*')
.replace(/\+/g, '\\+')
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace(/\{/g, '\\{')
.replace(/\}/g, '\\}')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/\[/g, '\\[')
.replace(/\]/g, '\\]')
.replace(/\^/g, '\\^')
.replace(/\$/g, '\\$')
.replace(/\|/g, '\\|')
, 'g'), after);
};
var test_replaceAll = function (replaceAll) {
check('AAABBBAAA', replaceAll('123BBB123', '123', 'AAA'));
check('AAAABBBBBBBAAAA',
replaceAll('AAAAAAABBBBBBBAAAAAAA', 'AA', 'A'));
check('C:_Temp_Test',
replaceAll('C:\\Temp\\Test', '\\', '_'));
check('C:_Temp_Test',
replaceAll('C:*Temp*Test', '*', '_'));
check('C:_Temp_Test',
replaceAll('C:+Temp+Test', '+', '_'));
check('C:_Temp_Test',
replaceAll('C:.Temp.Test', '.', '_'));
check('C:_Temp_Test',
replaceAll('C:?Temp?Test', '?', '_'));
check('C:_Temp_Test',
replaceAll('C:{Temp{Test', '{', '_'));
check('C:_Temp_Test',
replaceAll('C:}Temp}Test', '}', '_'));
check('C:_Temp_Test',
replaceAll('C:(Temp(Test', '(', '_'));
check('C:_Temp_Test',
replaceAll('C:)Temp)Test', ')', '_'));
check('C:_Temp_Test',
replaceAll('C:[Temp[Test', '[', '_'));
check('C:_Temp_Test',
replaceAll('C:]Temp]Test', ']', '_'));
check('C:_Temp_Test',
replaceAll('C:^Temp^Test', '^', '_'));
check('C:_Temp_Test',
replaceAll('C:$Temp$Test', '$', '_'));
check('C:_Temp_Test',
replaceAll('C:|Temp|Test', '|', '_'));
check('C:_Temp_Test',
replaceAll('C:-Temp-Test', '-', '_'));
check('C:_Temp_Test',
replaceAll('C:/Temp/Test', '/', '_'));
};
var test_replaceAll2 = function () {
test_replaceAll(replaceAll2);
};
var test_replaceAll3 = function () {
test_replaceAll(replaceAll3);
};
test_replaceAll(replaceAll2);
test_replaceAll(replaceAll3);
var benchMark = function (loopCount, f) {
var
i, max,
startTime, endTime;
startTime = new Date();
for (var i = 1, max = loopCount - 1; i <= max; i+=1) {
f();
}
endTime = new Date();
return endTime - startTime;
};
alert(benchMark(10000, test_replaceAll2));
alert(benchMark(10000, test_replaceAll3));
alert('test finish ' + testName);
}
if (typeof module !== 'undefined') {
module.exports = main;
}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<script src="script.js"></script>
<script>
var alert = function (message) {
console.log(message);
};
window.addEventListener("load",function(eve){
main('load');
},false);
</script>
</head>
<body>
<form name="form01">
<table class="table02">
<tr>
<td>
<input type="button" value="Test01" onclick="main('test01')"
style="width:150px;">
</td>
</tr>
<tr>
<td>
<input type="button" value="Test02" onclick="main('test02')"
style="width:150px;">
</td>
</tr>
</table>
</form>
</body>
</html>
var main = require('./script.js');
main('node.js');
node call_script_node.js
pause
<?xml version="1.0" encoding="shift-jis" ?>
<job>
<script language="JavaScript" src=".\script.js"></script>
<script language="JavaScript">
<![CDATA[
var alert = function (message) {
WScript.Echo(message);
}
main('WSH_wsf');
]]>
</script>
</job>
ファイルはそれぞれ、次の通りです
-
script.js
スクリプト本体
-
index.html
実行用HTML
-
call_script_node.js
node.js起動用。
> node call_script_node.js というコマンドで script.js の内容が実行されます。
-
run_node_script.bat
Windowsでファイルダブルクリックでnode.jsで実行するためのbatファイル
-
run_wsh_script.wsf
Windowsで、WSH の JScript で動作させるためのwsfファイル
このようになります。
いろいろな環境で動作確認できるようにしました。
ベンチマーク結果
で、ベンチマークの結果は....
もう、数値でお知らせするまでもなく、
replaceAll2 早い
replaceAll3 3倍から5倍遅い
という結果でした。どの環境でもです。
ということで、おとなしく replaceAll2 を使った方がいいですよ。
ベンチマーク関数
ソースコードに含まれているように、ベンチマーク関数を作りましたが、引数がない関数しかベンチマークできないので、引数を渡すことがしたいな。と思いまして、下記の場所で聞いてます。
誰か、すごい人がいたら、ベンチマーク関数がもっとよくなりそうなので、リンク先を参考にしてください。
JavaScript ベンチマーク関数などで実行する関数と引数を渡して動作させたい - スタック・オーバーフロー
現場から、「手間ばっかりかかって、みのりのなかったレポート」は、以上です。