2012-03-01 8 views
5

저는 Ruby에서 다른 형식으로 시간 값을 파싱하는 텍스트 변환 루틴을 만들고 있습니다. 이 루틴은 복잡해지고 있으며 현재이 문제에 대한 더 나은 접근 방법을 테스트 중입니다.ruby ​​scanf가 왜 그렇게 느린가요?

현재 scanf을 (를) 테스트하는 중입니다. 왜? 항상 이라고 생각했는데, 정규 표현식보다 빠르지 만 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 

이 내가 scanf 제 1 및 정규식 초를 사용하여 무엇을 가지고있다 : 나는 루비 1.9.2-p290 [x86_64에 (MRI)

먼저 루비 테스트를 사용하고 있습니다 :

 user  system  total  real 
    95.020000 0.280000 95.300000 (96.364077) 
     user  system  total  real 
    2.820000 0.000000 2.820000 ( 2.835170) 

번째 테스트하여 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); 
} 
,536,913,632 10 개

C 코드의 결과는 분명히 더 빨리, 그리고 정규 표현식 결과는 예상대로보다 느린 scanf 결과입니다

sscanf_time (500k iterations): 0.1774 

regex_time (500k iterations): 3.9692 

C 실행 시간이 빠른 것은 분명하다, 그래서 루비는 것을 언급하지 마십시오 해석하고 제발 그런 것들.

이것은 관련 gist입니다.

+0

C에서 반복 할 때마다 표현식을 다시 컴파일하지 않습니까? 나는 루비가 그렇게 생각하지 않는다. 표현식을 한 번만 컴파일하면 C 결과를 볼 수 있습니다. 또한 왜 분할을 사용하고 있습니까? 문자열에 대해 더 이상 작업하지 않고 직접 값을 캡처 할 수 있도록 문자열을 일치시킵니다. – Qtax

+0

그래, 내가 재 컴파일이야, 그보다 더 빠를 수 있지만 때로는 애꾸 바꾸기가 필요해. – AndreDurao

+0

그런 다음 변경 될 때만 다시 컴파일하면됩니다. 하지만 숫자를보고 싶습니다. ;-) – Qtax

답변

4

문제는 그것이 해석 된 것이 아니라 Ruby의 모든 것이 객체라는 것입니다. Ruby 배포판에서 "scanf.rb"를 탐색하여 C의 scanf 구현과 비교할 수 있습니다.

RegExp 일치를 기반으로하는 scanf의 Ruby 구현. "% d"와 같은 모든 아톰은 루비의 객체입니다. C에서는 단 하나의 사례 항목입니다. 따라서, 이러한 실행 시간의 이유는 많은 객체 할당/할당 취소입니다.

+0

scanf가 .so 파일을 필요로하는 openssl과 같은 네이티브 구현을 사용한다고 생각했습니다. – AndreDurao

2

MRI를 가정 할 때 : scanf는 10 년 전에 Ruby (scanf.rb)로 작성되었으며 분명히 만져 본 적이 없으며 (복잡 해 보인다!). split, map 및 정규식은 많이 최적화 된 C에서 구현됩니다.

관련 문제