patternMinor
Monte-Carlo method to estimate Pi runs slower in multiple threads, than in single thread
Viewed 0 times
estimatemontemethodthreadsthanslowersinglethreadmultiplecarlo
Problem
I'm learning Scala, and I wrote a program to estimate the value of Pi. When I'm using multiple threads, it takes 5-6 times longer to calculate the same iterations. I wrote a similar program in Java, with plain old threads, and with
Why is this the case?
(I have 4 cores in my processor, and during the multithreading calculation, the processor usage goes up to 100%.)
```
import java.util.Random
import java.util.concurrent.Executors
import java.util.concurrent.Callable
object Main {
def main(args: Array[String]): Unit = {
println("Calculating PI using the Monte-Carlo method")
val n = 10000000
evaluatePiCalculator(new SimplePiCalculator(n))
evaluatePiCalculator(new ThreadedPiCalculator(n))
}
def evaluatePiCalculator(calc: PiCalculator) = {
val pi = time { calc.calculate() }
println(pi)
val d = Math.abs(Math.PI - pi)
println(d)
}
def timeR: R = {
val t0 = System.nanoTime()
val result = block // call-by-name
val t1 = System.nanoTime()
val tdiff = t1 - t0
println("Elapsed time: " + tdiff + "ns" + " " + (tdiff / 1000000) + "ms")
result
}
}
object Circle {
class Point(val x: Double, val y: Double) {
def isInside: Boolean = Math.sqrt(x x + y y) < 1.0
}
val random = new Random()
private def randomPoint(): Point = {
def generate() = random.nextDouble() * 2 - 1
new Point(generate(), generate())
}
def tryToHit(): Boolean = randomPoint().isInside
}
abstract class PiCalculator() {
def calculate(): Double
}
class SimplePiCalculator(n: Int) extends PiCalculator with Callable[Int] {
override def call(): Int = calculateHits()
def calculateHits(): Int = {
var hits = 0
for (i <- 1 to n) {
if (Circle.tryToHit()) hits += 1
}
hits
}
def calculate(): Double = 4.0 * calculateHits / n
}
class ThreadedPiCalculator(n: Int) extends PiCalculator {
val threads = 4
val pool = Executors.newFixedThread
ExecutorService , and the results are the same.Why is this the case?
(I have 4 cores in my processor, and during the multithreading calculation, the processor usage goes up to 100%.)
```
import java.util.Random
import java.util.concurrent.Executors
import java.util.concurrent.Callable
object Main {
def main(args: Array[String]): Unit = {
println("Calculating PI using the Monte-Carlo method")
val n = 10000000
evaluatePiCalculator(new SimplePiCalculator(n))
evaluatePiCalculator(new ThreadedPiCalculator(n))
}
def evaluatePiCalculator(calc: PiCalculator) = {
val pi = time { calc.calculate() }
println(pi)
val d = Math.abs(Math.PI - pi)
println(d)
}
def timeR: R = {
val t0 = System.nanoTime()
val result = block // call-by-name
val t1 = System.nanoTime()
val tdiff = t1 - t0
println("Elapsed time: " + tdiff + "ns" + " " + (tdiff / 1000000) + "ms")
result
}
}
object Circle {
class Point(val x: Double, val y: Double) {
def isInside: Boolean = Math.sqrt(x x + y y) < 1.0
}
val random = new Random()
private def randomPoint(): Point = {
def generate() = random.nextDouble() * 2 - 1
new Point(generate(), generate())
}
def tryToHit(): Boolean = randomPoint().isInside
}
abstract class PiCalculator() {
def calculate(): Double
}
class SimplePiCalculator(n: Int) extends PiCalculator with Callable[Int] {
override def call(): Int = calculateHits()
def calculateHits(): Int = {
var hits = 0
for (i <- 1 to n) {
if (Circle.tryToHit()) hits += 1
}
hits
}
def calculate(): Double = 4.0 * calculateHits / n
}
class ThreadedPiCalculator(n: Int) extends PiCalculator {
val threads = 4
val pool = Executors.newFixedThread
Solution
The cause of the long execution times in the multithreaded case were the use of static methods, fields, objects.
In the scala code code above, if I change the Circle class to object:
And then create a Circle instance inside of the PiCalculator class:
Then execution times are as follows:
Also in the Java solution, I've found, that the Circle class was using a
So it looks like, when writing multithreaded code, one must avoid using common objects, and static methods/variables.
In the scala code code above, if I change the Circle class to object:
class Circle {
class Point(val x: Double, val y: Double) {
def isInside: Boolean = Math.sqrt(x * x + y * y) < 1.0
}
val random = new Random()
private def randomPoint(): Point = {
def generate() = random.nextDouble() * 2 - 1
new Point(generate(), generate())
}
def tryToHit(): Boolean = randomPoint().isInside
}And then create a Circle instance inside of the PiCalculator class:
class SimplePiCalculator(n: Int) extends PiCalculator with Callable[Int] {
override def call(): Int = calculateHits()
val circle = new Circle
def calculateHits(): Int = {
var hits = 0
for (i <- 1 to n) {
if (circle.tryToHit()) hits += 1
}
hits
}
def calculate(): Double = 4.0 * calculateHits / n
}Then execution times are as follows:
Calculating PI using the Monte-Carlo method
Elapsed time: 481745137ns 481ms
3.142086
4.933464102068186E-4
Elapsed time: 219407150ns 219ms
3.1410116
5.810535897929903E-4Also in the Java solution, I've found, that the Circle class was using a
public static final Random RANDOM static variable to generate random points, and this was the cause of the slowdown.So it looks like, when writing multithreaded code, one must avoid using common objects, and static methods/variables.
Code Snippets
class Circle {
class Point(val x: Double, val y: Double) {
def isInside: Boolean = Math.sqrt(x * x + y * y) < 1.0
}
val random = new Random()
private def randomPoint(): Point = {
def generate() = random.nextDouble() * 2 - 1
new Point(generate(), generate())
}
def tryToHit(): Boolean = randomPoint().isInside
}class SimplePiCalculator(n: Int) extends PiCalculator with Callable[Int] {
override def call(): Int = calculateHits()
val circle = new Circle
def calculateHits(): Int = {
var hits = 0
for (i <- 1 to n) {
if (circle.tryToHit()) hits += 1
}
hits
}
def calculate(): Double = 4.0 * calculateHits / n
}Calculating PI using the Monte-Carlo method
Elapsed time: 481745137ns 481ms
3.142086
4.933464102068186E-4
Elapsed time: 219407150ns 219ms
3.1410116
5.810535897929903E-4Context
StackExchange Code Review Q#140360, answer score: 4
Revisions (0)
No revisions yet.