2015-12-16 64 views
2

我不明白爲什麼下面的代碼太慢。這段代碼的目標非常簡單:我有一組點,我想分成6個桶(所以每桶100000個點)。代碼:地圖上的列表緩衝簡單循環太慢

import scala.collection.mutable.{Map, ListBuffer} 
object Main { 
    def main(args : Array[String]) = { 
    val m : Map[String, ListBuffer[Double]] = Map() 
    val labels = Array("1","2","3","4","5","6") 
    val points = Array.fill(600000){0.0} 
    var it = 0 
    val t1 = System.currentTimeMillis 
    for (i <- 0 until points.length) { 
     if(it == labels.length-1) it = 0 
     val point = points(i) 
     val currentLabel = labels(it) 
     val values = m.getOrElse(currentLabel, ListBuffer()) 
     m += (currentLabel -> (values :+ point)) 
     it += 1 
     println("it -> = " + it) 

    } 
    val t2 = System.currentTimeMillis 
    println("fill values in = " + (t2-t1) + " msecs") 
    } 
} 

訪問並追加需要一定的時間所以對我來說,這將代碼的複雜度爲O(n),其中n爲分割點的數量。我能否提供一些建議讓代碼更快?

+0

在不可變映射中插入和搜索是'O(log n)',你使用哪一個? – Lee

+3

需要優化的工作代碼屬於http://codereview.stackexchange.com/ –

+0

對不起,我添加了導入,但我使用了一個可變的Map。 – alifirat

回答

1

這裏(作爲一個單獨的答案,因爲它與我的第一個答案完全不同)是另一個「C風格」,它處理可變數量的桶。

有趣的是,在我的機器上,它比Espen Brekke的其他C式回答快兩倍。更新:我回過頭來看 - 兩者運行速度非常快,實際執行時間被啓動時間等所掩蓋。運行在100x的點數(60000000)上,Espen的速度是其兩倍(但是具有固定數量的存儲桶) - 大約220ms相比,大約420ms

更新2:Espen的最新現在或多或少像下面一樣(除了使用異常而不是明確的邊界檢查)並且毫不奇怪,以相似的速度運行。

另請注意,這兩個版本都不會創建存儲桶陣列。在我的機器上,這大約是另一個200毫秒(對於任一版本),因此大約有50%-100%的時間需要將東西放入桶中......

因此,此變體比OP的原始代碼(在我的機器上)!

object buckets2 extends App { 

val labels = Array("1","2","3","4","5","6") 
val points = Array.fill(600000){0.0} 

val count = 6 
val bucket_size = ((points.length-1)/count) + 1 

val buckets = Array.fill(count) (new Array[Double](bucket_size)); 

var b = 0 
val t1 = System.currentTimeMillis 

while (b < count) { 
    var i = b 
    var j = 0 
    val this_bucket = buckets(b) 
    while (i < points.length) { 
    this_bucket(j) = points(i) 
    i = i + count 
    j = j + 1 
    } 
    b = b + 1 

} 

val t2 = System.currentTimeMillis 

println("fill values in = " + (t2-t1) + " msecs") 

} 
+0

你的解決方案速度更快,謝謝你,現在,我會在FP解決方案和非FP解決方案之間小心處理這些問題:) – alifirat

+0

有句老話:「如果它不必返回正確的答案,我可以做它的運行速度如你所願「。我發現更多的FP方法在讓我得到正確答案方面比我以最高性能心態開始時更好。 FP性能通常很好,因爲它不是瓶頸。所以「做得對,然後加快速度(如果需要的話)」 –

1

由於@Noah在註釋中正確注意到,您不必將緩衝區推回到地圖。這應該是足夠了:

val values = m.getOrElseUpdate(currentLabel, ListBuffer()) 
values += point 

或者,如果你使用Scala工作,你可以用一個功能性的方法,這是推薦這樣做:

val labels = Array("1","2","3","4","5","6") 
val points = Array.fill(60000){2.0} 
val t1 = System.currentTimeMillis 
val m = points.zipWithIndex.groupBy { 
    case (point, i) => labels(i % labels.size) 
}.mapValues(arr => arr.map(_._1).toList) 

val t2 = System.currentTimeMillis 
println(m) 
println("fill values in = " + (t2-t1) + " msecs") 

請注意 - 這裏沒有可變的數據結構

+0

Heh。剛剛提出了完全相同的事情。這對我來說大約快1000倍。 –

+0

謝謝你的迴應,我之所以沒有使用'grouped'或'groupBy'是因爲這個代碼在點數大於一百萬時崩潰。 – alifirat

6

下重構不創造儘可能多的藏品爲點承擔,並依賴於Scala的API,

object Main { 
    def main(args : Array[String]) = { 
    val labels = Array("1","2","3","4","5","6") 
    val points = Array.fill(600000){0.0} 

    val t1 = System.currentTimeMillis 
    val xst = points.grouped(labels.size).toArray.transpose 
    val m = (labels zip xst).toMap 
    val t2 = System.currentTimeMillis 

    println("fill values in = " + (t2-t1) + " msecs") 
    } 
} 

儘管原始代碼需要幾分鐘,但這個代碼耗時約700毫秒。

此代碼避免索引引用和更新現有集合。

更新與我填的是內存(Alifirat)

object Main { 
    def main(args : Array[String]) = { 
    val labels = Array("1","2","3","4","5","6", "7") 
    val points = Array.fill(7000000){0.0} 

    val t1 = System.currentTimeMillis 
    val xst = points.grouped(labels.size).toArray.transpose 
    val m = (labels zip xst).toMap 
    val t2 = System.currentTimeMillis 

    println("fill values in = " + (t2-t1) + " msecs") 
    } 
} 

相同的代碼,但在7 000 000分,連續7桶運行的代碼。

更新

嘗試

scala -J-Xmx4g 

然後粘貼更新的代碼。

更新

如若最終地圖上的0.0陣列,下面的證明開70μm萬點相當快,

val m = labels.map(l => l -> Array.fill(10*1000*1000){0.0}).toMap 

在情況下的性能是必不可少的,C爲導向的方法前面已經建議我證明自己的記憶和時間有效率,可能會犧牲可擴展性和組合性。

+0

很好的與'分組'。我總是忘記它的存在 – Archeg

+0

謝謝你的迴應,我沒有使用'分組'的原因是因爲如果點數大於一百萬,這個代碼會崩潰。 – alifirat

+0

@alifirat如果你看'分組'實現('groupBy'幾乎相同),你會發現它的實現和你正在做的完全一樣。它構造了可變的生成器併爲其增加了價值。只有當你做這些事情時它不會做空插入,所以它實際上要快得多。如果你的代碼沒有,它不會崩潰。你可能用錯誤的方式編碼了一些東西 – Archeg

2

剝皮貓的另一種方式:

val buckets = Array.fill(labels.length)(ArrayBuffer.empty[Double]) 
points.zipWithIndex.foreach{case(p, i) => buckets(i%labels.length) += p} 
(labels zip buckets).toMap 

我已經無法正常基準,但它是我試過最快的東西(沒有任何更多 - 看到我的其他答案)

0

有時正確的做法是改變功能風格。 我已經寫了c風格Scala的解決方案。

def main(args : Array[String]) = { 
val labels = Array("1","2","3","4","5","6") 
val points = Array.fill(600000){0.0} 


val numChannels=6; 
val channels=new Array[Array[Double]](numChannels); 
for(i<-0 until numChannels){ 
    channels(i)=new Array[Double]((points.length/6)); 
} 

var from=0; 

val t1 = System.currentTimeMillis 

for(i<-0 until numChannels){ 
    val channel=channels(i); 
    from=i 
    var to=0; 
    try{ 
    while(true){ 
     channel(to)=points(from); 
     to=to+1; 
     from=from+numChannels; 
    } 
    } catch { 
    case e:ArrayIndexOutOfBoundsException =>{ 
     //Ok finished this channel 
    } 
    } 
} 

val t2 = System.currentTimeMillis 

println("fill values in = " + (t2-t1) + " msecs") 
println("from:"+from) 
} 

該解決方案只處理需要完成的工作,不再需要處理。

在我的mashine上它使用8毫秒。
elm的代碼使用309毫秒。
原來花了221226毫秒。

雖然我愛Scala,但重要的是要記住它很好地隱藏了其操作的計算成本。對集合的大多數操作至少引入了每個元素的幾個過程調用。

+0

我知道您的代碼具有良好性能,但問題是,我不知道頻道的數量,所以想象我有35個頻道,我需要更改您的代碼.. – alifirat

+0

@alifirat,那麼你最終會得到像我的答案代碼。您可以預先創建正確長度的數組,而不是使用ArrayBuffers,這可以加快速度。 –

+0

如果點數組長度爲6,則給出1個元素太長的數組。 –