Commit d6b7bc4944f by Pierre Augier

Doc benchmark

parent 24f75e21f1e
NAME=proj.py
all: pythran cython other
pythran:
transonic $(NAME) -b pythran -pf "-DUSE_XSIMD -Ofast"
cython:
transonic $(NAME) -b cython -pf "-Ofast "
other:
transonic $(NAME) -b numba
transonic $(NAME) -b python
clean:
rm -rf __cython__ __python__ __numba__ __pythran__
bench:
python proj.py
import numpy as np
from transonic import boost, Array, Type
A = Array[Type(np.float64, np.complex128), "3d"]
Af = "float[:,:,:]"
A = Af # issue fused type with Cython
def proj(vx: A, vy: A, vz: A, kx: Af, ky: Af, kz: Af, inv_k_square_nozero: Af):
tmp = (kx * vx + ky * vy + kz * vz) * inv_k_square_nozero
vx -= kx * tmp
vy -= ky * tmp
vz -= kz * tmp
def proj_loop(vx: A, vy: A, vz: A, kx: Af, ky: Af, kz: Af, inv_k_square_nozero: Af):
# type annotations only useful for Cython
n0: int
n1: int
n2: int
i0: int
i1: int
i2: int
tmp: float
n0, n1, n2 = kx.shape[0], kx.shape[1], kx.shape[2]
for i0 in range(n0):
for i1 in range(n1):
for i2 in range(n2):
tmp = (
kx[i0, i1, i2] * vx[i0, i1, i2]
+ ky[i0, i1, i2] * vy[i0, i1, i2]
+ kz[i0, i1, i2] * vz[i0, i1, i2]
) * inv_k_square_nozero[i0, i1, i2]
vx[i0, i1, i2] -= kx[i0, i1, i2] * tmp
vy[i0, i1, i2] -= ky[i0, i1, i2] * tmp
vz[i0, i1, i2] -= kz[i0, i1, i2] * tmp
proj_pythran = boost(backend="pythran")(proj)
proj_numba = boost(backend="numba")(proj)
proj_cython = boost(backend="cython")(proj)
proj_loop_pythran = boost(backend="pythran")(proj_loop)
proj_loop_numba = boost(backend="numba")(proj_loop)
proj_loop_cython = boost(backend="cython", boundscheck=False, wraparound=False)(proj_loop)
if __name__ == "__main__":
from textwrap import dedent
import numba
import pythran
from transonic import __version__
from transonic.util import timeit
print(
f"transonic {__version__}\n"
f"pythran {pythran.__version__}\n"
f"numba {numba.__version__}\n"
)
setup = dedent("""
shape = n0, n1, n2 = 64, 512, 512
k0 = np.linspace(0, 100, n0)
k1 = np.linspace(0, 100, n1)
k2 = np.linspace(0, 100, n2)
K1, K0, K2 = np.meshgrid(k1, k0, k2, copy=False)
kz = np.ascontiguousarray(K0)
ky = np.ascontiguousarray(K1)
kx = np.ascontiguousarray(K2)
k_square_nozero = K0 ** 2 + K1 ** 2 + K2 ** 2
k_square_nozero[0, 0, 0] = 1e-14
inv_k_square_nozero = 1.0 / k_square_nozero
vx = np.ones(shape)
vy = np.ones(shape)
vz = np.ones(shape)
""")
loc = locals()
def bench(call, norm=None):
ret = result = timeit(call, setup=setup, globals=loc)
if norm is None:
norm = result
result /= norm
print(f"{call.split('(')[0]:33s}: {result:.2f}")
return ret
norm = bench("proj(vx, vy, vz, kx, ky, kz, inv_k_square_nozero)")
print(f"norm = {norm:.2e} s")
for backend in ("cython", "numba", "pythran"):
bench(f"proj_{backend}(vx, vy, vz, kx, ky, kz, inv_k_square_nozero)", norm=norm)
bench(f"proj_loop_{backend}(vx, vy, vz, kx, ky, kz, inv_k_square_nozero)", norm=norm)
\ No newline at end of file
Benchmark projection vector
===========================
.. literalinclude:: proj.py
which gives::
transonic 0.4.1
pythran 0.9.3post1
numba 0.46.0
proj : 1.00
norm = 4.46e-01 s
proj_cython : 1.26
proj_loop_cython : 0.23
proj_numba : 1.09
proj_loop_numba : 0.22
proj_pythran : 0.46
proj_loop_pythran : 0.22
For the solution with loops, the 3 backends are equally good.
Pythran also accelerates the high level implementation, but in this case, it is
still more than twice slower than the implementation with loops.
......@@ -5,10 +5,10 @@ NAMEJIT=row_sum_jit.py
all: pythran cython other
pythran:
transonic $(NAME) -b pythran -pf "-march=native -DUSE_XSIMD"
transonic $(NAME) -b pythran -pf "-march=native -DUSE_XSIMD -Ofast"
cython:
transonic $(NAME) -b cython
transonic $(NAME) -b cython -pf "-march=native -Ofast"
other:
transonic $(NAME) -b numba
......@@ -17,7 +17,8 @@ other:
clean:
rm -rf __cython__ __python__ __numba__ __pythran__
bench: bench_python bench_cython bench_numba bench_pythran
bench:
python bench.py
bench_cython:
TRANSONIC_BACKEND="cython" python $(NAME)
......@@ -32,6 +33,4 @@ bench_python:
TRANSONIC_BACKEND="python" python $(NAME)
bench_jit:
TRANSONIC_BACKEND="cython" python $(NAMEJIT)
TRANSONIC_BACKEND="numba" python $(NAMEJIT)
TRANSONIC_BACKEND="pythran" python $(NAMEJIT)
\ No newline at end of file
python bench.py jit
\ No newline at end of file
from subprocess import getoutput
import sys
decorator = "boost"
if "jit" in sys.argv:
decorator = "jit"
print(f"With decorator {decorator}:")
def get_times(backend):
output = getoutput(
f"TRANSONIC_BACKEND='{backend}' python row_sum_{decorator}.py"
)
lines = output.split("\n")
index_backend = 1
if decorator == "jit":
index_backend = 2
assert lines[index_backend] == backend.capitalize()
time_high = float(lines[index_backend + 1].split()[1])
time_low = float(lines[index_backend + 2].split()[1])
return time_high, time_low
backend = "python"
time_high, time_low = get_times(backend)
time_ref = time_high
def print_result(backend, time_high, time_low):
print(backend.capitalize())
print(f"high level: {time_high:.2e} s (= {time_high/time_ref:5.2f} * norm)")
print(f"low level: {time_low:.2e} s (= {time_low/time_ref:5.2f} * norm)\n")
print_result(backend, time_high, time_low)
for backend in ("cython", "numba", "pythran"):
time_high, time_low = get_times(backend)
print_result(backend, time_high, time_low)
......@@ -30,31 +30,29 @@ To compile this file with different backends, one can run:
transonic -b numba row_sum_boost.py
transonic -b pythran row_sum_boost.py -pf "-march=native -DUSE_XSIMD"
Then, on my PC (meige8pcpa79), I get::
To choose the backend, we can call for example:
TRANSONIC_BACKEND="python" python row_sum_boost.py
Checks passed: results are consistent
Python
row_sum 2.0e-03 s
row_sum_loops 1.6e-01 s
.. code:: bash
TRANSONIC_BACKEND="cython" python row_sum_boost.py
Checks passed: results are consistent
Then, on my PC ("gre"), I get::
Python
high level: 1.31e-03 s (= 1.00 * norm)
low level: 1.04e-01 s (= 79.27 * norm)
Cython
row_sum 2.0e-03 s
row_sum_loops 5.2e-04 s
high level: 1.29e-03 s (= 0.99 * norm)
low level: 4.10e-04 s (= 0.31 * norm)
TRANSONIC_BACKEND="numba" python row_sum_boost.py
Checks passed: results are consistent
Numba
row_sum 1.9e-03 s
row_sum_loops 4.4e-04 s
high level: 1.04e-03 s (= 0.80 * norm)
low level: 2.69e-04 s (= 0.21 * norm)
TRANSONIC_BACKEND="pythran" python row_sum_boost.py
Checks passed: results are consistent
Pythran
row_sum 1.1e-03 s
row_sum_loops 4.2e-04 s
high level: 7.68e-04 s (= 0.59 * norm)
low level: 2.55e-04 s (= 0.19 * norm)
The fastest solutions are in this case the Numba and Pythran backends for the
implementation with explicit loops.
......@@ -77,23 +75,18 @@ remove all type annotations (which is bad for Cython).
which gives::
TRANSONIC_BACKEND="cython" python row_sum_jit.py
Checks passed: results are consistent
Checks passed: results are consistent
Python
high level: 1.20e-03 s (= 1.00 * norm)
low level: 1.15e-01 s (= 95.26 * norm)
Cython
row_sum 1.3e-03 s
row_sum_loops 1.2e-02 s
high level: 1.25e-03 s (= 1.04 * norm)
low level: 1.22e-02 s (= 10.18 * norm)
TRANSONIC_BACKEND="numba" python row_sum_jit.py
Checks passed: results are consistent
Checks passed: results are consistent
Numba
row_sum 1.1e-03 s
row_sum_loops 2.7e-04 s
high level: 1.12e-03 s (= 0.93 * norm)
low level: 2.51e-04 s (= 0.21 * norm)
TRANSONIC_BACKEND="pythran" python row_sum_jit.py
Checks passed: results are consistent
Checks passed: results are consistent
Pythran
row_sum 8.4e-04 s
row_sum_loops 2.6e-04 s
high level: 6.71e-04 s (= 0.56 * norm)
low level: 2.41e-04 s (= 0.20 * norm)
......@@ -14,6 +14,6 @@ def check(functions, arr, columns):
def bench(functions, arr, columns):
print(backend_default.capitalize())
for func in functions:
result = timeit(lambda: func(arr, columns))
print(f"{func.__name__:20s} {result:.1e} s")
result = timeit("func(arr, columns)", globals=locals())
print(f"{func.__name__:20s} {result:.3e} s")
print()
Writing benchmarks
==================
With Transonic, writting benchmarks for the different accelerators is very
simple. We present an example in this page.
Other examples can be found here:
.. toctree::
:maxdepth: 2
../row_sum/txt
../proj_perp/txt
Comparison Numba vs Pythran (JIT)
---------------------------------
......
......@@ -3,4 +3,4 @@ cimport cython
cdef inline int add(int a, int b) nogil
@cython.locals(n=int, i=int, result=int)
cpdef use_add(int n)
cpdef int use_add(int n)
# cython: language_level=3
import cython
@cython.cfunc
@cython.inline
@cython.nogil
def add(a, b):
return a + b
@cython.ccall
def use_add(n):
result = 1
with cython.nogil:
......
......@@ -45,6 +45,14 @@ def bench_one(name_module="cmorph", func=None, total_duration=2):
with open(path_setup) as file:
setup = file.read()
if (name_module, name_function) in import_from_skimage:
setup_from_skimage = setup.replace(
f"from future.{name_module} import {name_function}",
import_from_skimage[(name_module, name_function)],
)
time = timeit(stmt, setup_from_skimage, total_duration=total_duration)
print(f"{'from skimage':18s} {time:.2e} s")
setup_pyx = setup.replace(
f"from future.{name_module} import", f"from pyx.{name_module} import"
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment