Package: numpy-api-bench
We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil[1].numpy-api-benchis a small Python package comparing speed differences
between NumPy’s Python and C APIs that also serves as an example project for
writing C extension modules that make use of theNumPy C API[2].[1]Attributed to Sir Tony Hoare, popularized by Donald Knuth.[2]This package is a fork of the latest version of an old PyPI project of
mine calledc-npy-demowith more concise and efficient code as well as a
new name.c-npy-demowas written at a time when I was still struggling
with the Python and NumPy C APIs and had no idea how to test them, but now
that I am much more practiced with both APIs, I felt it right to use my new
knowledge to rework my old code.InstallationFrom sourceLinux, Mac, and Windows binary wheels have been built from source on Github
Actions runners using the excellentcibuildwheeltool, which eases the
process of building binary wheels from compiled code for different platforms.cibuildwheelespecially helps with buildingmanylinuxwheels.To build locally, you will neednumpy>=1.19and the latestsetuptools[3]installed. Your C compiler should be appropriate for your
platform, ex. GCC for Linux, MSVC for Windows, but letsetuptoolsdo the
work.First, usegit cloneor download + unzip to get the repo source code and
install the requirements with[4]pip3install-rinstall_requires.txtAfter youcdinto the repository root, you can build the C extensions
in-place and install the package files withmakeinplace&&pip3install.If you don’t have or don’t wish to usemake, you may instead usepython3setup.pybuild_ext--inplace&&pip3install.[3]setuptoolshas seen a lot of change, especially postPEP 517,
but since C extension modules have to be built in this package the legacysetup.pymethod of building distributions still has to be used. Note
that thedistutils.core.Extensionclass is present insetuptoolsas
thesetuptools.extension.Extensionclass.[4]Only Linux users need worry about usingpip3. Usepipfor
Windows and Mac.From PyPI64-bit Python 3.6-3.9 binary wheels for Windows, MacOS, manylinux1, and
manylinux2010 can be installed from PyPI, with 32-bit wheels for Windows (x86)
and Linux (i686) also available. Install withpip3installnumpy-api-benchPackage contentsThenumpy-api-benchpackage contains a pure Python module and several C
extension modules. The pure Python module isnpapibench.pyimpl, containing
one function that centers and scales to unit variance anumpy.ndarraythat
is implemented with only one line ofnumpy-enabled Python code. It is the
“benchmark” for the C extension modulenpapibench.cimpl, which implements
a near-identical function by using the NumPy C API. The other C extension
modules are part of thenpapibench.functimersubpackage, which provides a
callable API for timing the execution of a function with optional arguments in
atimeit-like fashion[5].On installation,setuptoolswill also create an entry point titlednpapibenchto access the benchmarking code. Just typing the name of the
entry point in the terminal should produce thetimeit-like outputnumpy.ndarray shape (40, 5, 10, 10, 20, 5), size 2000000
pyimpl.stdscale -- 10 loops, best of 5: 31.9 msec per loop
 cimpl.stdscale -- 50 loops, best of 5: 13.6 msec per loopFor usage details, trynpapibench--help.[5]Previously, I had usedtimeit.mainfor its pretty output, but
unlike the callable API provided bytimeit, one cannot pass in a global
symbol table to avoid repeated setup. Therefore, thenumpy.ndarrayallocated in the benchmarking code is allocated twice. I thus wrotenpapibench.functimer, which providestimeit.main-like capabilities
with a callable API intended for use with functions. It is written as a C
extension module to reduce the timing measurement error resulting from
timingnexecutions of a statement within a Python loop, which has a
higher per-loop overhead than a C for loop.Unit testsTesting internal functionsThe unit testing requirements for a C extension module are rather unique.
Although one is writing C code, the resulting shared object built bysetuptoolsis loaded by the Python interpreter, so it easier to test
Python-accessible functions by using Python unit testing tools. However, it is
likely that the C extension module, whichby conventionis a single file
with all members static except the module initialization function, may contain
some internal functions that cannot be accessed directly from Python. So far,
there does not seem to be a widely accepted approach to unit testing code in
Python C extensions, especially these internal C functions.For this project, in separate C extension modules, I wrote Python wrappers for
the internal functions I wanted to test, providing a C API for other extension
modules by using the header file andPyCapsulemethod described in theofficial tutorialon writing Python C extensions. Then, I wrote unit tests
in Python using thepytestAPI and simply invokedpytestto collect and
run all unit tests, as it produces far better unit test output compared to
most C unit testing frameworks and is aware of Python objects. If there were
any segmentation faults or need to more closely debug, I would just then invokegdbon the Python interpreter runningpytest[6]withgdb--argspython3-mpytestTogether,pytestandgdballowed me to hammer out a significant number
of bugs.[6]Thepytestentry point is a Python script run by the interpreter sogdb pytestdoes not work.For usersTo run the unit tests in the package,pytest>=6.0.1must be installed. If
installing the wheel from PyPI, you can installpytestas an optional
dependency alongside the package code withpip3installnumpy-api-bench[tests]The unit tests are located innpapibench.testsandnpapibench.functimer.testsand can be run withpytest--pyargsnpapibench.tests&&pytest--pyargsnpapibench.functimer.testsOther desired flags can be passed topytestbefore the--pyargsflag.If building from source, follow the steps inFrom sourcebut replace the
finalpip3 install .withpip3install.[tests]The unit tests can be run aftercding to the repository root by simply
callingpytest.