Created
February 8, 2013 11:28
-
-
Save temporaer/4738296 to your computer and use it in GitHub Desktop.
Observations on the effect of using nogil or not in Cython function declarations
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
#cython: boundscheck=False | |
#cython: cdivision=True | |
#cython: wraparound=False | |
# While learning about Cython, I found code which proudly adds | |
# 'nogil' to function heads wherever possible. | |
# | |
# In the generated C-Code, the effect is mainly three lines: | |
# | |
# | |
# __Pyx_RefNannyDeclarations | |
# __Pyx_RefNannySetupContext("with_gil", 0); | |
# | |
# # function body here | |
# | |
# __Pyx_RefNannyFinishContext(); | |
# | |
# | |
# What exactly is the overhead of these lines in a single-threaded program? | |
# After some googling and discussion, I simply timed it with this cython | |
# module. It recursively calls a function /many/ times, either with, or without | |
# the `nogil` appendix to the function head. | |
# | |
# The result: On my machine, there is no significant difference. | |
# | |
# Number of function calls: 13492900 | |
# with nogil: | |
# 9.15090990067 | |
# without nogil: | |
# 9.17464494705 | |
# | |
# On the other hand, if you remove the default parameters (value=10), my | |
# compiler (gcc 4.6.3) picks up the tail recursion in the `nogil` case -- and | |
# optimizes all the function calls away, resulting in millisecond-runtime. | |
# Without nogil, speed also improves to 1.6s! I'd attribute this to the | |
# statistic above being dominated by the overhead of handling default | |
# parameters in Cython. | |
# | |
# | |
# Take home messages: | |
# - Do not use default parameters in cdef functions | |
# - Use the nogil, it may help the compiler do things that it can't do | |
# otherwise. | |
# | |
import timeit | |
cdef int without_gil(int value=10) nogil: | |
if value == 0: | |
return 1 | |
cdef int counter = 0 | |
for i in xrange(value): | |
counter += without_gil(value - 1) | |
return counter + value | |
cdef int with_gil(int value=10): | |
if value == 0: | |
return 1 | |
cdef int counter = 0 | |
for i in xrange(value): | |
counter += with_gil(value - 1) | |
return counter + value | |
def run_with_gil(): with_gil(10) | |
def run_without_gil(): without_gil(10) | |
def main(): | |
print "Number of function calls: ", with_gil(10) | |
print "with nogil: " | |
print timeit.timeit("gil_or_nogil.run_without_gil()", "import gil_or_nogil", number=100) | |
print "without nogil: " | |
print timeit.timeit("gil_or_nogil.run_with_gil()", "import gil_or_nogil", number=100) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment