質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

意見交換

クローズ

12回答

2686閲覧

正規表現ライブラリの機能比較とベンチマーク

退会済みユーザー

退会済みユーザー

総合スコア0

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

1クリップ

投稿2023/03/23 06:25

編集2023/03/23 06:29

1

1

テーマ、知りたいこと

C++で使えるUnicode対応した正規表現ライブラリでこれいいよ!みたいなのとか、いいベンチマークを知ってる人がいたら教えてください。

背景、状況

なんとなく普段何気なく使っている正規表現って実は遅いのでは?と思って気になって調べてみました。
以下はLF(\n)、半角空白、全角空白の3文字を消すというだけの処理をpythonでreplaceを3つ並べたものと、正規表現で実施したものを比較しています。

python

1class SubRepl: 2 def __init__(self, callback): 3 self.found = False 4 self.callback = callback 5 def get_callback(self): 6 def real_callback(m): 7 self.found = True 8 return self.callback(m) 9 return real_callback 10 11import re 12repl = SubRepl(lambda m: '') 13regex = re.compile('[\n  ]') 14callback = repl.get_callback() 15 16def hoge_normal(str): 17 return str.replace('\n','').replace(' ', '').replace(' ', '') 18def hoge_regexp(str): 19 return regex.sub('', str) 20def hoge_regexp2(str): 21 return regex.sub(callback, str) 22 23import timeit 24print(timeit.timeit(lambda: hoge_normal('hoge\nfuga\npiyo'))) 25print(timeit.timeit(lambda: hoge_regexp('hoge\nfuga\npiyo'))) 26print(timeit.timeit(lambda: hoge_regexp2('hoge\nfuga\npiyo'))) 27 28# 出力例) 29# 0.36671502102399245 30# 0.8715399430366233 31# 1.347482957993634

hoge_regexp2はsubのrepl部分に関数を使ってさらに重たくなっちゃってますが(マッチしたのかどうかを判定するため。これの速度影響を調べようとしたのが事の発端)、問題は最初の2つの比較で、正規表現思ったより遅いなという印象でした。パターンマッチする文字列の数が多くなるほど正規表現の方が有利なのは分かるのですが、ではどれくらいの数だとどちらがどの程度速い/遅いのかはちょっと気になります。

ただ、pythonでやってたのではよく分からないので、C++でやるとどうなるのか?を見てみたのです。こちらはさらに厄介で、C++標準の正規表現std::regexがstd::wstringでしかunicode対応できないと分かり、じゃあ遅いのを我慢してstd::stringを都度std::wstringにしようかと考えたら<codecvt>がC++17で非推奨になってて詰んでました。std::stringをutf-8に限定し、utf-8<->utf16(be/le)/utf-8<->utf32(be/le)だけどっかから拾ってくれば比較的プラットフォーム依存を抑えて実装出来そうですが、もう後回しにして、utf-8を直接扱える正規表現ライブラリ
を探すことにしてみました。

調べたところ、

  • pcre2
  • re2
  • boost::regex/xpressive

あたりが使えそうでした。とりあえずうちの環境でインストールせずに使えそうな、pcre2を使ってみたところ、

c++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7 8#define PCRE2_CODE_UNIT_WIDTH 8 9#include <pcre2.h> 10 11using namespace std; 12using namespace chrono; 13 14template<typename Func> 15auto measure(Func func, size_t count = 1000000) { 16 auto begin = high_resolution_clock::now(); 17 for (;count > 0; --count) func(); 18 auto end = high_resolution_clock::now(); 19 return duration_cast<milliseconds>(end - begin).count(); 20} 21 22string replace_normal(const string& str) { 23 static const array<string, 3> search_strings{"\n", " ", " "}; 24 string result(str); 25 for (const auto& s: search_strings) { 26 string::size_type idx = 0; 27 string::size_type pos = 0; 28 while (idx != string::npos) { 29 idx = result.find(s, pos); 30 if (idx != string::npos) { 31 result.erase(idx, s.length()); 32 pos = idx; 33 } 34 } 35 } 36 return result; 37} 38 39struct pcre2_wrap { 40 int errorcode = 0; 41 string errormessage; 42 PCRE2_SIZE erroroffset = 0; 43 pcre2_code* re = NULL; 44 pcre2_match_data *match_data = NULL; 45 int rc = 0; 46 47 bool compile(const string& pattern, uint32_t option) { 48 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 49 if (re == NULL) { 50 vector<PCRE2_UCHAR> buffer(256); 51 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 52 ostringstream sstream; 53 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 54 errormessage = sstream.str(); 55 } else { 56 match_data = pcre2_match_data_create_from_pattern(re, NULL); 57 } 58 return re != NULL; 59 } 60 bool match(const string& str, PCRE2_SIZE pos) { 61 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, match_data, NULL); 62 return rc >= 0; 63 } 64 pcre2_wrap() {} 65 pcre2_wrap(const string& pattern, uint32_t option) {compile(pattern, option);} 66 ~pcre2_wrap() { 67 pcre2_match_data_free(match_data); 68 match_data = NULL; 69 pcre2_code_free(re); 70 re = NULL; 71 } 72}; 73 74string replace_regexp(const string& str) { 75 static pcre2_wrap re("[\n  ]", 0); 76 string result = str; 77 PCRE2_SIZE pos = 0; 78 while (re.match(result, pos)) { 79 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(re.match_data); 80 //cout << "Match succeeded at offset " << (int)ovector[0] << '-' << (int)ovector[1] << endl; 81 result.erase(ovector[0], ovector[1] - ovector[0]); 82 pos = ovector[0]; 83 } 84 return result; 85} 86 87int main() { 88 cout << replace_normal("hoge fuga piyo") << endl; 89 cout << (double)(measure([]{replace_normal("hoge\nfuga\npiyo");})) / 1000 << endl; 90 //cout << (double)(measure([]{replace_normal("hoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\n");})) / 1000 << endl; 91 cout << replace_regexp("hoge fuga piyo") << endl; 92 cout << (double)(measure([]{replace_regexp("hoge\nfuga\npiyo");})) / 1000 << endl; 93 //cout << (double)(measure([]{replace_regexp("hoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\nhoge\nfuga\npiyo\n");})) / 1000 << endl; 94 return 0; 95} 96// コンパイル例) 97// g++ -g -Wall -O3 replace_vs_regex.cpp -lpcre2-8 -o replace_vs_regex 98// 出力例) 99// hogefugapiyo 100// 0.091 101// hogefugapiyo 102// 0.414

こんな感じになりました。遅いな…と。(上のコードは実用に耐えないコードなので注意)

私がやったからなのかもしれないと、ベンチマークを調べたところ、あまり新しいのが見つかりませんでした。Unicode対応した正規表現ライブラリでこれいいよ!みたいなのとか、いいベンチマークを知ってる人がいたら教えてください。嘘か真かRustやNimでいい結果が出てるベンチマークも見たので、この言語なら!みたいのでもいいですよ。自由に語ってください。

※片手間にやってることなので、そんな急いでません

tatsu99👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答12

#1

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/03/28 22:44

質問文は3日経つと編集できないそうなので、その後の進捗をこちらに追記します。

  • 既存コードの多少の見直し
  • 元の文字列を3種類全て含むように変更
  • 文字列長の影響を見るため5倍長い文字列も調査
  • string <-> wstringの変換コードを拾ってきてstd::regexも計測

結果

置換内容: s/[\n  ]//g

入力文字列"hoge fuga piyo\n"5 x "hoge fuga piyo\n"
Python3連replace0.7621.321
Python正規表現0.8813.282
C++3連replace0.2760.753
C++正規表現(pcre2)0.8603.860
C++正規表現(std::regex + wstring経由)3.92418.058

現状C++正規表現が(私の実装力により)pythonより遅くなるという屈辱の結果になっています。

引き続きベンチマークや関連情報を募集します。

TODO:

  • pythonのエンジンが何なのかの調査
  • wstring変換がない場合の速度計測
  • wstring変換自体の速度計測
  • re2についての同様の調査

コード

python

1class SubRepl: 2 def __init__(self, callback): 3 self.found = False 4 self.callback = callback 5 def get_callback(self): 6 def real_callback(m): 7 self.found = True 8 return self.callback(m) 9 return real_callback 10 11import re 12repl = SubRepl(lambda m: '') 13regex = re.compile('[\n  ]') 14callback = repl.get_callback() 15 16def hoge_normal(str): 17 return str.replace('\n','').replace(' ', '').replace(' ', '') 18def hoge_regexp(str): 19 return regex.sub('', str) 20def hoge_regexp2(str): 21 return regex.sub(callback, str) 22 23import timeit 24print(timeit.timeit(lambda: hoge_normal('hoge fuga piyo\n'))) 25print(timeit.timeit(lambda: hoge_regexp('hoge fuga piyo\n'))) 26print(timeit.timeit(lambda: hoge_regexp2('hoge fuga piyo\n'))) 27print(timeit.timeit(lambda: hoge_normal('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 28print(timeit.timeit(lambda: hoge_regexp('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 29print(timeit.timeit(lambda: hoge_regexp2('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 30# 出力例 31# 0.7618313769999077 32# 0.881146389998321 33# 1.5141964299982646 34# 1.3207860059992527 35# 3.2819478820019867 36# 5.972708930999943

c++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7#include <functional> 8#include <numeric> 9#include <type_traits> 10#include <regex> 11#include <cwchar> 12#define PCRE2_CODE_UNIT_WIDTH 8 13#include <pcre2.h> 14// https://github.com/ww898/utf-cpp/tree/v2.2.2 15#include "ww898/utf_converters.hpp" 16#include "ww898/utf_sizes.hpp" 17using namespace std; 18using namespace chrono; 19using namespace ww898::utf; 20// func(args...)をcount回実行してかかった時間[ms]を返す 21template<typename Func, typename ...Args> 22auto measure(Func func, size_t count = 1000000, Args... args) { 23 auto begin = high_resolution_clock::now(); 24 for (;count > 0; --count) func(args...); 25 auto end = high_resolution_clock::now(); 26 return duration_cast<milliseconds>(end - begin).count(); 27} 28// 引数リストに渡されたものを要素としたstd::arrayを返す 29template<typename T, typename... Tail> 30auto make_array(T head, Tail... tail) -> array<T, 1 + sizeof...(Tail)> { 31 array<T, 1 + sizeof...(Tail)> result = { head, tail ... }; 32 return result; 33} 34// strを走査し、patで指定された部分文字列をreplで指定した文字列に置換したものを返す 35string replace_string(const string& str, const string& pat, const string& repl) { 36 string result(str); 37 string::size_type idx = 0; 38 string::size_type pos = 0; 39 while ((idx = result.find(pat, pos)) != string::npos) { 40 result.erase(idx, pat.length()); 41 result.insert(idx, repl); 42 pos = idx + repl.length(); 43 } 44 return result; 45} 46// strから改行と半角/全角スペースをreplに単純置換した文字列を返す 47string replace_normal(const string& str, const string& repl) { 48 static const auto search_strings = make_array<string>("\n", " ", " "); 49 string result(str); 50 for (const auto& s: search_strings) { 51 result = replace_string(result, s, repl); 52 } 53 return result; 54} 55//pcre2のラップクラス 56struct pcre2_wrap { 57 int errorcode = 0; 58 string errormessage; 59 PCRE2_SIZE erroroffset = 0; 60 pcre2_code* re = NULL; 61 struct match_data { 62 pcre2_match_data *m = NULL; 63 // match_dataからi番目のグループを返す 64 const PCRE2_SIZE* get_ovector_pointer(PCRE2_SIZE i) {return pcre2_get_ovector_pointer(m) + i;} 65 match_data(const pcre2_wrap& re) {m = pcre2_match_data_create_from_pattern(re.re, NULL);} 66 ~match_data() {pcre2_match_data_free(m); m = NULL;} 67 }; 68 int rc = 0; 69 // 正規表現patternをコンパイルしてreに設定し、結果を返す 70 bool compile(const string& pattern, uint32_t option) { 71 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 72 if (re == NULL) { 73 vector<PCRE2_UCHAR> buffer(256); 74 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 75 ostringstream sstream; 76 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 77 errormessage = sstream.str(); 78 } 79 return re != NULL; 80 } 81 // strのpos番目からreで検索してmに設定し、結果を返す 82 bool match(const string& str, PCRE2_SIZE pos, match_data& m) { 83 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, m.m, NULL); 84 return rc >= 0; 85 } 86 pcre2_wrap() {} 87 pcre2_wrap(const string& pattern, uint32_t option) {compile(pattern, option);} 88 ~pcre2_wrap() { 89 pcre2_code_free(re); 90 re = NULL; 91 } 92}; 93// pcre2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 94string replace_regexp_pcre2(const string& str, const string& repl) { 95 static pcre2_wrap re("[\n  ]", 0); 96 static pcre2_wrap::match_data m(re); 97 string result = str; 98 PCRE2_SIZE pos = 0; 99 while (re.match(result, pos, m)) { 100 const PCRE2_SIZE *ovector = m.get_ovector_pointer(0); 101 result.erase(ovector[0], ovector[1] - ovector[0]); 102 result.insert(ovector[0], repl); 103 pos = ovector[0] + repl.length(); 104 } 105 return result; 106} 107// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 108string replace_regexp_std(const string& str, const string& repl) { 109 static wregex re(L"[\n  ]"); 110 return conv<char>(regex_replace(convz<wchar_t>(str.c_str()), re, L"")); 111} 112// LFとTABのみ制御文字をエスケープし、二重引用符で囲った文字列を返す 113string express_string(const string& str) { 114 static const auto conv_pairs = make_array( 115 make_pair("\t", "\\t"), 116 make_pair("\n", "\\n")); 117 static const string double_quote{"\""}; 118 string result{str}; 119 result = double_quote + 120 accumulate( 121 conv_pairs.begin(), 122 conv_pairs.end(), 123 result, 124 [](auto total, auto e){ 125 return replace_string(total, e.first, e.second); 126 }) + 127 double_quote; 128 return result; 129} 130// strをN回繰り返した(+で結合した)ものを返す(SFINAE) 131template<int N, typename T> 132static auto repeat(const T& str) -> typename std::enable_if<(N==1),T>::type{ 133 return str; 134} 135template<int N, typename T> 136static auto repeat(const T& str) -> typename std::enable_if<(N>1),T>::type{ 137 return repeat<N-1, T>(str) + str; 138} 139// メイン 140int main() { 141 const string input = "hoge fuga piyo\n"; 142 const string rep_n_times = repeat<5>(input); 143 const auto print_title = [](auto& os, const auto& type, const auto& input, const auto& output) { 144 os << type << ": " << express_string(input) << " -> " << express_string(output) << endl; 145 }; 146 const auto print_time = [](auto& os, auto ms) { 147 os << "\ttime: " << static_cast<double>(ms) / 1000 << endl; 148 }; 149 auto inputs = make_array(input, rep_n_times); 150 auto replace_methods = make_array( 151 make_pair("normal", replace_normal), 152 make_pair("regexp(pcre2)", replace_regexp_pcre2), 153 make_pair("regexp(std)", replace_regexp_std) 154 ); 155 for (const auto& method: replace_methods) { 156 for (const auto& input: inputs) { 157 print_title(cout, method.first, input, method.second(input, "")); 158 print_time(cout, measure(method.second, 1000000, input, "")); 159 } 160 } 161 return 0; 162} 163// コンパイル例) 164// g++ -g -Wall -I./external/include -O3 replace_vs_regex.cpp -lpcre2-8 -o replace_vs_regex 165// 出力例) 166// normal: "hoge fuga piyo\n" -> "hogefugapiyo" 167// time: 0.276 168// normal: "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 169// time: 0.753 170// regexp(pcre2): "hoge fuga piyo\n" -> "hogefugapiyo" 171// time: 0.86 172// regexp(pcre2): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 173// time: 3.86 174// regexp(std): "hoge fuga piyo\n" -> "hogefugapiyo" 175// time: 3.924 176// regexp(std): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 177// time: 18.058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/03/29 11:22

編集2023/03/31 21:03

その後の進捗です。前回TODOのうち以下を実施。

  • wstring変換がない場合の速度計測(std::wregexを使用せず、std::regexそのままの計測)
  • wstring変換自体の速度計測
  • re2についての同様の調査

結果

入力文字列"hoge fuga piyo\n"5 x "hoge fuga piyo\n"
Python3連replace(流用)0.7621.321
Python正規表現(流用)0.8813.282
C++3連replace0.2440.725
C++正規表現(pcre2)0.8043.658
C++正規表現(std::wregex + wstring経由)3.74817.363
C++正規表現(std::regex)1.3116.157
C++ →wstring→string0.2290.789
C++正規表現(re2)0.8323.714

※Win10上VirtualBox7.0.4上Ubuntu20.04 amd64 AMD Ryzen 5 1400

分かったこと

  • pcre2とre2は似たような速度
  • std::regexは ASCIIだけではなく、utf-8を正しく扱えた(locale呼んでないけど) 置換した文字列が""だったために上手く動いてるように見えただけで、1文字1バイトで処理されていた
  • wstring変換が遅いのかと思ったが、std::wregexが遅いようだ

TODO

  • pythonのエンジンが何なのかの調査
  • Windowsでの計測
  • boostの計測

募集
引き続きベンチマークや関連情報を募集します。

コード

c++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7#include <functional> 8#include <numeric> 9#include <type_traits> 10#include <regex> 11#include <cwchar> 12#define PCRE2_CODE_UNIT_WIDTH 8 13#include <pcre2.h> 14#include <re2/re2.h> 15// https://github.com/ww898/utf-cpp/tree/v2.2.2 16#include <ww898/utf_converters.hpp> 17#include <ww898/utf_sizes.hpp> 18using namespace std; 19using namespace chrono; 20using namespace ww898::utf; 21// func(args...)をcount回実行してかかった時間[ms]を返す 22template<typename Func, typename ...Args> 23auto measure(Func func, size_t count = 1000000, Args... args) { 24 auto begin = high_resolution_clock::now(); 25 for (;count > 0; --count) func(args...); 26 auto end = high_resolution_clock::now(); 27 return duration_cast<milliseconds>(end - begin).count(); 28} 29// 引数リストに渡されたものを要素としたstd::arrayを返す 30template<typename T, typename... Tail> 31auto make_array(T head, Tail... tail) -> array<T, 1 + sizeof...(Tail)> { 32 array<T, 1 + sizeof...(Tail)> result = { head, tail ... }; 33 return result; 34} 35// strを走査し、patで指定された部分文字列をreplで指定した文字列に置換したものを返す 36string replace_string(const string& str, const string& pat, const string& repl) { 37 string result(str); 38 string::size_type idx = 0; 39 string::size_type pos = 0; 40 while ((idx = result.find(pat, pos)) != string::npos) { 41 result.erase(idx, pat.length()); 42 result.insert(idx, repl); 43 pos = idx + repl.length(); 44 } 45 return result; 46} 47// strから改行と半角/全角スペースをreplに単純置換した文字列を返す 48string replace_normal(const string& str, const string& repl) { 49 static const auto search_strings = make_array<string>("\n", " ", " "); 50 string result(str); 51 for (const auto& s: search_strings) { 52 result = replace_string(result, s, repl); 53 } 54 return result; 55} 56//pcre2のラップクラス 57struct pcre2_wrap { 58 int errorcode = 0; 59 string errormessage; 60 PCRE2_SIZE erroroffset = 0; 61 pcre2_code* re = NULL; 62 struct match_data { 63 pcre2_match_data *m = NULL; 64 // match_dataからi番目のグループを返す 65 const PCRE2_SIZE* get_ovector_pointer(PCRE2_SIZE i) {return pcre2_get_ovector_pointer(m) + i;} 66 match_data(const pcre2_wrap& re) {m = pcre2_match_data_create_from_pattern(re.re, NULL);} 67 ~match_data() {pcre2_match_data_free(m); m = NULL;} 68 }; 69 int rc = 0; 70 // 正規表現patternをコンパイルしてreに設定し、結果を返す 71 bool compile(const string& pattern, uint32_t option) { 72 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 73 if (re == NULL) { 74 vector<PCRE2_UCHAR> buffer(256); 75 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 76 ostringstream sstream; 77 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 78 errormessage = sstream.str(); 79 } 80 return re != NULL; 81 } 82 // strのpos番目からreで検索してmに設定し、結果を返す 83 bool match(const string& str, PCRE2_SIZE pos, match_data& m) { 84 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, m.m, NULL); 85 return rc >= 0; 86 } 87 pcre2_wrap() {} 88 pcre2_wrap(const string& pattern, uint32_t option) {compile(pattern, option);} 89 ~pcre2_wrap() { 90 pcre2_code_free(re); 91 re = NULL; 92 } 93}; 94// pcre2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 95string replace_regexp_pcre2(const string& str, const string& repl) { 96 static pcre2_wrap re("[\n  ]", 0); 97 static pcre2_wrap::match_data m(re); 98 string result = str; 99 PCRE2_SIZE pos = 0; 100 while (re.match(result, pos, m)) { 101 const PCRE2_SIZE *ovector = m.get_ovector_pointer(0); 102 result.erase(ovector[0], ovector[1] - ovector[0]); 103 result.insert(ovector[0], repl); 104 pos = ovector[0] + repl.length(); 105 } 106 return result; 107} 108// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(ただし、utf-cppを使ってstring<->wstringの変換を行う) 109string replace_regexp_std_wstringconv(const string& str, const string& repl) { 110 static wregex re(L"[\n  ]"); 111 return conv<char>(regex_replace(convz<wchar_t>(str.c_str()), re, L"")); 112} 113// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(stringをそのまま使用する。ロケール依存) 114string replace_regexp_std(const string& str, const string& repl) { 115 static regex re("[\n  ]"); 116 return regex_replace(str, re, ""); 117} 118// utf-cppを使ってstrをwstringにしてから、同様にstringに戻して返す 119string to_wstring_to_string(const string& str, const string& repl) { 120 return conv<char>(convz<wchar_t>(str.c_str())); 121} 122// perlで使用されるパターン文字列を許容するオプション付きRE2 123class RE2_with_perl_class { 124 RE2* re_; 125 const string pat_; 126public: 127 RE2_with_perl_class(const string& s): re_(nullptr), pat_(s) {} 128 ~RE2_with_perl_class() {delete re_; re_ = nullptr;} 129 RE2& re() { 130 if (re_ == nullptr) { 131 RE2::Options o; 132 o.set_perl_classes(true); 133 re_ = new RE2(pat_, o); 134 } 135 return *re_; 136 } 137}; 138// re2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 139string replace_regexp_re2(const string& str, const string& repl) { 140 static RE2_with_perl_class re("[\n  ]"); 141 string result(str); 142 RE2::GlobalReplace(&result, re.re(), repl); 143 return result; 144} 145// LFとTABのみ制御文字をエスケープし、二重引用符で囲った文字列を返す 146string express_string(const string& str) { 147 static const auto conv_pairs = make_array( 148 make_pair("\t", "\\t"), 149 make_pair("\n", "\\n")); 150 static const string double_quote{"\""}; 151 string result{str}; 152 result = double_quote + 153 accumulate( 154 conv_pairs.begin(), 155 conv_pairs.end(), 156 result, 157 [](auto total, auto e){ 158 return replace_string(total, e.first, e.second); 159 }) + 160 double_quote; 161 return result; 162} 163// strをN回繰り返した(+で結合した)ものを返す(SFINAE) 164template<int N, typename T> 165static auto repeat(const T& str) -> typename std::enable_if<(N==1),T>::type{ 166 return str; 167} 168template<int N, typename T> 169static auto repeat(const T& str) -> typename std::enable_if<(N>1),T>::type{ 170 return repeat<N-1, T>(str) + str; 171} 172// メイン 173int main() { 174 const string input = "hoge fuga piyo\n"; 175 const string rep_n_times = repeat<5>(input); 176 const auto print_title = [](auto& os, const auto& type, const auto& input, const auto& output) { 177 os << type << ": " << express_string(input) << " -> " << express_string(output) << endl; 178 }; 179 const auto print_time = [](auto& os, auto ms) { 180 os << "\ttime: " << static_cast<double>(ms) / 1000 << endl; 181 }; 182 auto inputs = make_array(input, rep_n_times); 183 auto replace_methods = make_array( 184 make_pair("normal", replace_normal), 185 make_pair("regexp(pcre2)", replace_regexp_pcre2), 186 make_pair("regexp(to_wstring->std::wregex->to_string)", replace_regexp_std_wstringconv), 187 make_pair("regexp(std::regex)", replace_regexp_std), 188 make_pair("to_wstring_to_string", to_wstring_to_string), 189 make_pair("regexp(re2)", replace_regexp_re2) 190 ); 191 for (const auto& method: replace_methods) { 192 for (const auto& input: inputs) { 193 print_title(cout, method.first, input, method.second(input, "")); 194 print_time(cout, measure(method.second, 1000000, input, "")); 195 } 196 } 197 return 0; 198} 199// コンパイル例) 200// g++ -g -Wall -I./external/include -O3 replace_vs_regex.cpp -lpcre2-8 -lre2 -o replace_vs_regex 201// 出力例) 202// normal: "hoge fuga piyo\n" -> "hogefugapiyo" 203// time: 0.244 204// normal: "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 205// time: 0.725 206// regexp(pcre2): "hoge fuga piyo\n" -> "hogefugapiyo" 207// time: 0.804 208// regexp(pcre2): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 209// time: 3.658 210// regexp(to_wstring->std::wregex->to_string): "hoge fuga piyo\n" -> "hogefugapiyo" 211// time: 3.748 212// regexp(to_wstring->std::wregex->to_string): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 213// time: 17.363 214// regexp(std::regex): "hoge fuga piyo\n" -> "hogefugapiyo" 215// time: 1.311 216// regexp(std::regex): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 217// time: 6.157 218// to_wstring_to_string: "hoge fuga piyo\n" -> "hoge fuga piyo\n" 219// time: 0.229 220// to_wstring_to_string: "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" 221// time: 0.789 222// regexp(re2): "hoge fuga piyo\n" -> "hogefugapiyo" 223// time: 0.832 224// regexp(re2): "hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n" -> "hogefugapiyohogefugapiyohogefugapiyohogefugapiyohogefugapiyo" 225// time: 3.714

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/03/31 13:13

編集2023/03/31 13:22

その後の進捗です。前回TODOのうち以下を実施。

  • pythonのエンジンが何なのかの調査
  • Windowsでの計測

実装上の変更

  • WindowsではシステムロケールがシフトJISなので、入力文字列をワイド文字にして、それをutf-8に変換してから処理させるように変更
  • マルチバイトでの置換を空文字列にすると文字単位で処理されているのか分からないので、アンダーバーに変更
  • Windowsでcoutを使うとシステムロケール依存になってしまうので、wcoutに変更し、必要な関数をテンプレートでchar/wchar_tが切り替えられるように変更
  • pcre2のcompile時にPCRE2_UTFが付いていなかったので追加

結果

Pythonの正規表現エンジン(CPythonのre)

pcre2やre2など出来合いのエンジンではなく、python専用のSecret Labs' Regular Expression Engine。Pythonで書かれた正規表現コンパイラと、そのバイトコードを解釈するCで書かれたVMとのこと。
https://softwareengineering.stackexchange.com/questions/410006/in-what-programming-language-is-pythons-regex-module-written-in
https://github.com/python/cpython/blob/main/Lib/re/__init__.py

Windowsを含めた計測

置換内容: s/[\n  ]/_/g

入力文字列/Platform"hoge fuga piyo\n" /Ubuntu5 x "hoge fuga piyo\n" /Ubuntu"hoge fuga piyo\n" /Win105 x "hoge fuga piyo\n" /Win10
Python3連replace0.5000.7860.5650.825
Python正規表現0.9113.1751.0673.706
C++3連replace0.3260.8650.4801.023
C++正規表現(pcre2)0.6613.9470.6443.388
C++正規表現(std::wregex + wstring経由)3.71417.1983.25616.330
C++正規表現(std::regex)1.4686.3664.14020.321
C++ →wstring→string0.2440.7610.3471.750
C++正規表現(re2)0.8373.8071.0514.619

Ubuntu: Win10上VirtualBox7.0.4上Ubuntu20.04 amd64 AMD Ryzen 5 1400/python 3.8.10/gcc 9.4.0/pcre2 10.34/re2 2020-01-01
Win10: 19045.2728 AMD Ryzen 5 1400/python 3.8.5/vc++2019/pcre2 10.34(※)/re2 2020-01-01(※)
(※)cmakeで作ったソリューションからVC++でデフォルトビルドしたx64 Release版
(※)VC++のビルドはマルチスレッドDLLでUNICODE(デフォルト)
(※)Windowsのシステムロケールはコードページ932のまま

分かったこと

  • pythonの正規表現は独自であり、pcre2やre2といった汎用のものではなく、単純に比較できない
  • pcre2とre2はプラットフォームに依らず、似たようなパフォーマンスとなっている
  • re2は安全性を目標としたエンジンとのことで(時間的脆弱性への考慮があるらしい)、同程度の機能/速度なら良さげに見えた
  • WindowsはデフォルトのUNICODEで動かしたが、マルチバイトが遅いのか、VC++付属の標準ライブラリが遅いのか、正規表現のstd::regexが極端に遅かった
  • WindowsのVC++付属std::wregexも速くはない

現時点で有力なのは実績のpcre2、安定性のre2。個人的にはC++ということもありre2一択。

TODO

  • mingw環境での新しめのgccで計測
  • boostの計測
  • 良さげなベンチマーク結果の調査

コード

python

1class SubRepl: 2 def __init__(self, callback): 3 self.found = False 4 self.callback = callback 5 def get_callback(self): 6 def real_callback(m): 7 self.found = True 8 return self.callback(m) 9 return real_callback 10 11import re 12repl = SubRepl(lambda m: '_') 13regex = re.compile('[\n  ]') 14callback = repl.get_callback() 15 16def hoge_normal(str): 17 return str.replace('\n','_').replace(' ', '_').replace(' ', '_') 18def hoge_regexp(str): 19 return regex.sub('_', str) 20def hoge_regexp2(str): 21 return regex.sub(callback, str) 22 23import timeit 24print(timeit.timeit(lambda: hoge_normal('hoge fuga piyo\n'))) 25print(timeit.timeit(lambda: hoge_regexp('hoge fuga piyo\n'))) 26print(timeit.timeit(lambda: hoge_regexp2('hoge fuga piyo\n'))) 27print(timeit.timeit(lambda: hoge_normal('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 28print(timeit.timeit(lambda: hoge_regexp('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 29print(timeit.timeit(lambda: hoge_regexp2('hoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\nhoge fuga piyo\n'))) 30# 出力例 31# 0.5004470729909372 32# 0.9112305690068752 33# 1.458983212010935 34# 0.7863287159998436 35# 3.1747756089898758 36# 5.850148669996997

(続く)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#4

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/03/31 13:23

(続き)

C++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7#include <functional> 8#include <numeric> 9#include <type_traits> 10#include <regex> 11#include <cwchar> 12#define PCRE2_CODE_UNIT_WIDTH 8 13#include "pcre2.h" 14#include <re2/re2.h> 15// https://github.com/ww898/utf-cpp/tree/v2.2.2 16#include <ww898/utf_converters.hpp> 17#include <ww898/utf_sizes.hpp> 18using namespace std; 19using namespace chrono; 20using namespace ww898::utf; 21// func(args...)をcount回実行してかかった時間[ms]を返す 22template<typename Func, typename ...Args> 23auto measure(Func func, size_t count = 1000000, Args... args) { 24 auto begin = high_resolution_clock::now(); 25 for (; count > 0; --count) func(args...); 26 auto end = high_resolution_clock::now(); 27 return duration_cast<milliseconds>(end - begin).count(); 28} 29// 引数リストに渡されたものを要素としたstd::arrayを返す 30template<typename T, typename... Tail> 31auto make_array(T head, Tail... tail)->array<T, 1 + sizeof...(Tail)> { 32 array<T, 1 + sizeof...(Tail)> result = { head, tail ... }; 33 return result; 34} 35// strを走査し、patで指定された部分文字列をreplで指定した文字列に置換したものを返す 36template<typename T> 37T replace_string(const T& str, const T& pat, const T& repl) { 38 T result(str); 39 typename T::size_type idx = 0; 40 typename T::size_type pos = 0; 41 while ((idx = result.find(pat, pos)) != T::npos) { 42 result.erase(idx, pat.length()); 43 result.insert(idx, repl); 44 pos = idx + repl.length(); 45 } 46 return result; 47} 48// strから改行と半角/全角スペースをreplに単純置換した文字列を返す 49string replace_normal(const string& str, const string& repl) { 50 static const auto search_strings = make_array<string>(conv<char>(wstring(L"\n")), conv<char>(wstring(L" ")), conv<char>(wstring(L" "))); 51 string result(str); 52 for (const auto& s : search_strings) { 53 result = replace_string(result, s, repl); 54 } 55 return result; 56} 57//pcre2のラップクラス 58struct pcre2_wrap { 59 int errorcode = 0; 60 string errormessage; 61 PCRE2_SIZE erroroffset = 0; 62 pcre2_code* re = NULL; 63 struct match_data { 64 pcre2_match_data* m = NULL; 65 // match_dataからi番目のグループを返す 66 const PCRE2_SIZE* get_ovector_pointer(PCRE2_SIZE i) { return pcre2_get_ovector_pointer(m) + i; } 67 match_data(const pcre2_wrap& re) { m = pcre2_match_data_create_from_pattern(re.re, NULL); } 68 ~match_data() { pcre2_match_data_free(m); m = NULL; } 69 }; 70 int rc = 0; 71 // 正規表現patternをコンパイルしてreに設定し、結果を返す 72 bool compile(const string& pattern, uint32_t option) { 73 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 74 if (re == NULL) { 75 vector<PCRE2_UCHAR> buffer(256); 76 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 77 ostringstream sstream; 78 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 79 errormessage = sstream.str(); 80 } 81 return re != NULL; 82 } 83 // strのpos番目からreで検索してmに設定し、結果を返す 84 bool match(const string& str, PCRE2_SIZE pos, match_data& m) { 85 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, m.m, NULL); 86 return rc >= 0; 87 } 88 pcre2_wrap() {} 89 pcre2_wrap(const string& pattern, uint32_t option) { compile(pattern, option); } 90 ~pcre2_wrap() { 91 pcre2_code_free(re); 92 re = NULL; 93 } 94}; 95// pcre2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 96string replace_regexp_pcre2(const string& str, const string& repl) { 97 static pcre2_wrap re(conv<char>(wstring(L"[\n  ]")), PCRE2_UTF); 98 static pcre2_wrap::match_data m(re); 99 string result = str; 100 PCRE2_SIZE pos = 0; 101 while (re.match(result, pos, m)) { 102 const PCRE2_SIZE* ovector = m.get_ovector_pointer(0); 103 result.erase(ovector[0], ovector[1] - ovector[0]); 104 result.insert(ovector[0], repl); 105 pos = ovector[0] + repl.length(); 106 } 107 return result; 108} 109// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(ただし、utf-cppを使ってstring<->wstringの変換を行う) 110string replace_regexp_std_wstringconv(const string& str, const string& repl) { 111 static wregex re(L"[\n  ]"); 112 return conv<char>(regex_replace(convz<wchar_t>(str.c_str()), re, convz<wchar_t>(repl.c_str()))); 113} 114// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(stringをそのまま使用する。ロケール依存) 115string replace_regexp_std(const string& str, const string& repl) { 116 static regex re(conv<char>(wstring(L"[\n  ]"))); 117 return regex_replace(str, re, repl); 118} 119// utf-cppを使ってstrをwstringにしてから、同様にstringに戻して返す 120string to_wstring_to_string(const string& str, const string& repl) { 121 return conv<char>(convz<wchar_t>(str.c_str())); 122} 123// perlで使用されるパターン文字列を許容するオプション付きRE2 124class RE2_with_perl_class { 125 RE2* re_; 126 const string pat_; 127public: 128 RE2_with_perl_class(const string& s) : re_(nullptr), pat_(s) {} 129 ~RE2_with_perl_class() { delete re_; re_ = nullptr; } 130 RE2& re() { 131 if (re_ == nullptr) { 132 RE2::Options o; 133 o.set_perl_classes(true); 134 re_ = new RE2(pat_, o); 135 } 136 return *re_; 137 } 138}; 139// re2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 140string replace_regexp_re2(const string& str, const string& repl) { 141 static RE2_with_perl_class re(conv<char>(wstring(L"[\n  ]"))); 142 string result(str); 143 RE2::GlobalReplace(&result, re.re(), repl); 144 return result; 145} 146#define polystring(str, type) std::get<const type *>(std::make_tuple(str, L##str)) 147// LFとTABのみ制御文字をエスケープし、二重引用符で囲った文字列を返す 148template<typename T> 149T express_string(const T& str) { 150 static const auto conv_pairs = make_array( 151 make_pair(polystring("\t", typename T::value_type), polystring("\\t", typename T::value_type)), 152 make_pair(polystring("\n", typename T::value_type), polystring("\\n", typename T::value_type))); 153 static const T quote{ polystring("\"", typename T::value_type) }; 154 T result{ str }; 155 result = quote + 156 accumulate( 157 conv_pairs.begin(), 158 conv_pairs.end(), 159 result, 160 [](const T& total, const pair<T, T>& e) { 161 return replace_string(total, e.first, e.second); 162 }) + 163 quote; 164 return result; 165} 166// strをN回繰り返した(+で結合した)ものを返す(SFINAE) 167template<int N, typename T> 168static auto repeat(const T& str) -> typename std::enable_if<(N == 1), T>::type { 169 return str; 170} 171template<int N, typename T> 172static auto repeat(const T& str) -> typename std::enable_if<(N > 1), T>::type{ 173 return repeat<N - 1, T>(str) + str; 174} 175// メイン 176int main() { 177 setlocale(LC_ALL, ""); 178 const string input = conv<char>(wstring(L"hoge fuga piyo\n")); 179 const string rep_n_times = repeat<5>(input); 180 const auto print_title = [](auto& os, const auto& type, const auto& input, const auto& output) { 181 os << type << ": " << express_string(input) << " -> " << express_string(output) << endl; 182 }; 183 const auto print_time = [](auto& os, auto ms) { 184 os << "\ttime: " << static_cast<double>(ms) / 1000 << endl; 185 }; 186 auto inputs = make_array(input, rep_n_times); 187 auto replace_methods = make_array( 188 make_pair("normal", replace_normal), 189 make_pair("regexp(pcre2)", replace_regexp_pcre2), 190 make_pair("regexp(to_wstring->std::wregex->to_string)", replace_regexp_std_wstringconv), 191 make_pair("regexp(std::regex)", replace_regexp_std), 192 make_pair("to_wstring_to_string", to_wstring_to_string), 193 make_pair("regexp(re2)", replace_regexp_re2) 194 ); 195 for (const auto& method : replace_methods) { 196 for (const auto& input : inputs) { 197#ifdef _WIN32 198 print_title(wcout, method.first, conv<wchar_t>(input), conv<wchar_t>(method.second(input, "_"))); 199 print_time(wcout, measure(method.second, 1000000, input, "_")); 200#else 201 print_title(cout, method.first, input, method.second(input, "_")); 202 print_time(cout, measure(method.second, 1000000, input, "_")); 203#endif 204 } 205 } 206 return 0; 207} 208// コンパイル例) 209// g++ -g -Wall -I./external/include -O3 replace_vs_regex.cpp -lpcre2-8 -lre2 -o replace_vs_regex

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#5

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/02 21:29

その後の進捗です。前回TODOのうち以下を実施。

  • mingw環境での新しめのgccで計測

実装上の変更

  • const指定忘れやpolystring対応忘れなど細かい修正
  • mingw64への対応

結果

計測

置換内容: s/[\n  ]/_/g

入力文字列/Platform"hoge fuga piyo\n" /Ubuntu5 x "hoge fuga piyo\n" /Ubuntu"hoge fuga piyo\n" /Win105 x "hoge fuga piyo\n" /Win10"hoge fuga piyo\n" /Win10(MINGW64)5 x "hoge fuga piyo\n" /Win10(MINGW64)
Python3連replace(流用)0.5000.7860.5650.825--
Python正規表現(流用)0.9113.1751.0673.706--
C++3連replace0.3030.7660.4941.0160.4821.133
C++正規表現(pcre2)0.6263.8730.6723.4460.6733.492
C++正規表現(std::wregex + wstring経由)3.69616.7463.23416.26011.78756.651
C++正規表現(std::regex)1.4696.6854.24320.6642.1679.258
C++ →wstring→string0.2270.7650.3531.7890.3801.341
C++正規表現(re2)0.8473.7740.9964.4501.2395.423

Ubuntu: Win10上VirtualBox7.0.4上Ubuntu20.04 amd64 AMD Ryzen 5 1400/python 3.8.10/gcc 9.4.0/pcre2 10.34/re2 2020-01-01
Win10: 19045.2728 AMD Ryzen 5 1400/python 3.8.5/vc++2019 or MINGW64 gcc12.2/pcre2 10.34(※)/re2 2020-01-01(※)
(※)cmakeで作ったソリューションからでデフォルトビルドしたx64 Release static版(VC++)/ninja+gcc12.2でデフォルトReleaseビルドしたstatic版(MINGW64)
(※)Windowsでビルドするときはpcre2を静的ライブラリにしたので、-DPCRE2_STATICをコンパイルオプションに加えること
(※)VC++のビルドはマルチスレッドDLLでUNICODE(デフォルト)
(※)Windowsのシステムロケールはコードページ932のまま

分かったこと

  • pcre2とプラットフォームに依らず、ほぼ同じ時間になっている
  • re2はプラットフォームに依らず似たような傾向を示すが、時間はubuntu < VC++ < MINGW64のようになっている
  • re2は安全性を目標としたエンジンとのことで(時間的脆弱性への考慮があるらしい)、同程度の機能/速度なら良さげに見えた
  • WindowsはデフォルトのUNICODEで動かしたが、VC++だとstd::regexが3倍ほど、mingwではstd::wregexが3倍ほど時間がかかっていた
  • WindowsのMINGW64のstd::regexも速くはない

現時点で有力なのは実績のpcre2、安定性のre2。個人的にはLinuxならre2、Windowsならpcre2/re2かといった感じ。
極端に遅いのは私のコードのバグの可能性が高いが、std::regexは魅力皆無なので気にしない。

TODO

  • boostの計測
  • 良さげなベンチマーク結果の調査

コード

Pythonは同じコード
(続く)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#6

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/02 21:31

(続き)

C++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7#include <functional> 8#include <numeric> 9#include <type_traits> 10#include <regex> 11#include <cwchar> 12#define PCRE2_CODE_UNIT_WIDTH 8 13#include "pcre2.h" 14#include <re2/re2.h> 15// https://github.com/ww898/utf-cpp/tree/v2.2.2 16#include <ww898/utf_converters.hpp> 17#include <ww898/utf_sizes.hpp> 18using namespace std; 19using namespace chrono; 20using namespace ww898::utf; 21// func(args...)をcount回実行してかかった時間[ms]を返す 22template<typename Func, typename ...Args> 23auto measure(Func func, size_t count = 1000000, Args... args) { 24 auto begin = high_resolution_clock::now(); 25 for (; count > 0; --count) func(args...); 26 auto end = high_resolution_clock::now(); 27 return duration_cast<milliseconds>(end - begin).count(); 28} 29// 引数リストに渡されたものを要素としたstd::arrayを返す 30template<typename T, typename... Tail> 31auto make_array(T head, Tail... tail)->array<T, 1 + sizeof...(Tail)> { 32 array<T, 1 + sizeof...(Tail)> result = { head, tail ... }; 33 return result; 34} 35// strを走査し、patで指定された部分文字列をreplで指定した文字列に置換したものを返す 36template<typename T> 37T replace_string(const T& str, const T& pat, const T& repl) { 38 T result(str); 39 typename T::size_type idx = 0; 40 typename T::size_type pos = 0; 41 while ((idx = result.find(pat, pos)) != T::npos) { 42 result.erase(idx, pat.length()); 43 result.insert(idx, repl); 44 pos = idx + repl.length(); 45 } 46 return result; 47} 48// strから改行と半角/全角スペースをreplに単純置換した文字列を返す 49string replace_normal(const string& str, const string& repl) { 50 static const auto search_strings = make_array<string>(conv<char>(wstring(L"\n")), conv<char>(wstring(L" ")), conv<char>(wstring(L" "))); 51 string result(str); 52 for (const auto& s : search_strings) { 53 result = replace_string(result, s, repl); 54 } 55 return result; 56} 57//pcre2のラップクラス 58struct pcre2_wrap { 59 int errorcode = 0; 60 string errormessage; 61 PCRE2_SIZE erroroffset = 0; 62 pcre2_code* re = NULL; 63 struct match_data { 64 pcre2_match_data* m = NULL; 65 // match_dataからi番目のグループを返す 66 const PCRE2_SIZE* get_ovector_pointer(PCRE2_SIZE i) { return pcre2_get_ovector_pointer(m) + i; } 67 match_data(const pcre2_wrap& re) { m = pcre2_match_data_create_from_pattern(re.re, NULL); } 68 ~match_data() { pcre2_match_data_free(m); m = NULL; } 69 }; 70 int rc = 0; 71 // 正規表現patternをコンパイルしてreに設定し、結果を返す 72 bool compile(const string& pattern, uint32_t option) { 73 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 74 if (re == NULL) { 75 vector<PCRE2_UCHAR> buffer(256); 76 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 77 stringstream sstream; 78 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 79 errormessage = sstream.str(); 80 } 81 return re != NULL; 82 } 83 // strのpos番目からreで検索してmに設定し、結果を返す 84 bool match(const string& str, PCRE2_SIZE pos, match_data& m) { 85 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, m.m, NULL); 86 return rc >= 0; 87 } 88 pcre2_wrap() {} 89 pcre2_wrap(const string& pattern, uint32_t option) { compile(pattern, option); } 90 ~pcre2_wrap() { 91 pcre2_code_free(re); 92 re = NULL; 93 } 94}; 95// pcre2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 96string replace_regexp_pcre2(const string& str, const string& repl) { 97 static pcre2_wrap re(conv<char>(wstring(L"[\n  ]")), PCRE2_UTF); 98 static pcre2_wrap::match_data m(re); 99 string result = str; 100 PCRE2_SIZE pos = 0; 101 while (re.match(result, pos, m)) { 102 const PCRE2_SIZE* ovector = m.get_ovector_pointer(0); 103 result.erase(ovector[0], ovector[1] - ovector[0]); 104 result.insert(ovector[0], repl); 105 pos = ovector[0] + repl.length(); 106 } 107 return result; 108} 109// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(ただし、utf-cppを使ってstring<->wstringの変換を行う) 110string replace_regexp_std_wstringconv(const string& str, const string& repl) { 111 static const wregex re(L"[\n  ]"); 112 return conv<char>(regex_replace(convz<wchar_t>(str.c_str()), re, convz<wchar_t>(repl.c_str()))); 113} 114// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(stringをそのまま使用する。ロケール依存) 115string replace_regexp_std(const string& str, const string& repl) { 116 static const regex re(conv<char>(wstring(L"[\n  ]"))); 117 return regex_replace(str, re, repl); 118} 119// utf-cppを使ってstrをwstringにしてから、同様にstringに戻して返す 120string to_wstring_to_string(const string& str, const string& repl) { 121 return conv<char>(convz<wchar_t>(str.c_str())); 122} 123// perlで使用されるパターン文字列を許容するオプション付きRE2 124class RE2_with_perl_class { 125 RE2* re_; 126 const string pat_; 127public: 128 RE2_with_perl_class(const string& s) : re_(nullptr), pat_(s) {} 129 ~RE2_with_perl_class() { delete re_; re_ = nullptr; } 130 RE2& re() { 131 if (re_ == nullptr) { 132 RE2::Options o; 133 o.set_perl_classes(true); 134 re_ = new RE2(pat_, o); 135 } 136 return *re_; 137 } 138}; 139// re2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 140string replace_regexp_re2(const string& str, const string& repl) { 141 static RE2_with_perl_class re(conv<char>(wstring(L"[\n  ]"))); 142 string result(str); 143 RE2::GlobalReplace(&result, re.re(), repl); 144 return result; 145} 146#define polystring(str, type) std::get<const type *>(std::make_tuple(str, L##str)) 147// LFとTABのみ制御文字をエスケープし、二重引用符で囲った文字列を返す 148template<typename T> 149T express_string(const T& str) { 150 static const auto conv_pairs = make_array( 151 make_pair(polystring("\t", typename T::value_type), polystring("\\t", typename T::value_type)), 152 make_pair(polystring("\n", typename T::value_type), polystring("\\n", typename T::value_type))); 153 static const T quote{ polystring("\"", typename T::value_type) }; 154 T result{ str }; 155 result = quote + 156 accumulate( 157 conv_pairs.begin(), 158 conv_pairs.end(), 159 result, 160 [](const T& total, const pair<T, T>& e) { 161 return replace_string(total, e.first, e.second); 162 }) + 163 quote; 164 return result; 165} 166// strをN回繰り返した(+で結合した)ものを返す(SFINAE) 167template<int N, typename T> 168static auto repeat(const T& str) -> typename std::enable_if<(N == 1), T>::type { 169 return str; 170} 171template<int N, typename T> 172static auto repeat(const T& str) -> typename std::enable_if<(N > 1), T>::type{ 173 return repeat<N - 1, T>(str) + str; 174} 175// メイン 176int main() { 177#if defined(_WIN32) && defined(__MINGW32__) && ! defined(__CYGWIN__) 178 ios_base::sync_with_stdio(false); 179#endif 180 setlocale(LC_ALL, ""); 181 const string input = conv<char>(wstring(L"hoge fuga piyo\n")); 182 const string rep_n_times = repeat<5>(input); 183 const auto print_title = [](auto& os, const auto& type, const auto& input, const auto& output) { 184 os << type << polystring(": ", typename remove_reference<decltype(os)>::type::char_type) 185 << express_string(input) << polystring(" -> ", typename remove_reference<decltype(os)>::type::char_type) 186 << express_string(output) << endl; 187 }; 188 const auto print_time = [](auto& os, auto ms) { 189 os << polystring("\ttime: ", typename remove_reference<decltype(os)>::type::char_type) << static_cast<double>(ms) / 1000 << endl; 190 }; 191 auto inputs = make_array(input, rep_n_times); 192 auto replace_methods = make_array( 193 make_pair("normal", replace_normal), 194 make_pair("regexp(pcre2)", replace_regexp_pcre2), 195 make_pair("regexp(to_wstring->std::wregex->to_string)", replace_regexp_std_wstringconv), 196 make_pair("regexp(std::regex)", replace_regexp_std), 197 make_pair("to_wstring_to_string", to_wstring_to_string), 198 make_pair("regexp(re2)", replace_regexp_re2) 199 ); 200 for (const auto& method : replace_methods) { 201 for (const auto& input : inputs) { 202#ifdef _WIN32 203 print_title(wcout, method.first, conv<wchar_t>(input), conv<wchar_t>(method.second(input, "_"))); 204 print_time(wcout, measure(method.second, 1000000, input, "_")); 205#else 206 print_title(cout, method.first, input, method.second(input, "_")); 207 print_time(cout, measure(method.second, 1000000, input, "_")); 208#endif 209 } 210 } 211 return 0; 212} 213// コンパイル例) 214// g++ -g -Wall -I./external/include -O3 replace_vs_regex.cpp -lpcre2-8 -lre2 -o replace_vs_regex

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#7

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/03 21:11

その後の進捗です。前回TODOのうち以下を実施。

  • boost::regexで計測(ただしicu未使用なのでstd::regexと同じ挙動)

結果

計測

置換内容: s/[\n  ]/_/g

入力文字列/Platform"hoge fuga piyo\n" /Ubuntu5 x "hoge fuga piyo\n" /Ubuntu"hoge fuga piyo\n" /Win105 x "hoge fuga piyo\n" /Win10"hoge fuga piyo\n" /Win10(MINGW64)5 x "hoge fuga piyo\n" /Win10(MINGW64)
Python3連replace (流用)0.5000.7860.5650.825--
Python正規表現 (流用)0.9113.1751.0673.706--
C++3連replace (流用)0.3030.7660.4941.0160.4821.133
C++正規表現(pcre2) (流用)0.6263.8730.6723.4460.6733.492
C++正規表現(std::wregex + wstring経由) (流用)3.69616.7463.23416.26011.78756.651
C++正規表現(std::regex) (流用)1.4696.6854.24320.6642.1679.258
C++ →wstring→string (流用)0.2270.7650.3531.7890.3801.341
C++正規表現(re2) (流用)0.8473.7740.9964.4501.2395.423
C++正規表現(boost::wregex + wstring経由)1.2884.5071.3895.5271.4504.962
C++正規表現(boost::regex)1.1995.0631.3805.1731.3045.053

Ubuntu: Win10上VirtualBox7.0.4上Ubuntu20.04 amd64 AMD Ryzen 5 1400/python 3.8.10/gcc 9.4.0/pcre2 10.34/re2 2020-01-01/boost 1.71.0
Win10: 19045.2728 AMD Ryzen 5 1400/python 3.8.5/vc++2019 or MINGW64 gcc12.2/pcre2 10.34(※)/re2 2020-01-01(※)/boost 1.71.0
(※)cmakeで作ったソリューションからでデフォルトビルドしたx64 Release static版(VC++)/ninja+gcc12.2でデフォルトReleaseビルドしたstatic版(MINGW64)
(※)Windowsでビルドするときはpcre2を静的ライブラリにしたので、-DPCRE2_STATICをコンパイルオプションに加えること
(※)VC++のビルドはマルチスレッドDLLでUNICODE(デフォルト)
(※)Windowsのシステムロケールはコードページ932のまま

現在までに分かっていること

  • pcre2とboostはプラットフォームに依らず、ほぼ同じ時間になっている
  • re2はプラットフォームに依らず似たような傾向を示すが、時間はubuntu < VC++ < MINGW64のようになっている
  • re2は安全性を目標としたエンジンとのことで(時間的脆弱性への考慮があるらしい)、同程度の機能/速度なら良さげに見えた
  • WindowsはデフォルトのUNICODEで動かしたが、VC++だとstd::regexが3倍ほど、mingwではstd::wregexが3倍ほど時間がかかっていた
  • WindowsのMINGW64のstd::regexも速くはない
  • icuのないboost::regexは仕組み的にstd::regexと同じなので同列に並べることも出来ないが、pcre2/re2よりやや遅い程度の時間で動作している

現時点で有力なのは実績のpcre2、安定性のre2。個人的にはLinuxならre2、Windowsならpcre2/re2かといった感じ。
極端に遅いのは私のコードのバグの可能性が高いが、std::regexは魅力皆無なので気にしない。
boost::regexはicu入れてからが本番。

TODO

  • icu付きのboost計測
  • 良さげなベンチマーク結果の調査

コード

Pythonは同じコード
(続く)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#8

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/03 21:12

(続き)

C++

1#include <iostream> 2#include <chrono> 3#include <string> 4#include <array> 5#include <vector> 6#include <sstream> 7#include <functional> 8#include <numeric> 9#include <type_traits> 10#include <regex> 11#include <cwchar> 12#define PCRE2_CODE_UNIT_WIDTH 8 13#include "pcre2.h" 14#include <re2/re2.h> 15#include "boost/regex.hpp" 16// https://github.com/ww898/utf-cpp/tree/v2.2.2 17#include <ww898/utf_converters.hpp> 18#include <ww898/utf_sizes.hpp> 19using namespace std; 20using namespace chrono; 21using namespace ww898::utf; 22// func(args...)をcount回実行してかかった時間[ms]を返す 23template<typename Func, typename ...Args> 24auto measure(Func func, size_t count = 1000000, Args... args) { 25 auto begin = high_resolution_clock::now(); 26 for (; count > 0; --count) func(args...); 27 auto end = high_resolution_clock::now(); 28 return duration_cast<milliseconds>(end - begin).count(); 29} 30// 引数リストに渡されたものを要素としたstd::arrayを返す 31template<typename T, typename... Tail> 32auto make_array(T head, Tail... tail)->array<T, 1 + sizeof...(Tail)> { 33 array<T, 1 + sizeof...(Tail)> result = { head, tail ... }; 34 return result; 35} 36// strを走査し、patで指定された部分文字列をreplで指定した文字列に置換したものを返す 37template<typename T> 38T replace_string(const T& str, const T& pat, const T& repl) { 39 T result(str); 40 typename T::size_type idx = 0; 41 typename T::size_type pos = 0; 42 while ((idx = result.find(pat, pos)) != T::npos) { 43 result.erase(idx, pat.length()); 44 result.insert(idx, repl); 45 pos = idx + repl.length(); 46 } 47 return result; 48} 49// strから改行と半角/全角スペースをreplに単純置換した文字列を返す 50string replace_normal(const string& str, const string& repl) { 51 static const auto search_strings = make_array<string>(conv<char>(wstring(L"\n")), conv<char>(wstring(L" ")), conv<char>(wstring(L" "))); 52 string result(str); 53 for (const auto& s : search_strings) { 54 result = replace_string(result, s, repl); 55 } 56 return result; 57} 58//pcre2のラップクラス 59struct pcre2_wrap { 60 int errorcode = 0; 61 string errormessage; 62 PCRE2_SIZE erroroffset = 0; 63 pcre2_code* re = NULL; 64 struct match_data { 65 pcre2_match_data* m = NULL; 66 // match_dataからi番目のグループを返す 67 const PCRE2_SIZE* get_ovector_pointer(PCRE2_SIZE i) { return pcre2_get_ovector_pointer(m) + i; } 68 match_data(const pcre2_wrap& re) { m = pcre2_match_data_create_from_pattern(re.re, NULL); } 69 ~match_data() { pcre2_match_data_free(m); m = NULL; } 70 }; 71 int rc = 0; 72 // 正規表現patternをコンパイルしてreに設定し、結果を返す 73 bool compile(const string& pattern, uint32_t option) { 74 re = pcre2_compile(reinterpret_cast<const unsigned char*>(pattern.c_str()), pattern.length(), option, &errorcode, &erroroffset, NULL); 75 if (re == NULL) { 76 vector<PCRE2_UCHAR> buffer(256); 77 pcre2_get_error_message(errorcode, buffer.data(), buffer.size()); 78 stringstream sstream; 79 sstream << "コンパイル失敗。オフセットは" << static_cast<int>(erroroffset) << ": " << reinterpret_cast<const char*>(buffer.data()) << "です。" << endl; 80 errormessage = sstream.str(); 81 } 82 return re != NULL; 83 } 84 // strのpos番目からreで検索してmに設定し、結果を返す 85 bool match(const string& str, PCRE2_SIZE pos, match_data& m) { 86 rc = pcre2_match(re, reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), pos, 0, m.m, NULL); 87 return rc >= 0; 88 } 89 pcre2_wrap() {} 90 pcre2_wrap(const string& pattern, uint32_t option) { compile(pattern, option); } 91 ~pcre2_wrap() { 92 pcre2_code_free(re); 93 re = NULL; 94 } 95}; 96// pcre2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 97string replace_regexp_pcre2(const string& str, const string& repl) { 98 static pcre2_wrap re(conv<char>(wstring(L"[\n  ]")), PCRE2_UTF); 99 static pcre2_wrap::match_data m(re); 100 string result = str; 101 PCRE2_SIZE pos = 0; 102 while (re.match(result, pos, m)) { 103 const PCRE2_SIZE* ovector = m.get_ovector_pointer(0); 104 result.erase(ovector[0], ovector[1] - ovector[0]); 105 result.insert(ovector[0], repl); 106 pos = ovector[0] + repl.length(); 107 } 108 return result; 109} 110// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(ただし、utf-cppを使ってstring<->wstringの変換を行う) 111string replace_regexp_std_wstringconv(const string& str, const string& repl) { 112 static const wregex re(L"[\n  ]"); 113 return conv<char>(regex_replace(convz<wchar_t>(str.c_str()), re, convz<wchar_t>(repl.c_str()))); 114} 115// std::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(1バイト1文字) 116string replace_regexp_std(const string& str, const string& repl) { 117 static const regex re(conv<char>(wstring(L"[\n  ]"))); 118 return regex_replace(str, re, repl); 119} 120// utf-cppを使ってstrをwstringにしてから、同様にstringに戻して返す 121string to_wstring_to_string(const string& str, const string& repl) { 122 return conv<char>(convz<wchar_t>(str.c_str())); 123} 124// perlで使用されるパターン文字列を許容するオプション付きRE2 125class RE2_with_perl_class { 126 RE2* re_; 127 const string pat_; 128public: 129 RE2_with_perl_class(const string& s) : re_(nullptr), pat_(s) {} 130 ~RE2_with_perl_class() { delete re_; re_ = nullptr; } 131 RE2& re() { 132 if (re_ == nullptr) { 133 RE2::Options o; 134 o.set_perl_classes(true); 135 re_ = new RE2(pat_, o); 136 } 137 return *re_; 138 } 139}; 140// re2を使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す 141string replace_regexp_re2(const string& str, const string& repl) { 142 static RE2_with_perl_class re(conv<char>(wstring(L"[\n  ]"))); 143 string result(str); 144 RE2::GlobalReplace(&result, re.re(), repl); 145 return result; 146} 147// boost::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(ただし、utf-cppを使ってstring<->wstringの変換を行う) 148string replace_regexp_boost_regex_wstringconv(const string& str, const string& repl) { 149 static const boost::wregex re(wstring(L"[\n  ]")); 150 return conv<char>(boost::regex_replace(convz<wchar_t>(str.c_str()), re, repl)); 151} 152// boost::regexを使ってstrから改行と半角/全角スペースをreplに置換した文字列を返す(1バイト1文字) 153string replace_regexp_boost_regex(const string& str, const string& repl) { 154 static const boost::regex re(conv<char>(wstring(L"[\n  ]"))); 155 return boost::regex_replace(str, re, repl); 156} 157#define polystring(str, type) std::get<const type *>(std::make_tuple(str, L##str)) 158// LFとTABのみ制御文字をエスケープし、二重引用符で囲った文字列を返す 159template<typename T> 160T express_string(const T& str) { 161 static const auto conv_pairs = make_array( 162 make_pair(polystring("\t", typename T::value_type), polystring("\\t", typename T::value_type)), 163 make_pair(polystring("\n", typename T::value_type), polystring("\\n", typename T::value_type))); 164 static const T quote{ polystring("\"", typename T::value_type) }; 165 T result{ str }; 166 result = quote + 167 accumulate( 168 conv_pairs.begin(), 169 conv_pairs.end(), 170 result, 171 [](const T& total, const pair<T, T>& e) { 172 return replace_string(total, e.first, e.second); 173 }) + 174 quote; 175 return result; 176} 177// strをN回繰り返した(+で結合した)ものを返す(SFINAE) 178template<int N, typename T> 179static auto repeat(const T& str) -> typename std::enable_if<(N == 1), T>::type { 180 return str; 181} 182template<int N, typename T> 183static auto repeat(const T& str) -> typename std::enable_if<(N > 1), T>::type{ 184 return repeat<N - 1, T>(str) + str; 185} 186// メイン 187int main() { 188#if defined(_WIN32) && defined(__MINGW32__) && ! defined(__CYGWIN__) 189 ios_base::sync_with_stdio(false); 190#endif 191 setlocale(LC_ALL, ""); 192 const string input = conv<char>(wstring(L"hoge fuga piyo\n")); 193 const string rep_n_times = repeat<5>(input); 194 const auto print_title = [](auto& os, const auto& type, const auto& input, const auto& output) { 195 os << type << polystring(": ", typename remove_reference<decltype(os)>::type::char_type) 196 << express_string(input) << polystring(" -> ", typename remove_reference<decltype(os)>::type::char_type) 197 << express_string(output) << endl; 198 }; 199 const auto print_time = [](auto& os, auto ms) { 200 os << polystring("\ttime: ", typename remove_reference<decltype(os)>::type::char_type) << static_cast<double>(ms) / 1000 << endl; 201 }; 202 auto inputs = make_array(input, rep_n_times); 203 auto replace_methods = make_array( 204 make_pair("normal", replace_normal), 205 make_pair("regexp(pcre2)", replace_regexp_pcre2), 206 make_pair("regexp(to_wstring->std::wregex->to_string)", replace_regexp_std_wstringconv), 207 make_pair("regexp(std::regex)", replace_regexp_std), 208 make_pair("to_wstring_to_string", to_wstring_to_string), 209 make_pair("regexp(re2)", replace_regexp_re2), 210 make_pair("regexp(to_wstring->boost::wregex->to_string)", replace_regexp_boost_regex_wstringconv), 211 make_pair("regexp(boost::regex)", replace_regexp_boost_regex) 212 ); 213 for (const auto& method : replace_methods) { 214 for (const auto& input : inputs) { 215#ifdef _WIN32 216 print_title(wcout, method.first, conv<wchar_t>(input), conv<wchar_t>(method.second(input, "_"))); 217 print_time(wcout, measure(method.second, 1000000, input, "_")); 218#else 219 print_title(cout, method.first, input, method.second(input, "_")); 220 print_time(cout, measure(method.second, 1000000, input, "_")); 221#endif 222 } 223 } 224 return 0; 225} 226// コンパイル例) 227// g++ -g -Wall -I./external/include -O3 replace_vs_regex.cpp -lpcre2-8 -lre2 -lboost_regex -o replace_vs_regex

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#9

ujimushi_sradjp

総合スコア2152

投稿2023/04/05 11:08

私が数年前に利用を検討し,結局C++を使うことが無くなったので放ったらかしですが,ぐぐって引っかかった
個人のホームページで公開しているライブラリで「SRELL」というのがあります。
気軽に使うのであれば,これを利用するのもいいかもしれません。

私が感じる特徴としては次のような感じです。

  • std::regexと同じライブラリ構造
  • テンプレートライブラリなのでヘッダーファイルをインクルードするだけとお手軽
  • unicode専用で,Shift_JISやEUC-JPは扱えない
  • 標準では3つのヘッダーファイル,単一ファイルにしたバージョンもあり

また,テンプレートを使ったヘッダファイルのみのライブラリのため

  • コンパイルに時間がかかる
  • ヘッダファイル内にunicode情報を含んでいるので実行ファイルサイズが大きめ
  • エラーメッセージが分かりづらい

等が気になる可能性が考えられます。
ただし,実行ファイルにした後はそれほど問題はないでしょう。

ベンチマークは調べていませんが,例示されているソースに当てはめると
次のような感じで利用可能でしょうか?

c++

1// includeパスを通しておく 2#include <srell.hpp> 3 4... 5 6string replace_regexp_srell(const string& str, const string& repl) { 7 static const srell::u8cregex re("[\n  ]"); 8 return srell::regex_replace(str, re, repl); 9}

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#10

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/05 11:58

#9
情報ありがとうございます。

最新版ではboost::regexもヘッダオンリーですよ(icuがなければ)。
C++で使用可能な正規表現ライブラリは星の数ほどあると思いますし、その中でUTF-8で動作するものは少ないかもしれませんが、恐らく大量にあると思います。なので一言でいうと個人開発のものまで手が回らない、というのが本音です。

私が選んだ基準は書いていませんでしたが、「ubuntuでcanonicalがパッケージ化している」ことです。その条件なら、ある程度の知名度があり、利用者もいて、開発者以外のメンテナまでいるということになります。正規表現エンジンはそれなりに複雑なアルゴリズムを持ち、ベースはNFA/DFAだとしても実装は一通りではなく、品質も大事なものな一方で基礎的な機能でもあるため、安全性/安定性はそれなりに重視したいということです。また有名なものは機能的な比較もすでにまとめられているので、楽なのです。例えば日本人作成で有名なのだとonigあたりが気になっていますが、日本人バイアスかかりそうなので今のところ控えてるくらいです(面倒なだけともいう)。

また、私が提示したコードは(そんなことする必要もないほどしょうもないものですが)最終的に何某かの形で誰でもビルドできるようにしたいとは思っています。その際cmakeとvcpkgを使おうかと考えているので、gitリポジトリもないものだと拾いようがないというのもあります。

まあ時間があったら検討するかもしれません。余談ですが、今はicuのバージョンを合わせようとしたらWindows8.1のSDKが必要になってVS2019インストーラから入れられず、ディスク容量もパンク寸前で頭を抱えているところです。先に前述したビルドシステムを決めて各ライブラリを(ほぼ)最新版で統一するかもしれません。

知りたいことはただの検索/置換と正規表現による検索/置換で感覚的にどの程度速度差があるかってだけなんですけどね。やってみたら比較するエンジンの選定がそれなりに大変だったというわけです。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#11

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/04/26 18:07

そろそろcloseしてしまいそうなので、進捗全くないんですが、ageさせてもらいます。
cmake + vcpkgがかなり難航してます。

上手く動いていない作りかけのCMakeLists.txtが以下のとおりです。

cmake

1cmake_minimum_required(VERSION 3.0.0) 2project(replace_vs_regex VERSION 0.1.0) 3 4add_executable(replace_vs_regex replace_vs_regex.cpp) 5 6include(FetchContent) 7FetchContent_Declare( 8 utf-cpp 9 URL https://github.com/ww898/utf-cpp/archive/refs/tags/v2.2.2.tar.gz 10) 11FetchContent_MakeAvailable(utf-cpp) 12target_include_directories(${PROJECT_NAME} PRIVATE ${utf-cpp_SOURCE_DIR}/include) 13 14set(PCRE2_USE_STATIC_LIBS ON) 15find_package(PCRE2 REQUIRED COMPONENTS 8BIT CONFIG) 16target_link_libraries(replace_vs_regex PRIVATE PCRE2::8BIT) 17 18find_package(re2 CONFIG REQUIRED) 19target_link_libraries(replace_vs_regex PRIVATE re2::re2) 20 21set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) 22find_package(ICU COMPONENTS i18n uc data REQUIRED) 23target_link_libraries(replace_vs_regex PRIVATE ICU::i18n ICU::uc ICU::data) 24 25set(Boost_USE_STATIC_LIBS ON) 26set(Boost_USE_MULTITHREADED ON) 27set(Boost_USE_STATIC_RUNTIME ON) 28find_package(Boost REQUIRED COMPONENTS regex) 29target_link_libraries(replace_vs_regex PRIVATE Boost::boost Boost::regex)

vcpkgを直下に入れてマニフェストを記述した上で、linuxから

bash

1cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release

とすると、pcre2のところでpcre2のconfig.cmakeが見つからず、エラーで止まります。 -DPCRE2_DIR=./vcpkg/buildtrees/pcre2/x64-linux-rel/cmake/ を入れるとlinuxでは何とかビルドできるのですが、まだ難航しそうです(他の環境ではさらに苦労しそう…特にmingw)。(で、作業用の領域を確保するのも面倒だし最近放置してました)

3環境で動くようになったらコードも公開予定です。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#12

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/05/23 01:18

そろそろageておきます。進捗ありません。
(vcpkgを使用したpcre2用cmakeのfind_packageで難航してます。あんまり調べる時間がないのもありますが)

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問