Estoy trabajando en algunas rutinas de conversión de texto que analizan valores de tiempo en diferentes formatos en Ruby. Esta rutina está creciendo en complejidad, y actualmente estoy probando un mejor enfoque para este problema.¿por qué ruby scanf es tan lento?
Actualmente estoy probando una forma de usar scanf
. ¿Por qué? Yo siempre pensaba que era más rápido que una expresión regular, pero ¿qué pasó en Ruby? ¡Fue mucho más lento!
¿Qué estoy haciendo mal?
Nota: Estoy usando ruby-1.9.2-p290 [] x86_64 (MRI)
pruebaPrimera Ruby:
require "scanf"
require 'benchmark'
def duration_in_seconds_regex(duration)
if duration =~ /^\d{2,}\:\d{2}:\d{2}$/
h, m, s = duration.split(":").map{ |n| n.to_i }
h * 3600 + m * 60 + s
end
end
def duration_in_seconds_scanf(duration)
a = duration.scanf("%d:%d:%d")
a[0] * 3600 + a[1] * 60 + a[2]
end
n = 500000
Benchmark.bm do |x|
x.report { for i in 1..n; duration_in_seconds_scanf("00:10:30"); end }
end
Benchmark.bm do |x|
x.report { for i in 1..n; duration_in_seconds_regex("00:10:30"); end }
end
Esto es lo que tengo uso de scanf
primero y segundo una expresión regular :
user system total real
95.020000 0.280000 95.300000 (96.364077)
user system total real
2.820000 0.000000 2.820000 ( 2.835170)
Segunda prueba usando C:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <string.h>
#include <regex.h>
char *regexp(char *string, char *patrn, int *begin, int *end) {
int i, w = 0, len;
char *word = NULL;
regex_t rgT;
regmatch_t match;
regcomp(&rgT, patrn, REG_EXTENDED);
if ((regexec(&rgT, string, 1, &match, 0)) == 0) {
*begin = (int) match.rm_so;
*end = (int) match.rm_eo;
len = *end - *begin;
word = malloc(len + 1);
for (i = *begin; i<*end; i++) {
word[w] = string[i];
w++;
}
word[w] = 0;
}
regfree(&rgT);
return word;
}
int main(int argc, char** argv) {
char * str = "00:01:30";
int h, m, s;
int i, b, e;
float start_time, end_time, time_elapsed;
regex_t regex;
regmatch_t * pmatch;
char msgbuf[100];
char *pch;
char *str2;
char delims[] = ":";
char *result = NULL;
start_time = (float) clock()/CLOCKS_PER_SEC;
for (i = 0; i < 500000; i++) {
if (sscanf(str, "%d:%d:%d", &h, &m, &s) == 3) {
s = h * 3600L + m * 60L + s;
}
}
end_time = (float) clock()/CLOCKS_PER_SEC;
time_elapsed = end_time - start_time;
printf("sscanf_time (500k iterations): %.4f", time_elapsed);
start_time = (float) clock()/CLOCKS_PER_SEC;
for (i = 0; i < 500000; i++) {
char * match = regexp(str, "[0-9]{2,}:[0-9]{2}:[0-9]{2}", &b, &e);
if (strcmp(match, str) == 0) {
str2 = (char*) malloc(sizeof (str));
strcpy(str2, str);
h = strtok(str2, delims);
m = strtok(NULL, delims);
s = strtok(NULL, delims);
s = h * 3600L + m * 60L + s;
}
}
end_time = (float) clock()/CLOCKS_PER_SEC;
time_elapsed = end_time - start_time;
printf("\n\nregex_time (500k iterations): %.4f", time_elapsed);
return (EXIT_SUCCESS);
}
Los resultados de código C son, evidentemente, más rápido, y los resultados de expresiones regulares son más lentos que scanf
resultados como se esperaba:
sscanf_time (500k iterations): 0.1774
regex_time (500k iterations): 3.9692
Es obvio que el tiempo de ejecución C es más rápido, así que por favor no comentar que Ruby es interpretado y cosas así por favor.
Este es el relacionado gist.
¿No recompila la expresión cada iteración en C? No creo que Ruby haga eso. Me interesaría ver los resultados de C si compila la expresión solo una vez. Además, ¿por qué estás usando una división? Está haciendo coincidir la cadena para que pueda capturar los valores directamente, sin más operaciones en la cadena. – Qtax
Sí, recomiendo, podría ser aún más rápido que eso, pero a veces necesito cambiar el exp. – AndreDurao
Entonces solo necesita recompilarlo cuando se lo cambie. Pero solo me gustaría ver los números. ;-) – Qtax