Kotlin performance optimization tool - Sqeuence Principle Analysis

Foreword

This article introduces the concept and use of Kotlin in sequence (Sequence), and describes the principles behind the inert set of operations to optimize performance of the call to the collection chain.

table of Contents

Kotlin performance optimization tool - Sqeuence Principle Analysis

Sequence (Sequence)

concept

When using Kotlin set operator calls for the chain, for example, map and filter, is created inside the intermediate collection functions, such as the following example, the use of selected map and filter member male gender in the User set, the result is a collection.

users.map(User :: sex)
     .filter {it.sex.equals("male")}

The use of sequence

Usage sequence is very simple, just add asSeqence again after the set () function can be

users.asSequence()
     .map(User :: sex)
     .filter {it.sex.equals("male")}

Here spots a concept which User :: user is a member of references, detailed below

Member reference (Member References)

concept

Your reference member can easily call a member of a class, this member comprising a corresponding class property or method. Before the double colon is referenced class, the double colon is the need to return the name of the property or method, is shown below returns the sex attributes User members:

User :: sex

Reference member can easily be assigned to variables or other functions, such as the above example to find the male gender, it can be written with a slightly more complex, as follows:

users.map(user : User -> user.sex)
     .filter {it.sex.equals("male")}

Visible members cited the wording more readable.

Talk sequence

Let's go back sequence introduced. When using map and filter mentioned above, will be created in the middle of a set of internal function, which causes a problem if the source list, that is, users in particular, multi-element, chaining collection will become very inefficient, because the creation a collection of more intermediate. If converted by the first processing set to be asSequence () method sequence, then map and filter operations, will become very efficient. Whether using a sequence of set operations, there are few prerequisites, if used improperly, it will cause loss of performance. Here summarize usage scenarios:
Here Insert Picture Description

Performance test sequence

上文提到,是否使用序列的条件之一是处理大量数据,那么这个阈值究竟是多少?下面来进行一个性能测试,构造一个商品列表,其中每个商品包含商品名和价格两个属性,现在要求出对每个商品的价格加价 100 后,价格为奇数 的商品的个数,这里用到了 count() 方法,作用是求得集合内满足 count 条件的元素的个数,代码如下:

/**
 * 商品类
 */
data class Commodity(var name: String, var price: String)
import java.util.*

fun main(args: Array<String>) {

    val commodityList = ArrayList<Commodity>()

    for (i in 0..1000000) {
        val goods = Commodity("商品 $i", i * 5)
        commodityList.add(goods)
    }

    val startTime = System.currentTimeMillis()

    commodityList
            .asSequence() // 使用此函数代表使用 Kotlin 序列功能
            .map { it.price + 100 }
            .count { it % 2 != 0 }

    println("consume time is ${System.currentTimeMillis() - startTime} ms")

}

测试结果折线图如下,其中横坐标为集合内元素的个数,纵坐标为代码执行时间,橙色线代表未使用序列,蓝色线代表使用序列:
Here Insert Picture Description
表格对比如下:
Here Insert Picture Description
由图可得出如下结论:

  • 上文提到的阈值大致为「一百万」个元素,大于该阈值时,使用序列大致能带来 90 % 的性能提升
  • 在小于「十万」个元素时,使用序列反而会造成性能下降

    为什么序列会提高集合操作的性能?

    1. 序列对集合的操作是惰性的。
    2. 不需要额外的创建中间集合保存链式操作的中间结果

对于第一点,惰性这个词可能给人带来迷惑,这和使用序列后,集合的计算方式有关,下面来介绍这一点,在下面这个例子中,你需要在一个整型数据集合,将每个数乘以 2 ,并找出集合中小于 10 的第一个元素。

var result = listOf(2,4,6,8,10).asSequence
                  .map(it * 2)
                  .find(it > 10)

find() 方法的作用是在集合中查找满足条件的第一个元素,并返回查找到的值。下图是 Kotlin 使用序列进行处理
Here Insert Picture Description
由图可知,所谓惰性,就是在使用 map 或 filter 等操作符的时候,在代码的执行顺序上,不会优先遍历所有的集合,即偷个懒,先对集合中第一个元素进行 map 和 filter, 然后对该元素执行 find 操作,发现满足 find 的条件,就立刻返回结果,由此可见,在有 map 和 find 或 last 等操作符组合时,序列的性能优化能发生最大的作用。

小结

  1. 在进行集合操作时,使用序列操作符,可以降低集合操作的时间和占用的空间,降低时间的原因是惰性操作,降低空间占用的原因是序列在执行操作时不会创建中间集合。
  2. 序列操作虽好,但也要视业务场景决定是否使用,否则反而会降低效率。

Guess you like

Origin blog.51cto.com/14332859/2402073