Rendimiento de expresiones regulares de C ++ vs .NET
Solicitado por un comentario de Konrad Rudolph enuna pregunta relacionada, Escribí el siguiente programa para comparar el rendimiento de expresiones regulares en 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
y el equivalente en 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;
}
Ambos programas cargan dos archivos como cadenas Unicode (estoy usando una copia de la Biblia), construyen una expresión regular Unicode no trivial\w?\w?\w?\w?\w?\w
y divida la cadena cuatro veces en paralelo usando la expresión regular que devuelve la suma de las longitudes de las cadenas divididas (para evitar la asignación).
Al ejecutarse tanto en Visual Studio (con MP y OpenMP habilitado para C ++) en la versión de lanzamiento de 64 bits, el C ++ toma 43.5s y el F # toma 3.28s (más de 13 veces más rápido). Esto no me sorprende porque creo que .NET JIT compila las expresiones regulares a código nativo, mientras que el stdlib de C ++ lo interpreta, pero me gustaría una revisión por pares.
¿Existe un error de rendimiento en mi código C ++ o es una consecuencia de expresiones regulares compiladas o interpretadas?
EDITAR: Billy ONeal ha señalado que .NET puede tener una interpretación diferente de\w
así lo he explicitado en una nueva expresión regular:
[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]?[0-9A-Za-z_]
En realidad, esto hace que el código .NET sea sustancialmente más rápido (C ++ es el mismo), lo que reduce el tiempo de 3.28s a 2.38s para F # (más de 17x más rápido).