2012-03-01 3 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最初と正規表現秒を使用して得たものです:私は、Ruby-1.9.2-P290 [x86_64版](MRI)

まずRubyのテストを使用しています:

 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を使用して第2テスト:

#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); 
} 

Cコードの結果が明らかに高速化され、そして期待通りの正規表現の結果はscanf結果より遅いです:

sscanf_time (500k iterations): 0.1774 

regex_time (500k iterations): 3.9692 

とてもRubyがあることをコメントしないでください、Cの実行時間が高速であることは明らかです解釈されて、そのようなものをしてください。

これは関連するgistです。

+0

C言語のすべての反復式を再コンパイルしていませんか?私はRubyがそれをしないと思う。式を一度しかコンパイルしないとCの結果を見るのには興味があります。また、なぜあなたは分割を使用していますか?文字列をさらに一致させることなく、値を直接取得できるように、文字列と一致しています。 – Qtax

+0

ええ、私は再コンパイルしている、それはそれよりもさらに高速かもしれませんが、時々エクスペットを変更する必要があります。 – AndreDurao

+0

その後、変更されたときに再コンパイルするだけで済みます。しかし、私は数字を見たいだけです。 ;-) – Qtax

答えて

4

問題は解釈されているわけではありませんが、Rubyのすべてがオブジェクトです。あなたのRubyディストリビューションで "scanf.rb"を探索し、それをCのscanf実装と比較することができます。

RubyでのRegExpマッチングに基づく実装。 "%d"のようなすべてのアトムはルビのオブジェクトですが、それはCの1つのケースの項目です。そのような実行時間の理由は、オブジェクトの割り当て/解放の多くです。

+0

私はscanfが.soファイルを必要とするopensslなどと同じようにネイティブの実装を使用すると考えました – AndreDurao

2

MRIを仮定すると、scanfは10年前にRuby(scanf.rb)で書かれていて、決して触れられませんでした(それは複雑に見えます)。 split,map、正規表現が大幅に最適化されたC言語で実装されています。

関連する問題