JVM: Как управлять памятью вне кучи, созданной JNI - PullRequest
0 голосов
/ 05 июня 2018

Я создаю оболочку Scala вокруг библиотек Torch .Я использую Swig , чтобы создать слой клея.Это позволяет мне создавать теноры вне кучи, которые я могу освободить только путем явного вызова статического метода библиотеки.Тем не менее, я хочу использовать тензоры в обязательном порядке, не беспокоясь об освобождении памяти, так же, как любой обычный объект в Java.

Единственный способ, которым я могу думать об этом, - это (неправильно) использовать сборщик мусора JVM следующим образом:

«Диспетчер памяти» отслеживает количество отключенныхпамять кучи расходуется, и при достижении порога вызывается System.gc().

object MemoryManager {    
  val Threshold: Long = 2L * 1024L * 1024L * 1024L // 2 GB
  val FloatSize = 4
  private val hiMemMark = new AtomicLong(0)

  def dec(size: Long): Long = hiMemMark.addAndGet(-size * FloatSize)
  def inc(size: Long): Long = hiMemMark.addAndGet(size * FloatSize)

  def memCheck(size: Long): Unit = {
    val level = inc(size)
    if (level > Threshold) {
      System.gc()
    }
  }
}

Сами тензоры обернуты в класс с помощью метода finalize, который освобождает память вне кучи, например:

class Tensor private (val payload: SWIGTYPE_p_THFloatTensor) {
  def numel: Int = TH.THFloatTensor_numel(payload)

  override def finalize(): Unit = {
    val memSize = MemoryManager.dec(numel)
    TH.THFloatTensor_free(payload)
  }    
}

Создание тензора выполняется фабрикойметод, который уведомляет менеджер памяти.Например, чтобы создать тензор нулей:

object Tensor {
  def zeros(shape: List[Int]): Tensor = {
      MemoryManager.memCheck(shape.product)
      val storage = ... // boilerplate
      val t = TH.THFloatTensor_new
      TH.THFloatTensor_zeros(t, storage)
      new Tensor(t)
  }
}

Я понимаю, что это наивный подход, но можно ли мне с этим сойти?Кажется, что он работает нормально, даже при параллельной работе (которая генерирует множество лишних вызовов на System.gc(), но в противном случае ничего) Или вы можете придумать лучшее решение?

Спасибо.

1 Ответ

0 голосов
/ 06 июня 2018

Есть более детерминированный вариант - явно управляемые области памяти

Итак, примерно, если бы у нас был такой класс:

class Region private () {
  private val registered = ArrayBuffer.empty[() => Unit]
  def register(finalizer: () => Unit): Unit = registered += finalizer
  def releaseAll(): Unit = {
    registered.foreach(f => f()) // todo - will leak if f() throws
  }
}

У нас мог бы быть метод, реализующий так называемый "Шаблон ссуды ", который дает нам новый регион и затем обрабатывает освобождение

object Region {
  def run[A](f: Region => A): A = {
    val r = new Region
    try f(r) finally r.releaseAll()
  }
}

Тогда то, что требует ручного освобождения, может быть описано как принятие неявного Region:

class Leakable(i: Int)(implicit r: Region) {
  // Class body is constructor body, so you can register finalizers
  r.register(() => println(s"Deallocated foo $i"))

  def foo() = println(s"Foo: $i")
}

, котороевы могли бы использовать его без лишних шаблонов:

Region.run { implicit r =>
  val a = new Leakable(1)
  val b = new Leakable(2)
  b.foo()
  a.foo()
}

Этот код выдает следующий вывод:

Foo: 2
Foo: 1
Deallocated foo 1
Deallocated foo 2

Такой подход немного ограничивает (если вы попытаетесьчтобы присвоить Leakable переменной вне замыкания, переданного в run, ее область действия не будет повышена), но будет быстрее и гарантированно работать, даже если вызовы System.gc отключены.

...