[Cython] GSoC Proposal - Reimplement C modules in CPython's standard library in Cython.

Arthur de Souza Ribeiro arthurdesribeiro at gmail.com
Wed Apr 20 05:04:25 CEST 2011


2011/4/17 Stefan Behnel <stefan_ml at behnel.de>

> Arthur de Souza Ribeiro, 17.04.2011 20:07:
>
>  Hi Stefan, about your first comment : "And it's better to let Cython know
>> that this name refers to a function."  in line 69 of encoder.pyx file I
>> didn't understand well what does that mean, can you explain more this
>> comment?
>>
>
> Hmm, sorry, I think that was not so important. That code line is only used
> to override the Python implementation with the implementation from the
> external C accelerator module. To do that, it assigns either of the two
> functions to a name. So, when that name is called in the code, Cython cannot
> know that it actually is a function, and has to resort to Python calling,
> whereas a visible c(p)def function that is defined inside of the same module
> could be called faster.
>
> I missed the fact that this name isn't really used inside of the module, so
> whether Cython knows that it's a function or not isn't really all that
> important.
>

So, I don't have to be worried about this, right?


>
> I added another comment to this commit, though:
>
>
> https://github.com/arthursribeiro/JSON-module/commit/e2d80e0aeab6d39ff2d9b847843423ebdb9c57b7#diff-4
>
>
>
I saw your comment and what I understood of it is that the alias that are
being attributed to the type names make code slower, I tried to compile in
cython the same way that it was in python, but, there is something wrong
with it. It says:

Error compiling Cython file:
------------------------------------------------------------
...
def _make_iterencode(dict markers, _default, _encoder, _indent, _floatstr,
        _key_separator, _item_separator, bint _sort_keys, bint _skipkeys,
bint _one_shot,
        ## HACK: hand-optimized bytecode; turn globals into locals
        ValueError=ValueError,
        dict=dict,
        float=float,
            ^
------------------------------------------------------------

encoder.pyx:273:13: Empty declarator

I turned that way because I think the user can maybe change what types are
going to be used and cython do not allow do these things like python. (for
reserved words)


>
>  About the other comments, I think I solved them all, any problem with them
>> or other ones, please tell me. I'll try to fix.
>>
>
> It looks like you fixed a good deal of them.
>
> I actually tried to work with your code, but I'm not sure how you are
> building it. Could you give me a hint on that?
>

I'm manually building them using setup.py files, for every module I create
one and build manually, I don't think that's the best way to do it, but, to
test things, that's the way I'm doing.


>
> Where did you actually take the original code from? Python 3.2? Or from
> Python's hg branch?
>
>
I took the original code from Python 3.2


>
>
>  Note that it's not obvious from your initial commit what you actually
>>> changed. It would have been better to import the original file first,
>>> rename
>>> it to .pyx, and then commit your changes.
>>>
>>
>> I created a directory named 'Diff files' where I put the files generated
>> by
>> 'diff' command that i run in my computer, if you think it still be better
>> if
>> I commit and then change, there is no problem for me...
>>
>
> Diff only gives you the final outcome. Committing on top of the original
> files has the advantage of making the incremental changes visible
> separately. That makes it clearer what you tried, and a good commit comment
> will then make it clear why you did it.
>
>
>
>  I think it's more important to get some performance
>>> numbers to see how your module behaves compared to the C accelerator
>>> module
>>> (_json.c). I think the best approach to this project would actually be to
>>> start with profiling the Python implementation to see where performance
>>> problems occur (or to look through _json.c to see what the CPython
>>> developers considered performance critical), and then put the focus on
>>> trying to speed up only those parts of the Python implementation, by
>>> adding
>>> static types and potentially even rewriting them in a way that Cython can
>>> optimise them better.
>>>
>>
>> I've profilled the module I created and the module that is in Python 3.2,
>> the result is that the cython module spent about 73% less time then
>> python's
>>
>
> That's a common mistake when profiling: the actual time it takes to run is
> not meaningful. Depending on how far the two profiled programs differ, they
> may interact with the profiler in more or less intensive ways (as is clearly
> the case here), so the total time it takes for the programs to run can
> differ quite heavily under profiling, even if the non-profiled programs run
> at exactly the same speed.
>
> Also, I don't think you have enabled profiling for the Cython code. You can
> do that by passing the "profile=True" directive to the compiler, or by
> putting it at the top of the source files. That will add module-inner
> function calls to the profiling output. Note, however, that enabling
> profiling will slow down the execution, so disable it when you measure
> absolute run times.
>
> http://docs.cython.org/src/tutorial/profiling_tutorial.html
>
>
As you said, I didn't enable profiling for Cython code, I did it and got a
bigger number of function calls if compared to the old ones. I added a new
test case for list object and profiled the code, as you said, They differ
exactly by the number of calls to isinstance function, the result stayed
like this:

------------------------------------ USING Profiler
------------------------------------
JSONModule nested_dict
Tue Apr 19 23:16:48 2011    Profile.prof

         200003 function calls in 0.964 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.964    0.964 {built-in method exec}
    50000    0.114    0.000    0.804    0.000 __init__.pyx:179(dumps)
    50000    0.217    0.000    0.690    0.000 encoder.pyx:193(encode)
    50000    0.473    0.000    0.473    0.000 encoder.pyx:214(iterencode)
    50000    0.089    0.000    0.893    0.000 {JSONModule.dumps}
        1    0.071    0.071    0.964    0.964 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


json nested_dict
Tue Apr 19 23:16:49 2011    Profile.prof

         300003 function calls in 1.350 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.350    1.350 {built-in method exec}
    50000    0.157    0.000    1.255    0.000 __init__.py:180(dumps)
    50000    0.115    0.000    0.115    0.000 {method 'join' of 'str'
objects}
    50000    0.558    0.000    0.558    0.000 encoder.py:193(iterencode)
    50000    0.317    0.000    1.099    0.000 encoder.py:172(encode)
        1    0.094    0.094    1.350    1.350 <string>:1(<module>)
   100000    0.108    0.000    0.108    0.000 {built-in method isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


JSONModule ustring
Tue Apr 19 23:16:49 2011    Profile.prof

         150003 function calls in 0.297 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.297    0.297 {built-in method exec}
    50000    0.099    0.000    0.160    0.000 __init__.pyx:179(dumps)
    50000    0.061    0.000    0.061    0.000 encoder.pyx:193(encode)
    50000    0.082    0.000    0.242    0.000 {JSONModule.dumps}
        1    0.055    0.055    0.297    0.297 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


json ustring
Tue Apr 19 23:16:50 2011    Profile.prof

         200003 function calls in 0.419 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.419    0.419 {built-in method exec}
    50000    0.118    0.000    0.346    0.000 __init__.py:180(dumps)
    50000    0.052    0.000    0.052    0.000 {built-in method
encode_basestring_ascii}
    50000    0.138    0.000    0.228    0.000 encoder.py:172(encode)
        1    0.072    0.072    0.419    0.419 <string>:1(<module>)
    50000    0.038    0.000    0.038    0.000 {built-in method isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


JSONModule xlist
Tue Apr 19 23:16:50 2011    Profile.prof

         200003 function calls in 0.651 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.651    0.651 {built-in method exec}
    50000    0.108    0.000    0.500    0.000 __init__.pyx:179(dumps)
    50000    0.154    0.000    0.392    0.000 encoder.pyx:193(encode)
    50000    0.238    0.000    0.238    0.000 encoder.pyx:214(iterencode)
    50000    0.086    0.000    0.585    0.000 {JSONModule.dumps}
        1    0.065    0.065    0.651    0.651 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


json xlist
Tue Apr 19 23:16:51 2011    Profile.prof

         300003 function calls in 1.029 seconds

   Random listing order was used

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.029    1.029 {built-in method exec}
    50000    0.145    0.000    0.940    0.000 __init__.py:180(dumps)
    50000    0.062    0.000    0.062    0.000 {method 'join' of 'str'
objects}
    50000    0.323    0.000    0.323    0.000 encoder.py:193(iterencode)
    50000    0.304    0.000    0.795    0.000 encoder.py:172(encode)
        1    0.089    0.089    1.029    1.029 <string>:1(<module>)
   100000    0.106    0.000    0.106    0.000 {built-in method isinstance}
        1    0.000    0.000    0.000    0.000 {method 'disable' of
'_lsprof.Profiler' objects}


----------------------------------------------------------------------------------------



>
>
>  (blue for cython, red for python):
>>
>
> Colours tend to pass rather badly through mailing lists. Many people
> disable the HTML presentation of e-mails, and plain text does not have
> colours. But it was still obvious enough what you meant.
>
>
Sorry about this.


>
>
> Thank you for the numbers. Could you add absolute timings using timeit? And
> maybe also try with larger input data?
>

Using timer, I got the following output:

------------------------------------ USING Timeit
--------------------------------------
JSONModule nested_dict spent 11.39 usec/pass (cython)
JSONModule ustring spent 0.94 usec/pass (cython)
JSONModule xlist spent 5.71 usec/pass (cython)

json nested_dict spent 16.61 usec/pass
json ustring spent 1.88 usec/pass
json xlist spent 10.38 usec/pass
----------------------------------------------------------------------------------------

The testcases are the same of the profile ones.


>
> ISTM that a lot of overhead comes from calls that Cython can easily
> optimise all by itself: isinstance() and (bytes|unicode).join(). That's the
> kind of observation that previously let me suggest to start by benchmarking
> and profiling in the first place. Cython compiled code has quite different
> performance characteristics from code executing in CPython's interpreter, so
> it's important to start by getting an idea of how the code behaves when
> compiled, and then optimising it in the places where it still needs to run
> faster.
>
>
As you said, starting profiling is a better approach, specially because
every change made reflects in time changes (using timeit of profiling).


> Optimisation is an incremental process, and you will often end up reverting
> changes along the way when you see that they did not improve the
> performance, or maybe just made it so slightly faster that the speed
> improvement is not worth the code degradation of the optimisation change in
> question.
>
> Could you try to come up with a short list of important code changes you
> made that let this module run faster, backed by some timings that show the
> difference with and without each change?
>

To let this module run faster, the bests changes were in classes definitions
(I'm going to show numbers soon) using cinit and defining the variables made
the code faster. Another changes that I made were in for loops. I created an
int variable and made to loop in range of a number instead of a 'for x in
...' statement. The other ones were about to add static types specially to
int and boolean types.


>
> Stefan
>


Thank you very much again.

Best Regards.

[]s

Arthur
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cython-devel/attachments/20110420/9170737a/attachment-0001.html>


More information about the cython-devel mailing list