2012-03-01 26 views
5

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)

prueba

Primera 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.

+0

¿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

+0

Sí, recomiendo, podría ser aún más rápido que eso, pero a veces necesito cambiar el exp. – AndreDurao

+0

Entonces solo necesita recompilarlo cuando se lo cambie. Pero solo me gustaría ver los números. ;-) – Qtax

Respuesta

4

El problema no es que se interprete, sino que todo en Ruby es un objeto. Puede explorar "scanf.rb" en su distribución de Ruby y compararlo con la implementación de scanf en C.

Implementación de Ruby de scanf basada en la coincidencia de RegExp. Cada átomo como "% d" es un objeto en ruby, mientras que es solo un elemento de caso en C. Entonces, en mi opinión, el motivo de tal tiempo de ejecución es una gran cantidad de asignación/desasignación de objetos.

+0

Pensé que scanf usa implementación nativa al igual que openssl y otras que requieren archivos .so – AndreDurao

2

Asumiendo MRI: scanf está escrito en Ruby (scanf.rb) aparentemente hace 10 años y nunca se ha tocado (¡y parece complejo!). split, map, y las expresiones regulares se implementan en C.

fuertemente optimizado
Cuestiones relacionadas