Monte Carlo Simulation: Why is the processing time greater for a smaller number of random points?

Viewed 78

I'm writing a Monte Carlo simulation to estimate the value of Pi, and I am recording the total processing time required for each successive number of points as it runs. Oddly enough, it seems that the processing time starts out decreasing with each run, then increases with each run as the number of points increases. I've tested this with perf_counter(), time(), clock(), and default_timer(). I've also attempted to test this with CPU clock functions like process_time() and thread_time() as well, but for most runs, it returned the total processing time as 0.0.

Is there a reason that the CPU timers return 0.0 as the total time, while the clock timers have such an odd trend? I'd expect the processing time to increase linearly, but clearly this is not the case unless there is an issue with my code somewhere. I've included both my code and a plot as an example.

import numpy as np
from numpy import random
import matplotlib.pyplot as plt
import time
import math

#Plot number of points vs fractional error of pi)
def piPlot(piList):
    
    #Set correct plot size
    piAx = plt.gca() 
    piAx.set_aspect(1)
    
    #Generate and label plot, set to logarithmic scale
    plt.scatter(N,piList)
    plt.xscale("log")
    plt.yscale("log")
    plt.title('Fractional Error vs Number of Points')
    plt.xlabel('Number of Points [Logarithmic]')
    plt.ylabel('Fractional Error [Logarithmic]')
    
#Plot generator for number of points vs processing time
def timePlot(timeList):
    
    #Set correct plot size
    timeAx = plt.gca() 
    timeAx.set_aspect(1)    
    
    #Generate and label plot, set to logarithmic scale
    plt.scatter(N,timeList)
    plt.xscale("log")
    plt.yscale("log")
    plt.title('Processing Time vs Number of Points')
    plt.xlabel('Number of Points [Logarithmic]')
    plt.ylabel('Processing Time [Logarithmic]')

def simulation(n, t):
    #Begin timer
    startTime = t
    
    #Initialize list for current run
    run = []
    
    #Generate random points, find number inside circle
    xPoint = np.random.rand(n)
    yPoint = np.random.rand(n)
    total = ((xPoint ** 2) + (yPoint ** 2) <= 1).sum()
    
    #Estimate pi
    pi = 4*(total/n)
    run.append(pi)
    
    #End timer, calculate total processing itme
    endTime = time.perf_counter()
    T = endTime - startTime
    run.append(T)  

    return run
    
    
def main():
    #Define the total numbers of points for each simulation
    N = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000]
    
    #Run the simulation and calculate fractional error for returned pi
    results1 = [simulation(i, time.perf_counter()) for i in N]
    pis1, times1 = map(list, zip(*results1)) 
    error1 = [abs((math.pi - i)/math.pi) for i in pis1]
    
    #Generate plots
    plot1 = plt.figure(1)
    piPlot(error1)
    
    plot2 = plt.figure(2)
    timePlot(times1)
    
main()

Plot for Processing Time vs Number of Points

1 Answers

Try it with

N = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1]

and you'll see the following results:

n =  1  t =  6.614800076931715e-05
n =  10  t =  2.7269999918644316e-05
n =  100  t =  2.177599981223466e-05
n =  1000  t =  4.9498999942443334e-05
n =  10000  t =  0.00042855599986069137
n =  100000  t =  0.0026685809998525656
n =  1000000  t =  0.021433439000247745
n =  10000000  t =  0.19109468099941296
n =  1000000  t =  0.019547208999938448
n =  100000  t =  0.0036834789998465567
n =  10000  t =  0.0002759440003501368
n =  1000  t =  3.2883999665500596e-05
n =  100  t =  1.4722999367222656e-05
n =  10  t =  1.0972000382025726e-05
n =  1  t =  1.0822999684023671e-05

It's related to compiling the code. After you call your function for the first time the code is compiled. So the first call takes a long time to do the initial processing. And for small n the time for memory management is comparable with the calculations themselves.

Related