Question

I'm trying to calculate pi with arbitrary precision on Python using one of Ramanujan's formulas: http://en.wikipedia.org/wiki/Approximations_of_%CF%80#20th_century. It basically requires lots of factorials and high-precision floating numbers division.

I'm using multiple threads to divide infinite series calculation by giving each thread all the members that have a certain modulus when divided by the number of threads. So if you have 3 threads, the sum should be divided like this Thread 1 ---> 1, 4, 7... members Thread 2 ---->2, 5, 8... Thread 3 ---->3, 6, 9...

Here's my code so far:

from decimal   import *
from math      import sqrt, ceil
from time      import clock
from threading import *
import argparse

memoizedFactorials = []
memoizedFactorials.append( 1 )
memoizedFactorials.append( 1 )

class Accumulator:
    def __init__( self ):
        self._sum = Decimal( 0 )

    def accumulate( self, decimal ):
        self._sum += decimal

    def sum( self ):
        return self._sum

def factorial( k ):
    if k < 2: return 1
    elif len(memoizedFactorials) <= k:
        product = memoizedFactorials[ - 1 ] #last element 
        for i in range ( len(memoizedFactorials), k+1 ):
            product *= i;
            memoizedFactorials.append(product)

    return memoizedFactorials[ k ]

class Worker(Thread):
    def __init__( self, startIndex, step, precision, accumulator ):
        Thread.__init__( self, name = ("Thread - {0}".format( startIndex ) ) )
        self._startIndex = startIndex
        self._step = step
        self._precision = precision
        self._accumulator = accumulator

    def run( self ):
        sum = Decimal( 0 )
        result = Decimal( 1 )
        zero = Decimal( 0 )

        delta = Decimal(1)/( Decimal(10)**self._precision + 1 )
        #print "Delta - {0}".format( delta ) 
        i = self._startIndex
        while( result - zero > delta ):
            numerator = Decimal(factorial(4 * i)*(1103 + 26390 * i))
            denominator = Decimal((factorial(i)**4)*(396**(4*i)))
            result =  numerator / denominator
            print "Thread - {2} --- Iteration - {0:3} --->{1:3}".format( i, result, self._startIndex )
            sum += result
            i += self._step

        self._accumulator.accumulate( sum ) 
        print 

def main( args ):
    numberOfDigits = args.numberOfDigits;
    getcontext().prec = numberOfDigits + 8
    zero = Decimal(1) / Decimal( 10**( numberOfDigits + 1 ) )

    start = clock()
    accumulator = Accumulator()

    threadsCount = args.numberOfThreads;
    threadPool = []
    for i in range(0, threadsCount ):
        worker = Worker( i, threadsCount, numberOfDigits, accumulator )
        worker.start()
        threadPool.append( worker )

    for worker in threadPool:
        worker.join()

    sum = accumulator.sum();

    rootOfTwo = Decimal(2).sqrt()

    result = Decimal( 9801 ) / ( Decimal( 2 ) * rootOfTwo * sum ) 
    end = clock();

    delta = end - start;

    print result;
    print ("Took it {0} second to finish".format( delta ) )

    #testing the results
    #realPiFile = open("pi.txt");
    #myPi = str(result)
    #realPi = realPiFile.read( len(myPi) - 1 )

    #if ( myPi[:-1] != realPi ):
    #    print "Answer not correct!"
    #    print "My pi   - {0}".format(myPi)
    #    print "Real pi - {0}".format(realPi)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description = 'Calculate Pi at with arbitrary precision')
    parser.add_argument('-p',            dest = 'numberOfDigits',  default=20, type = int, help ='Number of digits in pi ')
    parser.add_argument('-t', '--tasks', dest = 'numberOfThreads', default=1,  type = int, help ='Number of tasks( threads )')
    parser.add_argument('-o',            dest = 'outputFileName',  type = str,             help ='Connect to VCS testing servers')
    parser.add_argument('-q', '--quet',  dest = 'quetMode'      ,  action='store_true',    help ='Run in quet mode')

    args = parser.parse_args()

    print args
    main(args)
    a = raw_input("Press any key to continue...")

What concerns me thati have very small or no acceleration in time when using multiple threads. For example 1000 digits of pi: 1 Thread --> 0.68 seconds 2 Threads --> 0.74 seconds 4 Threads --> 0.75 seconds 10 threads --> 0.96 seconds

Do you have any ideas on how to decrease the time. I see on task manager that when using four threads both of my cores get involved on 100%. However time seems to be the same.

PS: It's a homework assignment so i can't use another formula. PSS: I'm using python 2.7

Thanks:)

Was it helpful?

Solution 2

Threads do not speed things up because of the GIL (Global Interpret Lock). Use multiprocessing for this kind of task. Its usage is very similar to threads.

OTHER TIPS

Python has a GIL(Global Interpreter Lock) that prevents more than one thread to execute python code at the same time, i.e. you cannot obtain a speed up on CPU-bound tasks using multiple threads. You must use multiple processes.

Instead to brute-force your way through the series and all those nasty factorials you shall definitely learn about the Binary Splitting algorithm.

http://numbers.computation.free.fr/Constants/Algorithms/splitting.html

This guy already did the work for you. It has python implementations of the binary splitting structure applied to the Chudnovsky formula:
http://www.craig-wood.com/nick/articles/pi-chudnovsky/

The main advantage of such a structure is that it eliminates the need for divisions, factorials and any floating point calculations while calculating the series. Then you perform a single, final, supermassive division between a numerator and a denominator. Actually I have no idea how to multithread it but that's a start.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top