【问题标题】:Why is List<Float>.toFloatArray so slow compared to filling a FloatArray? (bug?)为什么 List<Float>.toFloatArray 比填充 FloatArray 慢? (漏洞?)
【发布时间】:2018-10-17 05:46:15
【问题描述】:

在我的 Android 手机应用程序中,有许多大型浮点列表 (100000+)。为了将它们与 OpenGl 一起使用,列表先转换为 FloatArrays,然后再转换为字节缓冲区。我遇到了性能问题,几个小时后,Kotlin .toFloatArray() 函数似乎很慢。

我的三星 Galaxy 手机上的一些测试结果:

                        elapsed time(msec)  time/float(microsec)        
                            max min  avg 
fillFloatArray               70  39  59,7      6.0
fillFloatList               472 357 444,1     44.4
convertListToArrayLooping   165 101 155,2     15.5
convertWithToFloatArray     388 318 367,3     36.7

Number of floats    10000               
runs                   10
(the average time / float is the same for a list of 100000 floats)

用值填充浮点数组很快,用浮点数填充列表要慢 7.4 倍。

但是,通过标准函数 List.toFoatArray() 将 List 转换为浮点数组比简单地填充数组慢 6.1 倍,也比简单地填充数组慢 2.4 倍循环列表。它几乎和用浮点数填充一个空列表一样慢!

我预计转换时间更接近于简单填充数组的时间?

.toFloatArray() 的性能有意义吗?将 List 转换为 Bytebuffer 的最快或至少更快的方法是什么?

下面的小测试程序,供参考。

class PlayGround {
private val stopwatch = Timing()

private val arraylist: MutableList<Float> = arrayListOf()
private val fillFloatArray = Results()
private val fillFloatList = Results()
private val convertListToArrayLooping = Results()
private val convertWithToFloatArray = Results()

fun checkSpeed () {
    for (i in 0 until 10) {     // do the timing ten times to get an average
        // filling a floatarray with 10000 floats
        stopwatch.start()
        var array = FloatArray(10000, { it * 1f })
        fillFloatArray.add(stopwatch.stop())

        // filling a list with 10000 floats
        stopwatch.start()
        for (i in 0 until 10000) {
            arraylist.add(i * 1f)
        }
        fillFloatList.add(stopwatch.stop())

        // converting the fulllist to a floatarray by looping
        stopwatch.start()
        for (i in 0 until arraylist.size) {
            array[i] = arraylist[i]
        }
        convertListToArrayLooping.add(stopwatch.stop())

        // converting the fullist to a floatarray with toFloatArray
        stopwatch.start()
        array = arraylist.toFloatArray()
        convertWithToFloatArray.add(stopwatch.stop())
        arraylist.clear()
    }
}

private class Results {
    private var max = Long.MIN_VALUE
    private var min = Long.MAX_VALUE
    private var sum = 0L
    private var avg = 0L
    var measurePoints: MutableList<Long> = arrayListOf()

    fun add (msrp: Long) {
        measurePoints.add(msrp)
        if (msrp > max) max = msrp
        if (msrp < min) min = msrp
        sum += msrp
        avg = sum / measurePoints.size
    }

    fun clear() {
        measurePoints.clear()
        max = Long.MIN_VALUE
        min = Long.MAX_VALUE
        sum = 0L
        avg = 0L
    }

    fun max(): Long { return max}
    fun min(): Long { return min}
    fun sum(): Long { return sum}
    fun avg(): Long { return avg}

}
}

【问题讨论】:

  • 注意toFloatArray也必须分配一个新数组,所以这不一定是一个公平的比较。
  • 您没有指定arraylist 的初始大小。使用arraylist = ArrayList&lt;Float&gt;(10000)
  • 标准函数是Collection.toFloatArray,但您使用索引迭代将数据从列表传输到数组。 stdlib fun 无法使用索引迭代。
  • @OliverCharlesworth 说得太远了,但是对于 10000 个浮点数,这将花费 60 毫秒,但仍然使 toFloatArray 比循环遍历列表要慢?
  • @miensol,我重新运行它并预先分配,它不会改变结果(我很惊讶)

标签: android arrays list opengl-es kotlin


【解决方案1】:

这是我的代码,它试图将两种转换方式放在比您的测试更平等的基础上:

import kotlin.system.measureTimeMillis

const val PROBLEM_SIZE = 1_000_000

fun main(args: Array<String>) {
    val array = FloatArray(PROBLEM_SIZE, { it * 1f })
    val list: List<Float> = array.map { it }

    println("warmup")
    (1..20).forEach {
            list.myToFloatArray()
            list.toFloatArray()
    }

    println("list.toFloatArray " + (1..10).map {
        gc()
        measureTimeMillis {
            list.myToFloatArray()
        }
    }.average())

    println("collection.toFloatArray " + (1..10).map {
        gc()
        measureTimeMillis {
            list.toFloatArray()
        }
    }.average())
}

fun List<Float>.myToFloatArray(): FloatArray {
    val result = FloatArray(size)
    for (index in 0 until size)
        result[index] = this[index]
    return result
}

fun gc() {
    System.gc()
    System.gc()
}

这是一个典型的结果

list.toFloatArray 3.4
collection.toFloatArray 5.2

首先,差异不如您的测试那么明显,其次,标准函数是Collection.toFloatArray,不能使用索引迭代。

【讨论】:

  • 优秀的代码,(我认为我仍然需要学习 Kotlin :-)。在您的情况下,索引方法也快 1.5 倍。如果 Collection.toFloatArray 这么慢,我必须重新设计我的应用程序,避免使用列表并仅使用裸浮点数组。
  • 我建议在任何情况下,列表的内存开销都非常大。
猜你喜欢
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-30
  • 2017-08-25
  • 2017-05-29
  • 2014-10-03
  • 2014-02-09
相关资源
最近更新 更多