4

関連する質問 に関するKonrad Rudolph からのコメントに促されて、F# での正規表現のパフォーマンスをベンチマークする次のプログラムを作成しました。

open System.Text.RegularExpressions
let str = System.IO.File.ReadAllText "C:\\Users\\Jon\\Documents\\pg10.txt"
let re = System.IO.File.ReadAllText "C:\\Users\\Jon\\Documents\\re.txt"
for _ in 1..3 do
  let timer = System.Diagnostics.Stopwatch.StartNew()
  let re = Regex(re, RegexOptions.Compiled)
  let res = Array.Parallel.init 4 (fun _ -> re.Split str |> Seq.sumBy (fun m -> m.Length))
  printfn "%A %fs" res timer.Elapsed.TotalSeconds

および C++ での同等物:

#include "stdafx.h"

#include <windows.h>
#include <regex>
#include <vector>
#include <string>
#include <fstream>
#include <cstdio>
#include <codecvt>

using namespace std;

wstring load(wstring filename) {
    const locale empty_locale = locale::empty();
    typedef codecvt_utf8<wchar_t> converter_type;
    const converter_type* converter = new converter_type;
    const locale utf8_locale = locale(empty_locale, converter);
    wifstream in(filename);
    wstring contents;
    if (in)
    {
        in.seekg(0, ios::end);
        contents.resize(in.tellg());
        in.seekg(0, ios::beg);
        in.read(&contents[0], contents.size());
        in.close();
    }
    return(contents);
}

int count(const wstring &re, const wstring &s){
    static const wregex rsplit(re);
    auto rit = wsregex_token_iterator(s.begin(), s.end(), rsplit, -1);
    auto rend = wsregex_token_iterator();
    int count=0;
    for (auto it=rit; it!=rend; ++it)
        count += it->length();
    return count;
}

int _tmain(int argc, _TCHAR* argv[])
{
    wstring str = load(L"pg10.txt");
    wstring re = load(L"re.txt");

    __int64 freq, tStart, tStop;
    unsigned long TimeDiff;
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
    QueryPerformanceCounter((LARGE_INTEGER *)&tStart);

    vector<int> res(4);

#pragma omp parallel num_threads(4)
    for(auto i=0; i<res.size(); ++i)
        res[i] = count(re, str);

    QueryPerformanceCounter((LARGE_INTEGER *)&tStop);
    TimeDiff = (unsigned long)(((tStop - tStart) * 1000000) / freq);
    printf("(%d, %d, %d, %d) %fs\n", res[0], res[1], res[2], res[3], TimeDiff/1e6);
    return 0;
}

どちらのプログラムも、2 つのファイルを Unicode 文字列としてロードし (私は聖書のコピーを使用しています)、重要な Unicode 正規表現を構築し、\w?\w?\w?\w?\w?\w分割された文字列の長さの合計を返す正規表現を使用して文字列を 4 回並列に分割します (割り当てを避けるために)。

64 ビットをターゲットとするリリース ビルドで Visual Studio (C++ に対して MP ​​と OpenMP を有効にして) で両方を実行すると、C++ は 43.5 秒、F# は 3.28 秒かかります (13 倍以上高速)。.NET JIT は正規表現をネイティブ コードにコンパイルするのに対し、C++ stdlib はそれを解釈すると信じているため、これは私を驚かせませんが、ピア レビューが必要です。

私の C++ コードにパフォーマンス バグがありますか、それともコンパイルされた正規表現と解釈された正規表現の結果ですか?

EDIT : Billy ONeal は、.NET の解釈が異なる可能性があることを指摘した\wため、新しい正規表現で明示的にしました。

[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]

これにより、実際に .NET コードが大幅に高速になり (C++ も同じ)、F# の時間が 3.28 秒から 2.38 秒に短縮されます (17 倍以上高速)。

4

1 に答える 1

11

これらのベンチマークは実際には比較できません。C++ と .NET は完全に異なる正規表現言語 (ECMAScript と Perl) を実装し、完全に異なる正規表現エンジンを使用しています。.NET は (私の理解では) ここでGRETA プロジェクトの恩恵を受けています。このプロジェクトは、何年にもわたって調整されてきた非常に素晴らしい正規表現エンジンを作成しました。比較すると、 C++std::regexは最近追加されたものです (少なくとも MSVC++ では、非標準の型__int64と友人を考慮して使用していると思います)。

ここで、GRETA とより成熟したstd::regex実装の比較を確認できます(ただし、このテストは Visual Studio 2003 で行われました)。boost::regex

また、正規表現のパフォーマンスは、ソース文字列と正規表現に大きく依存することに注意してください。一部の正規表現エンジンは、正規表現の解析に多くの時間を費やして、より多くのソース テキストを高速に処理します。大量のテキストを解析する場合にのみ意味のあるトレードオフです。一部の正規表現エンジンは、一致を作成するのに比較的コストがかかるため、スキャン速度を犠牲にしています (したがって、一致の数が影響します)。ここには膨大な数のトレードオフがあります。1 組の入力が、実際に話を曇らせます。

したがって、質問にもっと明確に答えるために、この種のバリエーションは、コンパイルされているか解釈されているかを問わず、正規表現エンジン全体で正常です。上記のブーストのテストを見ると、多くの場合、最速の実装と最も遅い実装の違いは何百倍も異なります.17xはユースケースによってはそれほど奇妙ではありません.

于 2013-11-05T20:52:16.903 に答える