To używa Scala 2.8 Aktorów. Mam długą pracę, którą można zrównoleglić. Składa się z około 650 000 jednostek pracy. Dzielę go do 2600 różnych oddzielnych podzadań, a dla każdego z nich utworzyć nowy Aktor:Jak zapobiegać głodowaniu aktorów w obecności innych długoletnich aktorów?
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
}
to działa dość dobrze, ale ogólnie trwa 2 + H, aby zakończyć. Problem polega na tym, że w międzyczasie inni aktorzy, którzy tworzą zwyczajne zadania, wydają się być zagubieni przez pierwszych 2600 aktorów, którzy cierpliwie czekają, aż nadejdzie ich czas na wątek, ale czekali dłużej niż nowi aktorzy. zbliżać się.
Jak mogę uniknąć tego głodu?
myśli wstępne:
- Zamiast 2600 aktorów, użyj jednego aktora, który kolejno pługi przez stertę pracy. Nie przepadam za tym, ponieważ chciałbym, aby ta praca skończyła się wcześniej, dzieląc ją.
- Zamiast 2600 aktorów użyj dwóch aktorów, z których każdy przetwarza inną połowę całego zestawu zadań. To może działać lepiej, ale co, jeśli moja maszyna ma 8 rdzeni? Prawdopodobnie chciałbym wykorzystać więcej.
UPDATE
Niektórzy ludzie kwestionowali wykorzystanie Aktorzy w ogóle, zwłaszcza, że zdolność przechodzenia wiadomość nie był używany w ciągu pracowników. Przyjąłem, że aktor był bardzo lekką abstrakcją wokół ThreadPool na poziomie lub w pobliżu tego samego poziomu wydajności, po prostu ręcznie kodując wykonanie oparte na ThreadPool. Więc napisałem trochę odniesienia:
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)
użyłem Future abstrakcję w ActorTest uniknąć przekazując wiadomość z powrotem do innego uczestnika, aby zasygnalizować praca została wykonana. Zaskoczyło mnie, że mój kod aktora był ponad 10 razy wolniejszy. Zauważ, że utworzyłem również swój ThreadPoolExecutor z początkowym rozmiarem puli, z którym tworzona jest domyślna pula aktorów.
Patrząc wstecz, wydaje mi się, że prawdopodobnie nadużyłem abstrakcji Aktora. Zamierzam przyjrzeć się użyciu osobnych ThreadPools dla tych odrębnych, kosztownych, długotrwałych zadań.
Nic w opisywanym problemie w ogóle nie wymaga aktorów. Ponieważ dzielisz pracę na kilka identycznych części, możesz po prostu użyć przyszłości - zobacz moją odpowiedź poniżej: –