2013-07-25 35 views
1

我目前正在研究一些ruby的速度測試,我需要將一些文本文件解析爲數字值。由於速度較慢,我想知道我的代碼是否可以優化,或者如果ruby真的很慢。 代碼正在從文件中讀取,這些文件包含大約1 000 000隨機生成的行或數字,我只會顯示幾行,以便您知道正在讀取的內容。 我需要閱讀的文件名被作爲參數傳遞,coed是單獨的腳本(僅用於我自己的清晰度)。優化ruby代碼以將字符串解析爲數值

首先,我想分析一個簡單的數字,輸入進來的格式如下:

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 

我不需要顯示任何結果,我只是速度測試,所以不需要輸出。我確實檢查了結果,代碼本身似乎工作正常,它們只是令人驚訝地慢,我想加快紅寶石所能提供的最佳性能。 我知道我可以使用幾個速度測試,但爲了我的目的,我需要構建自己的測試。

我能做些什麼更好?這個代碼如何優化?我哪裏錯了,或者這已經是最好的紅寶石可以做的? 預先感謝您的提示和想法。

+0

什麼是每個文件中的「類型」?它總是「類型」?它是以下行的數據類型說明符嗎? –

+0

類型是用於我正在測試的另一個程序,它是特定的,但ruby從不讀取它,因此您可以忽略它。我把它放在那裏,所以你知道爲什麼迭代就像它一樣工作。 – HolgerS

回答

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 

$.是最後讀取行的行號的變量。

代替

在第二個,:

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

使用:

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

在第三個,而不是:

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的速度與其他語言進行比較,需要了解基於該步驟的多個基準測試完成每個步驟的最快方法。這也意味着你在相同的硬件和操作系統上運行,並且你的語言都被編譯爲最高效的表單。語言對存儲器使用與速度進行權衡,因此,儘管可能比另一個速度慢,但它也可能更有效率。

另外,在生產環境中進行編碼時,生成正確工作的代碼的時間必須考慮到「哪個更快」的方程中。 C的速度非常快,但是對於大多數問題,編寫程序比Ruby需要更長的時間,因爲C不像Ruby那樣握住你的手。當C代碼需要一週的時間來編寫和調試時,哪一個更快,而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 /秒的差別。在做同樣的事情的兩種方式之間沒有太大區別。如果你要處理更多的數據,那麼它很重要。對於大多數應用程序來說,這是一個有爭議的問題,因爲其他的事情會成爲瓶頸/減速,所以編寫代碼以適合您的方式,考慮到速度差異。


要顯示的Ruby版本導致差異,這裏是使用Ruby 1.8.7相同的基準測試結果:

 
1000000 
N=10 
1.8.7 

      user  system  total  real 
&: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 

      user  system  total  real 
tr  0.010000 0.000000 0.010000 ( 0.011652) 
gsub 3.010000 0.000000 3.010000 ( 3.014059) 

和:

 
1000000 
N=10 
2.0.0 

      user  system  total  real 
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 

On 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) 
+1

查閱文件的方式並沒有太大的區別,但'tr'和'split'似乎是要走的路,而'scan'看起來要慢很多。 – HolgerS

+1

'scan'正在取代一堆'gsub'和'split'調用,所以必須將它與整個鏈路進行比較。它不能自行測試。另外,使用的正則表達式會影響'scan'的速度。我可以想出許多不同的方式來提取信息,但是使用了我編寫掃描例子時想到的第一個方法;這不一定是最快的。正確書寫的模式可以非常快速,等同於提取數據的其他方式,或者大大超越它們。 –

+0

我沒有自己測試,我測試了整個代碼,速度較慢。 – HolgerS

1

這並不完全清楚你的性能問題在哪裏,但就實施而言,有幾件事情顯然不是最佳的。

如果您正在搜索並替換以刪除特定字符,請避免重複運行gsub。處理和重新處理每個字符的相同字符串需要相當長的時間。相反,這樣做在一個通:

s.gsub(/[\(\)]/, '') 

正則表達式內的[...]符號的意思是「設置如下字符」,所以在這種情況下,它是打開或關閉支架。

一個更有效的方法是tr方法是用於重新映射或刪除單個字符,通常是更快,因爲沒有正則表達式編譯或執行:

s.tr('()', '') 

另一個竅門是,如果你是看到這裏,你有一個由不帶參數的方法調用的塊模式:

map { |x| x.to_i } 

這種向下摺疊成短格式:

map(&:to_i) 

我不確定那個基準測試的速度是否會更快,但如果真的那樣我不會感到驚訝。這是一個內部生成的過程。

如果您擔心絕對速度,您可以將性能敏感部分作爲C or C++ extension to Ruby。另一種選擇是使用JRuby與某些Java來完成繁重的工作,但通常情況下,C對於像這樣的低級工作來說是最重要的。

代替
+0

我認爲在這種情況下都不應該使用gsub和tr,並且應該使用掃描(對於第三種情況嵌套)。 – sawa

+0

使用&:to_i成語實際上比map {| s | s.to_i}的習慣用法,都在jruby-1.6.7.2和ruby 1.8中。7(我在本地測試的唯一兩個) – mcfinnigan

+0

@HolgerS我認爲@mcfinnigan所暗示的是,您可以使用'scan'掃出數字並以這種方式處理它們,而不是'split'加'gsub'你現在擁有的組合。例如:'scan(/ \ d + /)'可以取出所有數字的字符串。 – tadman