Я пытаюсь провести параллельное программирование с помощью Scala и Akka, для которых я новичок. У меня довольно простое приложение Monte Carlo Pi (аппроксимирует pi по кругу), который я построил на нескольких языках. Однако производительность версии, которую я построила в Akka, меня озадачивает.Akka - худшее исполнение с большим количеством актеров
У меня есть последовательная версия, написанная на чистой скале, которая имеет тенденцию к выполнению примерно 400 мс.
По сравнению с одним работником-актером версия Акка занимает около 300-350 мс, однако, поскольку я увеличиваю количество участников, это время резко возрастает. С 4 актерами время может составлять от 500 мс до 1200 мс или выше.
Количество итераций разделяется между действующими участниками, поэтому в идеале производительность должна быть лучше, чем больше из них есть, в настоящее время она становится значительно хуже.
Мой код
object MCpi{
//Declare initial values
val numWorkers = 2
val numIterations = 10000000
//Declare messages that will be sent to actors
sealed trait PiMessage
case object Calculate extends PiMessage
case class Work(iterations: Int) extends PiMessage
case class Result(value: Int) extends PiMessage
case class PiApprox(pi: Double, duration: Double)
//Main method
def main(args: Array[String]): Unit = {
val system = ActorSystem("MCpi_System") //Create Akka system
val master = system.actorOf(Props(new MCpi_Master(numWorkers, numIterations))) //Create Master Actor
println("Starting Master")
master ! Calculate //Run calculation
}
}
//Master
class MCpi_Master(numWorkers: Int, numIterations: Int) extends Actor{
var pi: Double = _ // Store pi
var quadSum: Int = _ //the total number of points inside the quadrant
var numResults: Int = _ //number of results returned
val startTime: Double = System.currentTimeMillis() //calculation start time
//Create a group of worker actors
val workerRouter = context.actorOf(
Props[MCpi_Worker].withRouter(RoundRobinPool(numWorkers)), name = "workerRouter")
val listener = context.actorOf(Props[MCpi_Listener], name = "listener")
def receive = {
//Tell workers to start the calculation
//For each worker a message is sent with the number of iterations it is to perform,
//iterations are split up between the number of workers.
case Calculate => for(i <- 0 until numWorkers) workerRouter ! Work(numIterations/numWorkers);
//Receive the results from the workers
case Result(value) =>
//Add up the total number of points in the circle from each worker
quadSum += value
//Total up the number of results which have been received, this should be 1 for each worker
numResults += 1
if(numResults == numWorkers) { //Once all results have been collected
//Calculate pi
pi = (4.0 * quadSum)/numIterations
//Send the results to the listener to output
listener ! PiApprox(pi, duration = System.currentTimeMillis - startTime)
context.stop(self)
}
}
}
//Worker
class MCpi_Worker extends Actor {
//Performs the calculation
def calculatePi(iterations: Int): Int = {
val r = scala.util.Random // Create random number generator
var inQuadrant: Int = 0 //Store number of points within circle
for(i <- 0 to iterations){
//Generate random point
val X = r.nextFloat()
val Y = r.nextFloat()
//Determine whether or not the point is within the circle
if(((X * X) + (Y * Y)) < 1.0)
inQuadrant += 1
}
inQuadrant //return the number of points within the circle
}
def receive = {
//Starts the calculation then returns the result
case Work(iterations) => sender ! Result(calculatePi(iterations))
}
}
//Listener
class MCpi_Listener extends Actor{ //Recieves and prints the final result
def receive = {
case PiApprox(pi, duration) =>
//Print the results
println("\n\tPi approximation: \t\t%s\n\tCalculation time: \t%s".format(pi, duration))
//Print to a CSV file
val pw: FileWriter = new FileWriter("../../../..//Results/Scala_Results.csv", true)
pw.append(duration.toString())
pw.append("\n")
pw.close()
context.system.terminate()
}
}
Равнина Scala последовательная версия
object MCpi {
def main(args: Array[String]): Unit = {
//Define the number of iterations to perform
val iterations = args(0).toInt;
val resultsPath = args(1);
//Get the current time
val start = System.currentTimeMillis()
// Create random number generator
val r = scala.util.Random
//Store number of points within circle
var inQuadrant: Int = 0
for(i <- 0 to iterations){
//Generate random point
val X = r.nextFloat()
val Y = r.nextFloat()
//Determine whether or not the point is within the circle
if(((X * X) + (Y * Y)) < 1.0)
inQuadrant += 1
}
//Calculate pi
val pi = (4.0 * inQuadrant)/iterations
//Get the total time
val time = System.currentTimeMillis() - start
//Output values
println("Number of Iterations: " + iterations)
println("Pi has been calculated as: " + pi)
println("Total time taken: " + time + " (Milliseconds)")
//Print to a CSV file
val pw: FileWriter = new FileWriter(resultsPath + "/Scala_Results.csv", true)
pw.append(time.toString())
pw.append("\n")
pw.close()
}
}
Любые предложения о том, почему это происходит и как я могу улучшить производительность будет очень приветствуется.
Edit: Я хотел бы поблагодарить всех вас за ваши ответы, это мой первый вопрос на этом сайте и все ответы являются чрезвычайно полезными, у меня есть много, чтобы посмотреть и сейчас :)
В этом случае некоторую информацию о том, какой процессор (ы) вы используете это, вероятно, полезно. –
1) Пожалуйста, разместите свой код на SO. Отформатируйте его перед публикацией. 2) Что вы ожидаете от реализации актера, когда вы выполняете метод «calculatePi» несколько раз, что из того, что я вижу, эквивалент вашей последовательной реализации? И из того, что я вижу, вы просто вычисляете PI несколько раз (количество вычислений эквивалентно количеству действующих участников, что, вероятно, является объяснением замедления)? Поправьте меня если я ошибаюсь. 3) Считаете ли вы, что вы можете ничего не выиграть, используя модель актера в этом случае? –
@ Процессор Jasper-M - это четырехъядерный ядро Intel i7-4510U @ 3.1GHz @Branislav 1) Хорошо, я попробую обновить сообщение с кодом, когда я буду свободен позже. 2) 'calculatePi' запускается каждым рабочим, он генерирует множество случайных точек и измеряет, находятся ли эти точки в пределах« круга »определенного размера (в данном случае 1.0), а затем возвращает количество точек в круге (quadSum), как только результаты возвращаются от каждого рабочего, расчет выполняется один раз, чтобы определить, что такое Pi (в мастер-актере). 3) Я предположил, что у меня будет какое-то увеличение производительности, разделяющее работу над несколькими актерами. – Cipher478