Skip to content

Instantly share code, notes, and snippets.

@Micky774
Created December 29, 2022 01:02
Show Gist options
  • Save Micky774/d5c7ecbde3cbb90a7b05d5a680bb2bd0 to your computer and use it in GitHub Desktop.
Save Micky774/d5c7ecbde3cbb90a7b05d5a680bb2bd0 to your computer and use it in GitHub Desktop.
Cython vtable workarounds
# Compile with annotated view to compare generated instructions
# and presence of `__pyx_vtabstruct_*`
from cython cimport final
# Typedef function pointer
ctypedef int (*f_type)(A)
# Ancestor, serves as interface/ABC
cdef class A:
cdef f_type func
def __init__(self):
self.func = self.test
cdef int test(self):
return 1
# Entire class is finalized, therefore avoids vtable lookup
@final
cdef class B(A):
cdef int test(self):
return 2
# Relevant method is finalized, therefore avoids vtable lookup
cdef class C(A):
@final
cdef int test(self):
return 3
# Nothing is finalized, therefore still viable for vtable lookup
cdef class D(A):
cdef int test(self):
return 4
# Fused type of all finalized classes. Such a list is potentially difficult to
# maintain, but also makes concrete implementations clear.
ctypedef fused FINALIZED:
B
C
cdef class Tester:
cdef A x
def __init__(self, A x):
self.x = x
cdef void run_test_ref(self, Py_ssize_t n):
"""
We use a stored reference to the method and feed it the object it ought
to be bound to in order to avoid vtable lookups. This avoids the need for
finalization entirely, although it is still be good practice.
"""
cdef:
Py_ssize_t idx
f_type f = self.x.test
A x = self.x
for idx in range(n):
# Since f is unbound, we must give it the object it originated from.
# Potentially risky from a maintenance perspective, but should be
# manageable.
f(x)
cdef void run_test_fused(self, Py_ssize_t n, FINALIZED x):
"""
We use a fused-type composed of only objects with finalized methods to
avoid vtable lookups.
"""
cdef Py_ssize_t idx
for idx in range(n):
x.test()
cdef void run_test_brute(self, Py_ssize_t n, A x):
"""
We do nothing to avoid vtable lookups.
"""
cdef Py_ssize_t idx
for idx in range(n):
x.test()
cdef:
B b = B()
C c = C()
D d = D()
Tester T_B = Tester(b)
Tester T_C = Tester(c)
Tester T_D = Tester(d)
@jjerphan
Copy link

jjerphan commented Jan 4, 2023

Thanks for exploring this, @Micky774 and @thomasjpfan!

https://gist.github.com/jjerphan/bb6a9545e4bbbcb4a7d1b15af8e1b5d1 gives some information about the Cython Extension Types' Methods' resolution using vtable. It is extracted from a thread of discussion started by a relevant question from @thomasjpfan in scikit-learn/scikit-learn#20254.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment