Ok, I must admit parallelism in Scala rocks, but BigInteger in Java sucks. It seems multiplication has an O(n^2) complexity. 90% of time is eaten by the last multiplication of two big integers, which ofc cannot be easily parallelised without rolling your own multiplication. So I have to look for a better bignum library.
BTW: I get first million of primes in 0.079 s, but I haven't optimised nor parallelised that - it is not a bottleneck.
------------------------
Added later:
Here is my parallel code.
It differs from non-parallel code with only one method call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
import org.jscience.mathematics.number.LargeInteger
def sieve(n: Int) = {
val primes = new collection.mutable.BitSet() + 2 ++ (3 to (n, 2))
primes.view.map({ p => primes --= (p + p) to (n, p); p }).force
}
def multiplicity(n: Int, p: Int) = {
var q = n
var m = 0
while (q >= p) {
q /= p
m += q
}
m
}
def multiply(n: Int, x: Iterable[Int]) =
x.map(p => LargeInteger.valueOf(p).pow(multiplicity(n, p)))
def factorial(n: Int) =
(multiply(n, sieve(n))).toSeq.par.reduce(_ times _)
def measureTime(code: => Unit) {
val start = System.currentTimeMillis
code
val end = System.currentTimeMillis
println((end - start) / 1000.0)
}
|
Timings on Core i5 2.6 GHz:
1 2 3 4 5 6 7 8
|
scala> measureTime(factorial(100000))
0.477
scala> measureTime(factorial(500000))
10.037
scala> measureTime(factorial(1000000))
65.666
|
The multiplication is implemented using Karatsuba algorithm, so it could be better if I found a better library, e.g. with Toom-Cook algorithm. The CPU utilisation stays at about 85-98% while running this (all 2 double-cores of i5).
BTW: Helios, what hardware do you run your tests?
BTW2: Seems that prime factorization does little good.
Another version of the complete code:
1 2 3 4 5 6 7 8 9 10 11
|
import org.jscience.mathematics.number.LargeInteger
def factorial2(n: Int) =
(1 to n).map(LargeInteger.valueOf(_: Int)).par.reduce(_ times _)
def measureTime(code: => Unit) {
val start = System.currentTimeMillis
code
val end = System.currentTimeMillis
println((end - start) / 1000.0)
}
|
And timings:
1 2 3 4 5 6 7 8
|
scala> measureTime(factorial2(100000))
0.534
scala> measureTime(factorial2(500000))
10.268
scala> measureTime(factorial2(1000000))
44.375
|
The second (simpler) version seem to use my CPU in a more efficient way, especially for the 1000000! case - it doesn't drop below 95%, and most of the time stays on 100%.