Numba
from numba import jit
#from numpy.random import random
from random import random

import timeit

def monte_carlo_pi(nsamples):# Function is compiled and runs in machine code
acc = 0
for i in range(nsamples):
x = random()
y = random()
if (x ** 2 + y ** 2) < 1.0:
acc += 1
return 4.0 * acc / nsamples

@jit(nopython=True)
def monte_carlo_pi_numba(nsamples):# Function is compiled and runs in machine code
acc = 0
for i in range(nsamples):
x = random()
y = random()
if (x ** 2 + y ** 2) < 1.0:
acc += 1
return 4.0 * acc / nsamples

t = timeit.timeit("monte_carlo_pi_numba(1000000)", setup="from __main__ import monte_carlo_pi_numba", number=100)
print('Running time of {:30s} : {:.2f}s'.format('monte_carlo_pi_numba(1000000)', t))

t = timeit.timeit("monte_carlo_pi_cython(1000000)", setup="from _mc_cython import monte_carlo_pi_cython", number=100)
print('Running time of {:30s} : {:.2f}s'.format('monte_carlo_pi_cython(1000000)', t))

t = timeit.timeit("monte_carlo_pi(1000000)", setup="from __main__ import monte_carlo_pi", number=100)
print('Running time of {:30s} : {:.2f}s'.format('monte_carlo_pi(1000000)', t))

'''
Call 100 Monte Carlo runs, each run samples 1000000 points

random.random()
Running time of monte_carlo_pi_numba(1000000)  : 1.67s
Running time of monte_carlo_pi_cython(1000000) : 4.21s
Running time of monte_carlo_pi(1000000)        : 34.29s

numpy.random.random()
Running time of monte_carlo_pi_numba(1000000)  : 1.75s
Running time of monte_carlo_pi_cython(1000000) : 52.38s
Running time of monte_carlo_pi(1000000)        : 84.34s
'''