Package: cycept
CyceptEffortless just-in-time compilation of Python functions,
powered byCython.InstallationCycept is available onPyPi:python-mpipinstallcyceptCycept requires Python 3.9 or later.To run Cycept a C compiler needs to be installed on the system.OnLinuxyou may installGCC(Debian-like distros:sudo apt install build-essential).OnmacOSyou may installClang(available throughXcode).OnWindowsyou may installMSVC(available throughMicrosoft C++ Build Tools).If you are usingAnacondaon Linux or macOS,
you may also obtain a C compiler throughconda install -c conda-forge c-compiler.Once installed you can check whether Cycept functions correctly usingpython-c"import cycept; cycept.check()"If it does not work due to missingPython.hand you are running Linux,
make sure to install the Python development headers (Debian-like distros:sudo apt install python3-devif you are using the system Python).Demo"""Comparison of Python function JITsBelow we implement the sample function sum((a - b)**2) where a and bare both 2D NumPy arrays. The following strategies are implemented andcompared against each other:* Pure Python (baseline)* NumPy* Cycept JIT* Cython JIT* Numba JIT"""fromtimeimportperf_counterimportnumpyasnpm,n=2_000,3_000a=np.random.random((m,n))b=np.random.random((m,n))# Pure Pythondeffunc(a,b):x=0foriinrange(a.shape[0]):forjinrange(a.shape[1]):x+=(a[i,j]-b[i,j])**2returnxtic=perf_counter()result=func(a,b)toc=perf_counter()t_ref=toc-ticprint(f'Python:{result:<18}in{t_ref:.3e}s')# NumPydeffunc_numpy(a,b):return((a-b)**2).sum()tic=perf_counter()result=func_numpy(a,b)toc=perf_counter()t=toc-ticprint(f'NumPy:{result:<18}in{t:.3e}s ({int(t_ref/t)}x)')# Cyceptimportcycept@cycept.jitdeffunc_cycept(a,b):x=0foriinrange(a.shape[0]):forjinrange(a.shape[1]):x+=(a[i,j]-b[i,j])**2returnxfunc_cycept(a[:1,:1],b[:1,:1])# to compiletic=perf_counter()result=func_cycept(a,b)toc=perf_counter()t=toc-ticprint(f'Cycept:{result:<18}in{t:.3e}s ({int(t_ref/t)}x)')# Cythonimportcython@cython.compiledeffunc_cython(a,b):x=0foriinrange(a.shape[0]):forjinrange(a.shape[1]):x+=(a[i,j]-b[i,j])**2returnxfunc_cython(a[:1,:1],b[:1,:1])# to compiletic=perf_counter()result=func_cython(a,b)toc=perf_counter()t=toc-ticprint(f'Cython:{result:<18}in{t:.3e}s ({int(t_ref/t)}x)')# Numbaimportnumba@numba.njitdeffunc_numba(a,b):x=0foriinrange(a.shape[0]):forjinrange(a.shape[1]):x+=(a[i,j]-b[i,j])**2returnxfunc_numba(a[:1,:1],b[:1,:1])# to compiletic=perf_counter()result=func_numba(a,b)toc=perf_counter()t=toc-ticprint(f'Numba:{result:<18}in{t:.3e}s ({int(t_ref/t)}x)')Running the above results in something similar toPython: 1000265.9355757801 in 2.316e+00 s
NumPy:  1000265.9355757139 in 2.967e-02 s (78x)
Cycept: 1000265.9355757138 in 6.429e-03 s (360x)
Cython: 1000265.9355757801 in 7.103e-02 s (32x)
Numba:  1000265.9355757801 in 7.376e-03 s (314x)For scientific codebases in the wild, code of the NumPy style is the
most widespread. However, writing out the loops while adding a JIT can
often lead to dramatic performance improvements, even when compared
to NumPy. A further benefit of this is a reduced memory footprint,
as no temporary arrays are created behind the scenes by the computation.While Cycept especially shines on "array code",
speedups are generally achievable on all kinds of code.Performance benchmarksCycept comes with a suite of automated performance tests, all similar in
nature to the demo above. To run this, dopython-c"import cycept; cycept.bench(show_func=True)"Currently, the competitors included arePure PythonNumPyCyceptCythonNumbaNote that you do not need to haveallof the above installed in order to
run the benchmarks.How does it work?When a function is to be jitted by Cycept, the first step is to infer the
types of all variables. This is done by simply running the function once in
pure Python and applying introspection. For this reason, it it best not to
ask for too heavy a computation for the initial call. You might want to
manually set the type of some variables, which is possible via Pythontype hints.Special treatment is given to NumPy arrays. These are converted into Cython
memoryviews to enable fast indexing, but converted back whenever NumPy array
operations need to be performed.With the types and array modifications in place, the modified source code is
handed to Cython, which transpiles it into C/C++. The C compiler then compiles
this into an extension module, from which we finally get the jitted function.
By default, aggressive optimizations are applied at all stages.The jitted version of the function is cached, so that the whole process does
not have to be repeated for every call to the function. If however the
function is called with new types, another jitted version of the function will
be made, specialized to those types.Optional arguments to@cycept.jit()The transpilation carried out by Cycept can be tuned by various optional
arguments to@cycept.jit(). These are all documented in the docstring,
viewable throughpython-c"import cycept; help(cycept.jit)"Of particular interest ishtml, which allows for viewing of the transpiled
Cython and C code by calling the__cycept__()method on the jitted function,
e.g.importcycept@cycept.jit(html=True)deftwice(x):return2*xtwice(42)# int calltwice('hello')# str calltwice.__cycept__()# view all transpilationsUnit testsThe code contains a unit test suite which may be run aspython-c"import cycept; cycept.test()"This requirespytestto be installed
(python -m pip install pytest).What's up with the name?'Cycept' is an amalgamation of 'Cython' and
'CONCEPT', the latter of which is a
cosmological simulation code that makes heavy use of code transformation,
both custom and through Cython. As the author of both projects, Cycept is my
attempt to extract some of the code transformation ideas buried within
CONCEPT, making them available within an easy-to-use library.
Though no code is shared between the projects, in many respects Cycept
can be considered a spiritual successor to CONCEPT.
Furthermore, 'Cycept' has a nice inception ring to it,
which seems fitting for a piece of code that generates code.