Как предотвратить истощение акторов в присутствии других длительно работающих актеров?

Это использование Акторов Scala 2.8. У меня есть длительная работа, которую можно распараллелить. Он состоит из около 650 000 единиц работы. Я разделил его на 2600 различных отдельных подзадач, и для каждой из них я создал нового актера:

actor {
  val range = (0L to total by limit)
  val latch = new CountDownLatch(range.length)
  range.foreach { offset =>
    actor {
      doExpensiveStuff(offset,limit)
      latch.countDown
    }
  }
  latch.await
}

Это работает довольно хорошо, но в целом для выполнения требуется 2 + час. Проблема в том, что тем временем любые другие акторы, которые я создаю для выполнения обычных задач, кажутся голодными из-за начальных 2600 акторов, которые также терпеливо ждут своего времени для запуска в потоке, но ждали дольше, чем любые новые акторы, которые пойдем.

Как я могу избежать этого голода?

Начальные мысли:

  • Вместо 2600 актеров используйте одного актера, который последовательно продирается через большую кучу работы. Мне это не нравится, потому что я бы хотел, чтобы эта работа закончилась раньше, разделив ее.
  • Вместо 2600 актеров используйте двух актеров, каждый из которых обрабатывает свою половину общего набора работы. Это может сработать лучше, а что, если у моей машины 8 ядер? Скорее всего, я бы хотел использовать больше.

ОБНОВЛЕНИЕ

Некоторые люди вообще сомневаются в использовании Актеров, особенно потому, что возможность передачи сообщений не использовалась рабочими. Я предполагал, что Actor - это очень легкая абстракция вокруг ThreadPool на том же уровне производительности или почти на том же уровне производительности, что и при простом кодировании выполнения на основе ThreadPool вручную. Поэтому я написал небольшой тест:

import testing._
import java.util.concurrent._
import actors.Futures._

val count = 100000
val poolSize = 4
val numRuns = 100

val ActorTest = new Benchmark {
  def run = {
    (1 to count).map(i => future {
      i * i
    }).foreach(_())
  }
}

val ThreadPoolTest = new Benchmark {
  def run = {
    val queue = new LinkedBlockingQueue[Runnable]
    val pool = new ThreadPoolExecutor(
          poolSize, poolSize, 1, TimeUnit.SECONDS, queue)
    val latch = new CountDownLatch(count)
    (1 to count).map(i => pool.execute(new Runnable {
      override def run = {
        i * i
        latch.countDown
      }
    }))
    latch.await
  }
}

List(ActorTest,ThreadPoolTest).map { b =>
  b.runBenchmark(numRuns).sum.toDouble / numRuns
}

// List[Double] = List(545.45, 44.35)

Я использовал абстракцию Future в ActorTest, чтобы не передавать сообщение обратно другому актору, чтобы сигнализировать о том, что работа выполнена. Я был удивлен, обнаружив, что мой Актерский код был более чем в 10 раз медленнее. Обратите внимание, что я также создал свой ThreadPoolExecutor с начальным размером пула, с которым создается пул акторов по умолчанию.

Оглядываясь назад, кажется, что я, возможно, злоупотреблял абстракцией Actor. Я' m собираюсь изучить возможность использования отдельных пулов потоков для этих отдельных, дорогих и длительных задач.

5
задан Collin 9 November 2010 в 13:47
поделиться