没有堆分配的 Scala 中的点积
Dot product in Scala without heap allocation
我有一个 Scala 项目,其中包含一些密集型算法,有时它分配浮点数的速度比 GC 清理它们的速度还快。 (这不是关于保留引用导致的内存泄漏,只是临时值的快速内存消耗。)我尝试使用原始类型的数组,并在可能的情况下重用它们,但仍然有一些新的分配潜入。
有一个让我很疑惑的,比如:
import org.specs2.mutable.Specification
class CalcTest extends Specification {
def dot(a: Array[Float], b: Array[Float]): Float = {
require(a.length == b.length, "array size mismatch")
val n = a.length
var sum: Float = 0f
var i = 0
while (i < n) {
sum += a(i) * b(i)
i += 1
}
sum
}
val vector = Array.tabulate(1000)(_.toFloat)
"calculation" should {
"use memory sparingly" >> {
val before = Runtime.getRuntime().freeMemory()
for (i <- 0 to 1000000)
dot(vector, vector)
val after = Runtime.getRuntime().freeMemory()
(before - after) must be_<(1000L) // actual result above 4M
}
}
}
我希望它只使用堆栈内存来计算点积,但显然它在堆上每次调用分配大约 4 个字节。这听起来可能不多,但它在我的代码中加起来很快。
我怀疑总和,但从字节码输出来看,它似乎在堆栈上:
aload 1
arraylength
istore 3
fconst_0
fstore 4
iconst_0
istore 5
l2
iload 5
iload 3
if_icmpge l3
fload 4
aload 1
iload 5
faload
aload 2
iload 5
faload
fmul
fadd
fstore 4
iload 5
iconst_1
iadd
istore 5
_goto l2
l3
fload 4
freturn
堆上的是 return 值吗?有什么办法可以完全避免这种开销?是否有更好的方法来调查和解决此类内存问题?
从我的项目的 visualVM 输出中,我只看到我分配了非常多的浮点数。像这样的小对象很难被快速分配。它对于大对象和长时间拍摄的内存快照更有用。
更新:
我太专注于功能代码,我错过了测试中的问题。如果我用while循环重写,就成功了:
var i = 0
while (i < 1000000) {
dot(vector, vector)
i += 1
}
除了像这样的测试和使用 visualVM 内存快照之外,我仍然希望有更多关于调试此类问题的其他方法的想法。
中的范围实施
for (i <- 0 to 1000000)
dot(vector, vector)
可能会使用一些内存,或者只是速度太慢,让 JVM 在后台分配其他东西并破坏测试中使用的脆弱测量方法。
尝试将这些行修改为 while 循环,例如。
(这个post原来的版本说for()等价于map(),这是错误的,这里等价于foreach(),因为它没有yield子句。)
我有一个 Scala 项目,其中包含一些密集型算法,有时它分配浮点数的速度比 GC 清理它们的速度还快。 (这不是关于保留引用导致的内存泄漏,只是临时值的快速内存消耗。)我尝试使用原始类型的数组,并在可能的情况下重用它们,但仍然有一些新的分配潜入。
有一个让我很疑惑的,比如:
import org.specs2.mutable.Specification
class CalcTest extends Specification {
def dot(a: Array[Float], b: Array[Float]): Float = {
require(a.length == b.length, "array size mismatch")
val n = a.length
var sum: Float = 0f
var i = 0
while (i < n) {
sum += a(i) * b(i)
i += 1
}
sum
}
val vector = Array.tabulate(1000)(_.toFloat)
"calculation" should {
"use memory sparingly" >> {
val before = Runtime.getRuntime().freeMemory()
for (i <- 0 to 1000000)
dot(vector, vector)
val after = Runtime.getRuntime().freeMemory()
(before - after) must be_<(1000L) // actual result above 4M
}
}
}
我希望它只使用堆栈内存来计算点积,但显然它在堆上每次调用分配大约 4 个字节。这听起来可能不多,但它在我的代码中加起来很快。
我怀疑总和,但从字节码输出来看,它似乎在堆栈上:
aload 1
arraylength
istore 3
fconst_0
fstore 4
iconst_0
istore 5
l2
iload 5
iload 3
if_icmpge l3
fload 4
aload 1
iload 5
faload
aload 2
iload 5
faload
fmul
fadd
fstore 4
iload 5
iconst_1
iadd
istore 5
_goto l2
l3
fload 4
freturn
堆上的是 return 值吗?有什么办法可以完全避免这种开销?是否有更好的方法来调查和解决此类内存问题?
从我的项目的 visualVM 输出中,我只看到我分配了非常多的浮点数。像这样的小对象很难被快速分配。它对于大对象和长时间拍摄的内存快照更有用。
更新:
我太专注于功能代码,我错过了测试中的问题。如果我用while循环重写,就成功了:
var i = 0
while (i < 1000000) {
dot(vector, vector)
i += 1
}
除了像这样的测试和使用 visualVM 内存快照之外,我仍然希望有更多关于调试此类问题的其他方法的想法。
for (i <- 0 to 1000000)
dot(vector, vector)
可能会使用一些内存,或者只是速度太慢,让 JVM 在后台分配其他东西并破坏测试中使用的脆弱测量方法。
尝试将这些行修改为 while 循环,例如。
(这个post原来的版本说for()等价于map(),这是错误的,这里等价于foreach(),因为它没有yield子句。)