Help us understand the problem. What is going on with this article?

JavaScript で全置換(正規表現も使った)の速度比較

More than 3 years have passed since last update.

この記事は、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関数で動作確認もしています。
なんか正規表現エスケープで漏れがあったりしたら、コメント欄などで教えてください

全コード

script.js
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;
}

index.html
<!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>

call_script_node.js
var main = require('./script.js');
main('node.js');
run_node_script.bat
node call_script_node.js
pause
run_wsh_script.wsf
<?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 ベンチマーク関数などで実行する関数と引数を渡して動作させたい - スタック・オーバーフロー

Qiitaのすごい人が、コメント欄で教えてくれてもとっても助かりますが、これはマルチポストじゃないですよ。と。

現場から、「手間ばっかりかかって、みのりのなかったレポート」は、以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした