Created
March 28, 2018 21:29
-
-
Save standy66/67b174534e500e733fb493ac54a1e11c to your computer and use it in GitHub Desktop.
PyPy vs CPython extension call overhead
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Original code: https://gist.github.com/brentp/7e173302952b210aeaf3 | |
Compare speed of a cython wrapper vs a cffi wrapper to the same underlying | |
C-code with a fast function and a longer-running function. | |
This should run anywhere that has cffi and cython installed. | |
$ python3.6 -V | |
Python 3.6.2 (default, Jul 17 2017, 16:44:45) | |
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin | |
$ python3.6 compare_wrappers.py | |
adding 2 numbers with cffi wrapper: 0.299 usec | |
adding 2 numbers with cython wrapper: 0.242 usec | |
summing numbers inverses between 1 and 2000 with cffi wrapper: 6.356 usec | |
summing numbers inverses between 1 and 2000 with cython wrapper: 6.312 usec | |
$ pypy3 -V | |
Python 3.5.3 (3f6eaa010fce78cc7973bdc1dfdb95970f08fed2, Jan 13 2018, 18:14:01) | |
[PyPy 5.10.1 with GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] | |
$ pypy3 compare_wrappers.py | |
adding 2 numbers with cffi wrapper: 0.021 usec | |
adding 2 numbers with cython wrapper: 2.789 usec | |
summing numbers inverses between 1 and 2000 with cffi wrapper: 6.021 usec | |
summing numbers inverses between 1 and 2000 with cython wrapper: 8.376 usec | |
It seems that | |
* In PyPy 5.10.1 cpyext (CPython API compatibility layers) has a constant overhead | |
of ~2.5 usec per call. | |
* CFFI has zero overhead in PyPy (~20 nsec, comparable to plain C call). | |
* In CPython 3.6 CFFI and CPython C Extension API has almost identical overhead of | |
~250 nsec per call, with CFFI lagging a bit behind. | |
""" | |
from timeit import timeit | |
import cffi | |
import re | |
import os.path as op | |
import sys | |
import time | |
ffi = cffi.FFI() | |
ffi.cdef(""" | |
int sum(int, int); | |
long inv_sum_between(int, int); | |
""") | |
# here is the C code that we are wrapping. One quick running function and | |
# another potentially long-running function. | |
code = """ | |
int sum(int a, int b){ | |
return a + b; | |
} | |
long inv_sum_between(int a, int b){ | |
float sum = 0; | |
int i; | |
for(i=a; i<=b; i++) sum += (1.0 / i); | |
return (long)sum; | |
} | |
""" | |
C = ffi.verify(code) | |
import pyximport; pyximport.install() | |
with open('ccy.h', 'w') as fh: | |
fh.write(code) | |
# for cython, we also need to write the python callable wrappers. | |
cycode = """ | |
cdef extern from "%s/ccy.h": | |
int sum(int a, int b) | |
long inv_sum_between(int a, int b) | |
cpdef int pysum(int a, int b): | |
return sum(a, b) | |
cpdef long pyinv_sum_between(int a, int b): | |
return inv_sum_between(a, b) | |
""" % op.abspath(".") | |
with open('ccy.pyx', 'w') as fh: | |
fh.write(cycode) | |
import ccy | |
start = time.time() | |
a = 0 | |
for i in range(1000000): | |
a = C.sum(a, a) | |
print("adding 2 numbers with cffi wrapper: {:.3f} usec".format(time.time() - start)) | |
start = time.time() | |
a = 0 | |
for i in range(1000000): | |
a = ccy.pysum(a, a) | |
print("adding 2 numbers with cython wrapper: {:.3f} usec".format(time.time() - start)) | |
start = time.time() | |
a = 0 | |
for i in range(1000000): | |
a = C.inv_sum_between(1, 2000 + a % 2) | |
print("summing numbers inverses between 1 and 2000 with cffi wrapper: {:.3f} usec" | |
.format(time.time() - start)) | |
start = time.time() | |
a = 0 | |
for i in range(1000000): | |
a = ccy.pyinv_sum_between(1, 2000 + a % 2) | |
print("summing numbers inverses between 1 and 2000 with cython wrapper: {:.3f} usec" | |
.format(time.time() - start)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment