[譯]Kotlin中的龜(List)兔(Sequence)賽跑

翻譯說明:函數

原標題: Kotlin : Slow List and Lazy Sequencepost

原文地址: medium.com/@elye.proje…性能

原文做者: Elyespa

自從Kotlin能夠兼容Java7上後,很高興的是咱們能夠輕鬆地在List上使用各類集合操做符而且能夠鏈式調用它們。可是咱們知道在某些狀況下使用List的迭代器並非最好的方式,那麼還有另外一種方式就是使用序列(sequence)翻譯

沒有背景只能辛苦工做的List列表

在咱們瞭解序列在某些狀況下爲何更好以前,讓我告訴你一些關於List的內容。日誌

List內部使用Iterator進行操做。這是一個很是勤奮的羣體,我鏈式調用它的每個操做,它都能確保沒有任何遺漏的完成。code

val list = listOf(1, 2, 3, 4, 5, 6)
list.map{ it * 2 }.filter { it % 3  == 0 }.average()
複製代碼

正如你在上面的插圖中看到的,對於每一步操做,List的每一個元素都須要被處理。cdn

爲了證實這一點,讓咱們輸出一些log日誌:blog

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list
        .map{ println("In Map"); it * 2 }
        .filter { println("In Filter");it % 3  == 0 }
println("Before Average")
println(result.average())
複製代碼

結果以下:開發

In Map
In Map
In Map
In Map
In Map
In Map
In Filter
In Filter
In Filter
In Filter
In Filter
In Filter
Before Average
9.0
複製代碼

很棒。勤奮努力地工做,並完成全部的過程。

懶惰的傢伙,Sequence序列...

好的,如今讓咱們經過調用asSequence()擴展函數來將List轉化成一個序列(Sequence)。

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list.asSequence()
        .map{ println("In Map"); it * 2 }
        .filter { println("In Filter");it % 3  == 0 }
println("Before Average")
println(result.average())
複製代碼

結果以下:

Before Average
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
9.0
複製代碼

哇,有趣...,注意到 "Before Average" 是最早輸出的,換句話說,若是我不調用 average() 函數,那麼序列(sequence)就沒有作任何操做。

它很懶,不想作任何工做,直到終端鏈接到它。終端就像是一種操做,實際上就是一個操做符擴展函數,會返回其餘類型結果(除了Sequence<T>以外),例如 sum(),average(),first()等...。甚至toList()用於將Sequence轉換爲List

除此以外,你會注意到它輸出的In MapIn Filter交叉出現。這意味着它會在經過鏈條以前一個接一個地經過鏈條,直到它經過終端,即平均操做,而後經過下一個元素。

那麼,序列Sequence到底有什麼好處呢?

若是你這樣想,想象你想要拿到集合變換後的第一個元素。

讓咱們看下List處理方式:

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list
        .map{ println("In Map $it"); it * 2 }
        .filter { println("In Filter $it");it % 3  == 0 }
println(result.first())
複製代碼

結果以下:

In Map 1
In Map 2
In Map 3
In Map 4
In Map 5
In Map 6
In Filter 2
In Filter 4
In Filter 6
In Filter 8
In Filter 10
In Filter 12
6
複製代碼

全部在一塊兒總共13行,這意味着13次操做。

讓咱們看下Sequence處理方式:

val sequence = sequenceOf(1, 2, 3, 4, 5, 6)
val result = sequence
        .map{ println("In Map $it"); it * 2 }
        .filter { println("In Filter $it");it % 3  == 0 }
println(result.first())
複製代碼

結果是:

In Filter 2
In Map 2
In Filter 4
In Map 3
In Filter 6
6
複製代碼

僅僅7行即7次操做。這意味着它只要找到第一個元素的那一刻,就會終止整個過程。

你能夠想像,這會加快整個運行的過程。

加速僅僅只適用於first()操做嗎?

讓咱們作一些試驗。

試驗Map操做

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList()

println("List Map Sum= " 
        + measureNanoTime { list.map { it * 2 }.sum() })
println("Sequence Map Sum " 
        + measureNanoTime { sequence.map { it * 2 }.sum() })

println("List Map Average " 
        + measureNanoTime { list.map { it * 2 }.average() })
println("Sequence Map Average " 
        + measureNanoTime { sequence.map { it * 2 }.average() })
複製代碼

結果是:

List Map Sum 14727907362
Sequence Map Sum 2074397969
List Map Average 11460520785
Sequence Map Average 3268960487
複製代碼
  • List: 在Map:Sum操做上花費了14.7s,在Map:Average操做上花費了11.5s
  • Sequence: 在Map:Sum操做上花費了2.1s, 在Map:Average操做上花費了3.3s

看上去像前面的有一個Map操做時,Sequence的性能會比List更快。也許它不須要像List那樣存儲map操做後的中間結果,從而會更快。

試驗Filter操做

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList()

println("List Filter Sum " 
        + measureNanoTime { list.filter { it % 3 == 0 }.sum() })
println("Sequence Filter Sum " 
        + measureNanoTime { sequence.filter { it % 3 == 0 }.sum() })

println("List Filter Average " 
        + measureNanoTime { list.filter { it % 3 == 0 }.average() })
println("Sequence Filter Average " 
        + measureNanoTime { sequence.filter { it % 3 == 0 }.average() })
複製代碼

結果是:

List Filter Sum 506351694
Sequence Filter Sum 873175271
List Filter Average 391790033
Sequence Filter Average 838510968
複製代碼
  • List: 在Filter:Sum操做上花費了0.5s,在Filter:Average操做上花費了0.4s
  • Sequence: 在Filter:Sum操做上花費了0.9s, 在Filter:Average操做上花費了0.8s

對於前面的Filter操做,Sequence比List更慢。 深刻了解函數,看起來像Sequence的Filter操做須要有更多的開銷來檢查某些狀態,而List的Filter則是一個簡單的檢查並收集新的元素。

試驗Map和Filter操做

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList()

println("List Map Filter Sum\t\t " + measureNanoTime { 
    list.map { it * 2 }.filter { it % 3 == 0 }.sum() })
println("Sequence Map Filter Sum\t " + measureNanoTime { 
    sequence.map { it * 2 }.filter { it % 3 == 0 }.sum() })

println("List Map Filter Average\t\t " + measureNanoTime { 
    list.map { it * 2 }.filter { it % 3 == 0 }.average() })
println("Sequence Map Filter Average\t " + measureNanoTime { 
    sequence.map { it * 2 }.filter { it % 3 == 0 }.average() })
複製代碼

結果是:

List Map Filter Sum 34845242323
Sequence Map Filter Sum 2820436086
List Map Filter Average 2328258876
Sequence Map Filter Average 18618444560
複製代碼
  • List: 在Map:Filter:Sum操做上花費了34.8s,在Map:Filter:Average操做上花費了2.3s
  • Sequence: 在Map:Filter:Sum操做上花費了2.8s, 在Map:Filter:Average操做上花費了18.6s

一個相對使人驚訝的結果,如Map:Filter:Sum,Sequence比List快得多,而Map:Filter:Average,List比Sequence要快得多。

試驗直接使用Sequence和List

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList()

println("List Sum " + measureNanoTime { list.sum() })
println("Sequence Sum " + measureNanoTime { sequence.sum() })

println("List Average " + measureNanoTime { list.average() })
println("Sequence Average " + measureNanoTime { sequence.average() })
複製代碼

結果是:

List Sum 91726022
Sequence Sum 592771887
List Average 101141460
Sequence Average 622616340
複製代碼
  • List: 在Sum操做上花費了0.1s,在Average操做上花費了0.1s
  • Sequence: 在Sum操做上花費了0.5s, 在Average操做上花費了0.6s

沒有任何中間操做,明顯列表List比序列Sequence要快。

總結:

  • 一、當不須要中間操做時,使用List
  • 二、當僅僅只有map操做時,使用sequence
  • 三、當僅僅只有filter操做時,使用List
  • 四、若是末端操做是first時,使用sequence
  • 五、對於沒有說起的其餘操做符或者其餘操做符的組合,請嘗試使用例子去驗證一下

譯者有話說:

首先,說下爲何要翻譯這篇博客?關於Kotlin中的Sequence和List的使用以及源碼解析相關的文章我已經寫過兩篇了,這篇博客主要吸引個人一點就是以更多運行的例子試驗和相關幽默的配圖更加形象地描述了Sequence,List的區別以及各自的使用場景。

然而,這篇博客並無深刻源碼去講解Sequence的實現,這篇以前寫的博客 淺談Kotlin中的序列(Sequences)源碼徹底解析(十) 從源碼角度帶你一步步分析Sequence序列背後的原理,關於如何正確使用Sequence和List以及各自使用場景,以前翻譯的一篇博客 [譯]Kotlin中是應該使用序列(Sequences)仍是集合(Lists)? 會有更加全面的介紹。

最後,有了這三篇文章應該更加全面理解了Sequence的原理和使用。

歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~

相關文章
相關標籤/搜索