Scala中的Dot产品,没有堆分配

时间:2016-10-07 09:35:17

标签: scala memory-management memory-leaks profiler

我有一个带有一些密集算术的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内存快照之外,我还是会对其他调试此类问题的方法有所了解。

1 个答案:

答案 0 :(得分:2)

中的范围实施
for (i <- 0 to 1000000)
  dot(vector, vector)

可能会使用一些内存,或者只是足够慢以让JVM在后台分配其他内容并破坏测试中使用的脆弱的测量方法。

尝试将这些行修改为while循环,例如。

(这篇文章的原始版本说for()等同于map(),这是错误的。它等同于foreach(),因为它没有yield子句。)