1

現在、ルビーの速度テストに取り組んでおり、いくつかのテキストファイルを数値に解析する必要があります。速度が遅いため、コードを最適化できるかどうか、または ruby​​ が本当に遅いかどうか疑問に思っていました。コードはファイルから読み取られています。これらのファイルには、ランダムに生成された約 1 000 000 行または数字が含まれています。何が読み取られているかがわかるように、数行のみを表示します。読み取る必要のあるファイル名は引数として渡されます。coed は別のスクリプトです (わかりやすくするため)。

最初に単純な数値を解析したいのですが、入力は次の形式になります。

type
number

type
number

...

これが私がやった方法です:

incr = 1

File.open(ARGV[0], "r").each_line do |line|
  incr += 1
  if incr % 3 == 0
    line.to_i
  end

end

次に、単一のリストに解析する必要があります。入力は次の形式になります。

type
(1,2,3,...)

type
(1,2,3,...)

...

これが私がやった方法です

incr = 1

File.open(ARGV[0], "r").each_line do |line|
  incr += 1
  if incr % 3 == 0
    line.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}
  end

end

最後に、リストのリストに解析する必要があります。入力は次の形式になります。

type
((1,2,3,...),(1,2,3,...),(...))

type
((1,2,3,...),(1,2,3,...),(...))

...

これが私がやった方法です:

incr = 1

File.open(ARGV[0], "r").each_line do |line|
  incr += 1
  if incr % 3 == 0
    line.split("),(").map{ |s| s.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}}

  end

end

結果を表示する必要はありません。速度テストを行っているだけなので、出力は必要ありません。私は結果を確認しましたが、コード自体は正しく動作しているように見えます.コードは驚くほど遅いので、ルビーが提供する最適なものでスピードテストしたいと思います. 使用できるスピードテストがいくつかあることは知っていますが、私の目的のためには自分で作成する必要があります。

どうすれば改善できますか? このコードはどのように最適化できますか? どこで間違ったのですか、それともこれはすでにルビーができる最高のことですか? ヒントやアイデアをお寄せいただきありがとうございます。

4

2 に答える 2

3

最初のものでは、代わりに:

File.open(ARGV[0], "r").each_line do |line|

使用する:

File.foreach(ARGV[0]) do |line|

そして代わりに:

  incr += 1
  if incr % 3 == 0

使用する:

 if $. % 3 == 0

$.最後に読み取った行の行番号のマジック変数です。

2番目のものでは、代わりに:

line.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}

使用する:

line.tr('()', '').split(',').map(&:to_i)

3番目のものでは、代わりに:

line.split("),(").map{ |s| s.gsub("(","").gsub(")","").split(",").map{ |s| s.to_i}}

使用する:

line.scan(/(?:\d+,?)+/).map{ |s| s.split(',', 0).map(&:to_i) }

その行がどのように機能するかは次のとおりです。

line.scan(/(?:\d+,?)+/)
=> ["1,2,3,", "1,2,3,"]

line.scan(/(?:\d+,?)+/).map{ |s| s.split(',',0) }
=> [["1", "2", "3"], ["1", "2", "3"]]

line.scan(/(?:\d+,?)+/).map{ |s| s.split(',', 0).map(&:to_i) }
=> [[1, 2, 3], [1, 2, 3]]

速度を比較するためにベンチマークを実行しませんでしたが、gsub呼び出しがなくなったため、変更も高速になるはずです。私が行った変更は、必ずしも最速の方法ではなく、独自のコードのより最適化されたバージョンです。

Ruby の速度を他の言語と比較するには、各ステップの複数のベンチマークに基づいて、各ステップを最速で達成する方法に関する知識が必要です。また、同一のハードウェアと OS で実行していて、すべての言語が最も効率的な速度の形式にコンパイルされていることも意味します。言語は、メモリ使用量と速度のトレードオフを行うため、ある言語が別の言語よりも遅い場合でも、メモリ効率が高い場合があります。

さらに、実稼働環境でコーディングする場合、正しく機能するコードを生成する時間は、「どちらが速いか」という方程式に織り込む必要があります。C は非常に高速ですが、ほとんどの問題では Ruby よりもプログラムを作成するのに時間がかかります。C は Ruby のように手を握らないからです。C コードの作成とデバッグに 1 週​​間かかる場合と、1 時間かかった Ruby コードの場合、どちらが速いでしょうか? 考えることばかり。


@tadmanの回答とコメントは、完了するまで読みませんでした。使用:

map(&:to_i)

以前は以下よりも遅かった:

map{ |s| s.to_i }

速度の違いは、実行している Ruby のバージョンによって異なります。を使用することは、もともと&:いくつかのモンキー パッチで実装されていましたが、現在は Ruby に組み込まれています。彼らがその変更を行ったとき、それはかなりスピードアップしました:

require 'benchmark'

foo = [*('1'..'1000')] * 1000
puts foo.size

N = 10
puts "N=#{N}"

puts RUBY_VERSION
puts

Benchmark.bm(6) do |x|
  x.report('&:to_i') { N.times { foo.map(&:to_i) }}
  x.report('to_i') { N.times { foo.map{ |s| s.to_i } }}
end

どの出力:

1000000
N=10
2.0.0

             user     system      total        real
&:to_i   1.240000   0.000000   1.240000 (  1.250948)
to_i     1.400000   0.000000   1.400000 (  1.410763)

これは 10,000,000 要素を通過するため、0.2/秒の差しかありません。同じことを行う 2 つの方法に大きな違いはありません。より多くのデータを処理する場合、それは重要です。ほとんどのアプリケーションでは、他のことがボトルネック/スローダウンになるため、これは議論の余地があります。したがって、その速度の違いを念頭に置いて、自分に合った方法でコードを記述してください。


Ruby バージョンによる違いを示すために、Ruby 1.8.7 を使用した同じベンチマーク結果を次に示します。

1000000
N=10
1.8.7

            ユーザーシステム合計実数
&:to_i 4.940000 0.000000 4.940000 ( 4.945604)
to_i 2.390000 0.000000 2.390000 ( 2.396693)

限りgsubtr

require 'benchmark'

foo = '()' * 500000
puts foo.size

N = 10
puts "N=#{N}"

puts RUBY_VERSION
puts

Benchmark.bm(6) do |x|
  x.report('tr') { N.times { foo.tr('()', '') }}
  x.report('gsub') { N.times { foo.gsub(/[()]/, '') }}
end

これらの結果:

1000000
N=10
1.8.7

            ユーザーシステム合計実数
tr 0.010000 0.000000 0.010000 ( 0.011652)
gsub 3.010000 0.000000 3.010000 (3.014059)

と:

1000000
N=10
2.0.0

             ユーザーシステム合計実数
tr 0.020000 0.000000 0.020000 ( 0.017230)
gsub 1.900000 0.000000 1.900000 (1.904083)

正規表現パターンを変更した場合の違いは次のとおりです。これにより、目的の結果を得るために必要な処理が強制的に変更されます。

require 'benchmark'

line = '((1,2,3),(1,2,3))'

pattern1 = /\([\d,]+\)/
pattern2 = /\(([\d,]+)\)/
pattern3 = /\((?:\d+,?)+\)/
pattern4 = /\d(?:[\d,])+/

line.scan(pattern1) # => ["(1,2,3)", "(1,2,3)"]
line.scan(pattern2) # => [["1,2,3"], ["1,2,3"]]
line.scan(pattern3) # => ["(1,2,3)", "(1,2,3)"]
line.scan(pattern4) # => ["1,2,3", "1,2,3"]

line.scan(pattern1).map{ |s| s[1..-1].split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern2).map{ |s| s[0].split(',').map(&:to_i) }     # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern3).map{ |s| s[1..-1].split(',').map(&:to_i) } # => [[1, 2, 3], [1, 2, 3]]
line.scan(pattern4).map{ |s| s.split(',').map(&:to_i) }        # => [[1, 2, 3], [1, 2, 3]]

N = 1000000
Benchmark.bm(8) do |x|
  x.report('pattern1') { N.times { line.scan(pattern1).map{ |s| s[1..-1].split(',').map(&:to_i) } }}
  x.report('pattern2') { N.times { line.scan(pattern2).map{ |s| s[0].split(',').map(&:to_i) }     }}
  x.report('pattern3') { N.times { line.scan(pattern3).map{ |s| s[1..-1].split(',').map(&:to_i) } }}
  x.report('pattern4') { N.times { line.scan(pattern4).map{ |s| s.split(',').map(&:to_i) }        }}
end

Ruby 2.0-p427 の場合:

               user     system      total        real
pattern1   5.610000   0.010000   5.620000 (  5.606556)
pattern2   5.460000   0.000000   5.460000 (  5.467228)
pattern3   5.730000   0.000000   5.730000 (  5.731310)
pattern4   5.080000   0.010000   5.090000 (  5.085965)
于 2013-07-25T16:01:58.850 に答える