Panda3D and Cython

This is about how to speed up your Python Code, and has no direct impact on Panda3D’s performance. For most projects, the vast majority of the execution time is inside Panda3D’s C++ or in the GPU, so no matter what you do, fixing your Python will never help. For the other cases where you do need to speed up your Python code, Cython can help. This is mainly addressed to people who prefer programming in Python, but know at least a little about C. I will not discuss how to do optimizations within Python, though if this article is relevant to you, you really should look into it.

Cython is an interesting programming language. It uses an extended version of python’s syntax to allow things like statically typed variables, and direct calls into C++ libraries. Cython compiles this code to C++. The C++ then compiles as a python extension module that you can import and use just like a regular python module. There are several benefits to this, but in our context the main one is speed. Properly written Cython code can be as fast as C code, which in some particular cases can be even 1000 times faster than nearly identical python code. Generally you won’t see 1000x speed increases, but it can be quite a bit. This does cause the modules to only work on the platform they were compiled for, so you will need to compile alternate versions for different platforms.

By default, Cython compiles to C, but the new 0.13 version supports C++. This is more useful as you probably use at least one C++ library, Panda3D. I decided to try this out, and after stumbling on a few simple issues, I got it to work, and I don’t even know C++.

Before I get to the details, I’ll outline why you might want to use Cython, rather than porting performance bottlenecks to C++ by hand. The main benefit is in the process, as well as the required skill set. If you have a large base of Python code for a project, and you decide some of it needs to be much faster, you have a few options. The common approach seems to be to learn C++, port the code, and learn how to make it so you can interface to it from python. With Cython, you can just add a few type definitions on variables where you need the performance increase, and compile it which gives you a Python modules that works just like the one you had. If you need to speed up the code that interfaces with Panda3D, you can swap the Python API calls for C++ ones. Using Cython allows you to just put effort into speeding up the parts of code you need to work on, and to do so without having to change very much. This is vastly different from ditching all the code and reimplementing it another language. It also requires you to learn a pretty minimal amount of stuff. You also get to keep the niceness of the Python syntax which may Python coders have come to appreciate.

There are still major reasons to actually code in C++ when working with Panda, but as someone who does not do any coding in C++, I won’t talk about it much. If you want to directly extend or contribute to Panda3D, want to avoid redundantly specifying your imports from header files (Cython will require you to re-specify the parts of API you are using rather than just using the header files shipped with Panda), or you simply prefer C++, C++ may be a better option. I mainly see Cython as a convenient option when you end up needing to speed up parts of a Python code-base; however, it is practical to undertake large projects from the beginning in Cython.

Cython does have some downsides as well. It is still in rather early development. This means you will encounter bugs in its translators as well as the produced code. It also lacks support for a few Python features, such as most uses of generators. Generally I haven’t had much trouble with these issues, but your experience may differ.

Cython does offer an interesting side benefit as well. It allows you to optionally statically type variables and thus can detect more errors at compile time than Python.

To get started, first you need an install of Cython 0.13 (or probably any newer version). If you have a Cython install you can check the version with the -V command. You can pick up the source from the Cython Site, and install it by running “python setup.py install” from the Cython source directory. You will also need to have a compiler. The Cython site should help you get everything setup if you need additional guidance.

Then you should try out a sample to make sure you have everything you need, and that it’s all working. There is a nice C++ sample for Cython on the Cython Wiki. (This worked for me on Mac, and on Windows using MinGW or MSVC as a compiler).

As for working with Panda3D, there are a few things I discovered:

  • There are significant performance gains to be had by just compiling your existing Python modules as Cython. With a little additional work adding static typed variables, you can have larger performance gains without even moving over to Panda’s C++ API (Which means you don’t need to worry about linking against Panda3D which can be an issue).
  • Panda3D already has python bindings with nice memory management, so I recommend instancing all the objects using the python API, and only switching to the C++ one as needed.
  • You can use the ‘this’ property on Panda3D’s Python objects to get a pointer to the underlying C++ object.
  • On mac, you need to make sure libpanda (and is some cases, possibly others as well) is loaded before importing your extension module if you use any of Panda3D’s libraries.
  • On Windows, you need to specify the libraries you need when compiling (in my case, just libpanda)
  • The C++ classes and Python classes tend to have the same name. To resolve this, you can use “from x import y as z” when importing the python ones, or you can just import panda3d.core, and use the full name of the classes (like panda3d.core.Geom). There may be a way to rename the C++ classes on import too.
  • If using the Panda3D C++ API on Windows, you will need to use the MSVC compiler. You can get Microsoft Visual Studio 2008 Express Edition for free which includes the needed compiler.

Using this technique I got a 10x performance increase on my code for updating the vertex positions in my Geom. It avoided having to create python objects for all of the vertexes and passing them through the Python API which translates them back to C++ objects. It was just a matter of moving over one call in the inner loop to the other API. This, however, was done in already optimized Cython code that was simply loading vertex positions stored in a block of memory into the Geom. Most use cases would likely see less of a benefit. Overall though, I gained a lot of performance both from the change over to Cython, and from the change over to the C++ API. These changes only required relatively small changes to the speed critical portions of my existing python code.

I made a rather minimal example of using a Panda3D C++ API call from Cython. Place the setup.py and the testc.pyx files in the same directory, and from the said directory, run setup.py with your Python install you use with Panda3D. If everything is properly configured, this should compile the example Cython module, testc.pyx, to a python extension module and run it. If it works, it will print out a few lines ending with “done”. It is likely you may need to tweak the paths in setup.py. If not on Mac or Windows, you will get an error indicating where you need to enter your compiler settings (mostly just the paths to Panda3D’s libraries).

I would like to thank Lisandro Dalcin from the Cython-Users mailing list who helped me get this working on Windows.

19 thoughts on “Panda3D and Cython

  1. Marcc3d, I addressed the issues in the first paragraph. Basically it depends vastly on the specifics of the application, and what you convert to Cython. If you are suffering from python running to slow, Cython can help. If you don’t know what is causing your performance issues, doing any optimization is foolish and a waste of time. You can run text-stats or pstats to determine what is taking most of the time. See the manual: http://www.panda3d.org/manual/index.php/Measuring_Performance_with_PStats (I should update the post to include this probably)

  2. A very interesting article. Big thanks!
    Depending on how well I do optimizing my physics code, I might fall back to Cython. Only the fact that your code won’t be portable somehow turns me off.
    Considering this Cython is a kind of easy but wobbly way to get things done for people who don’t have any C++ skills. Are there any other ways mixing Panda’s C++ side with Python? If so, what are those? I guess interrogate is one such way, and it’s most probably more stable, right? How about a small summary and comparison? I’m sure you have lots of experience on that topic 🙂
    Another question I find interesting is: what way would you go to write fast and os-independent, portable code within panda that can be used in conjunction with python? Strike the “portable” part if that’s not possible at all.
    Have a nice day
    Neme

  3. Neme, Cython code is portable, you just have to compile it for the target operating system. My example works great for me on Mac and Windows for example. The included setup.py script handles all the platform specific compiling issues. It should work on any system which you can compile C++ for, which means all of Panda’s supported platforms. It is possible to write Cython code that can run as uncompiled in “pure python mode”, and thus works even if no compiled version is available (but without the speed gains), but I haven’t tried or needed it.
    Cython’s flakey issues are mainly with its source translator, and some issues with compiler compatibility. For me there has been one instance that it produced code GCC could compile but MSVC could not (Not Cython’s fault, but Cython’s problem I imagine). Only that one instance of that in over 2000 lines if Cython that I’ve integrated into one of my projects. In the previous version of Cython, I crashed the translator a few times when the source code that actually completely invalid, or I used features from Python that were not yet supported. Clearly its not finished, or polished, but I have never had an actual issue at runtime cause by a bug in Cython.
    And I can’t summarize many other options because I don’t know C++. I did try writing a python extension module in C before I learned about Cython. I later ported that code back into Cython because it was easier to integrate into my Python code that way, and it also let me use Panda’s C++ API which would be harder (it possible) from pure C code. Basically Cython does everything I want with little to no speed cost, and is easy to use and fast to write, so I haven’t bothered with harder things yet.

  4. anon: PyPy is not relevant. It’s an alternative to CPython which I believe would not work with Panda3D at all, nor provide any significant speed gains. I haven’t used Psyco, but I think it would not allow access to Panda’s C++ API as Cython does, does not support all of panda’s supported platforms, would use more memory, and probably have more start up overhead and less performance increases. Psyco does look like it may have some benefits, but I don’t see it as an good choice for use with Panda3D when compared to Cython.

  5. Very interesting this topic and congratulations for the new option!
    I was thinking for some time in make a .NET (C#) support for the Panda3D, so you could program with C# or Boo languages using the Panda… I still thinking about it, but your option is a good way to work anyway.

  6. Handloomweaver: Can you compile C++ for iOS? Yes. So you can compile Cython code for iOS. There is nothing about Cython that would prevent you from using it with Panda in iOS. Getting Panda to work is another issue, put apparently possible. As I mentioned, Cython should work with all platforms you can compile Panda for.

  7. Pysco is kind of a hack and seeing as its a JIT, it would take alot of overhead and alot of start up time. On top of that Pysco doesn’t have the ability to do static typing, so it wouldn’t give nearly the same performance boost as Cython.
    I also have to agree this won’t really improve your performance unless you’re doing some kind of crazy calculations in python, or possibly if you’re doing a very large game. Unless you have a significant amount of code execution(in python), then you probably won’t see any speed increase. You probably won’t see any FPS increase, maybe slightly, it may stabilize it a bit, but it will probably mostly improve execution speed. It won’t make graphic processes go any faster because those are already handled in C++ via OpenGL, which means most likely its hardware accelerated.
    Cython is definitely something to look into because people seem to keep porting Panda3D over to consoles, mobile devices, etc. but there only problem is the fact that porting the entire python interpreter over and linking it up to the Panda3D port is just a far too complicated task for a hobbyist. So if you want to write for these platforms in the future its a good idea to learn Cython so you can distribute on those platforms while still using Python code instead of C++.

  8. darkpotpot: I have gotten it to work on linux. It’s the same as the mac version, except you need to include the directory ‘/usr/include/panda3d/’ instead of the corresponding one for the mac. Both use gcc so its about the same. However, recently there has been a new cython release (0.14) thats supposed to have an improved build system so things may be able to be simplified a bit now.

  9. That’s what I did and when launching setup.py, I have this error :
    Traceback (most recent call last):
    File “setup.py”, line 72, in
    import testc
    ImportError: /home/julien/Downloads/cython_panda/testc.so: undefined symbol: _ZTI11TypedObject
    Also, when trying to use it in my own project, I have a crash when calling add_solid on a CollisionNode. Everything else seems to work.

  10. darkpotpot: That error is caused by (on mac or linux) loading a cython module before panda modules that it references are loaded. Panda’s internal c++ modules seem to be loaded as needed, but this fails when you avoid the python import and go straight through cython. You need to force the parts of panda you need to load before doing the import. This can be done by importing them in python. The fix is on a mac specific if at the bottom, so enable that for linux too.

  11. Weird, I didn’t change anything in the example so I call what is in the if. I also tried to call “from panda3d.core import Vec3” but no changes. I installed cython0.14 to test and it doesn’t works either.
    Just to be sure, the import stuff need to be done just before the “import testc” ?

  12. For anyone else looking for the answer to darkpotpot’s question, you need to add in the panda3d libraries to the linker phase like so:
    args[‘libraries’]=[‘p3framework’, ‘panda’, ‘pandafx’, ‘pandaexpress’, ‘p3dtoolconfig’, ‘p3dtool’, ‘p3pystub’, ‘p3direct’]
    before the compilation happens.

Comments are closed.