Performance testing¶
[1]:
import numpy as np
import galois
[2]:
GFp_small = galois.GF_factory(31, 1)
GFp_large = galois.GF_factory(2267, 1)
[9]:
def construct_arrays(GF, N):
order = GF.order
a = np.random.randint(0, order, N, dtype=int)
b = np.random.randint(0, order, N, dtype=int)
ga = GF(a)
gb = GF(b)
return a, b, ga, gb, order
[4]:
def pure_python_add(a, b, modulus):
c = np.zeros(a.size, dtype=a.dtype)
for i in range(a.size):
c[i] = (a[i] + b[i]) % modulus
return c
def pure_python_exp(a, b, order):
c = np.ones(a.size, dtype=a.dtype)
for i in range(a.size):
for _ in range(0, b):
c[i] = (c[i] * a[i]) % order
return c
def numpy_exp(a, b, order):
c = np.ones(a.size, dtype=a.dtype)
for _ in range(0, b):
c = (c * a) % order
return c
GF(p) speed tests¶
GF(31) addition, N=10e3¶
[12]:
N = int(10e3)
a, b, ga, gb, order = construct_arrays(GFp_small, N)
print(f"Pure python addition in GF({order})")
%timeit pure_python_add(a, b, order)
print(f"\nNative numpy addition in GF({order})")
%timeit (a + b) % order
print(f"\n`galois` implementation of addition in GF({order})")
%timeit ga + gb
Pure python addition in GF(31)
7.03 ms ± 170 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Native numpy addition in GF(31)
117 µs ± 475 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
`galois` implementation of addition in GF(31)
85.2 µs ± 2.44 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
GF(31) addition, N=10e6¶
[13]:
N = int(10e6)
a, b, ga, gb, order = construct_arrays(GFp_small, N)
print(f"Pure python addition in GF({order})")
%timeit pure_python_add(a, b, order)
print(f"\nNative numpy addition in GF({order})")
%timeit (a + b) % order
print(f"\n`galois` implementation of addition in GF({order})")
%timeit ga + gb
Pure python addition in GF(31)
8.56 s ± 598 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Native numpy addition in GF(31)
162 ms ± 1.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
`galois` implementation of addition in GF(31)
121 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
GF(31) exponentiation, N=10e3¶
[17]:
N = int(10e3)
a, b, ga, gb, order = construct_arrays(GFp_small, N)
exponent = 21
print(f"Pure python exponentiation in GF({order})")
%timeit pure_python_exp(a, exponent, order)
print(f"\nNative numpy exponentiation in GF({order})")
%timeit numpy_exp(a, exponent, order)
print(f"\n`galois` implementation of exponentiation in GF({order})")
%timeit ga ** exponent
Pure python exponentiation in GF(31)
151 ms ± 4.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Native numpy exponentiation in GF(31)
2.51 ms ± 62.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
`galois` implementation of exponentiation in GF(31)
1.03 ms ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
GF(31) exponentiation, N=10e6¶
[19]:
N = int(10e6)
a, b, ga, gb, order = construct_arrays(GFp_small, N)
exponent = 21
# print(f"Pure python exponentiation in GF({order})")
# %timeit pure_python_exp(a, exponent, order)
print(f"\nNative numpy exponentiation in GF({order})")
%timeit numpy_exp(a, exponent, order)
print(f"\n`galois` implementation of exponentiation in GF({order})")
%timeit ga ** exponent
Native numpy exponentiation in GF(31)
2.96 s ± 239 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
`galois` implementation of exponentiation in GF(31)
1.08 s ± 43.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)