我有一个带有一些密集算术的Scala项目,它有时会比GC更快地分配Floats。 (这不是由保留引用引起的内存泄漏,只是临时值的快速内存消耗。)我尝试使用带有基本类型的数组,并在可能时重用它们,但仍然会有一些新的分配潜入。
令我困惑的一件事,例如:
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
它是堆上的返回值吗?有没有办法完全避免这种开销?是否有更好的方法来调查和解决此类内存问题?
从我的项目的visualVM输出中,我只看到我分配了大量的Floats。很难跟踪这样的小物体,快速分配。它对于长时间拍摄的大型对象和内存快照更有用。
更新
我如此专注于功能代码,我错过了测试中的问题。如果我用while循环重写它,它会成功:
var i = 0
while (i < 1000000) {
dot(vector, vector)
i += 1
}
除了像这样的测试和使用visualVM内存快照之外,我还是会对其他调试此类问题的方法有所了解。
答案 0 :(得分:2)
中的范围实施
for (i <- 0 to 1000000)
dot(vector, vector)
可能会使用一些内存,或者只是足够慢以让JVM在后台分配其他内容并破坏测试中使用的脆弱的测量方法。
尝试将这些行修改为while循环,例如。
(这篇文章的原始版本说for()等同于map(),这是错误的。它等同于foreach(),因为它没有yield子句。)