Great question! I’ll answer the two possible interpretations of it:
- Why is the development process slow with Python?
- Why is the run-time performance slow with Python?
Slow Development Process
And the development process of Ragdoll was indeed slow, but not because of Python. Python, compared to C++, can be much quicker to develop with because development with C++ - and indeed any compiled language - involves a compilation step, meaning each change looks like this.
- Edit
- Compile
- Run
Each of which takes time. For example:
- Edit (1 minute)
- Compile (1-20 seconds)
- Run (10 seconds)
Whereas with Python, (2) is skipped entirely, saving precious time.
The development of Ragdoll for Blender was instead slow due to the Blender API and architecture, specifically:
- Undo and persistence
- Objects versus Bones
See, Ragdoll is like Blender in that it has its own objects, connections between objects, a rendering pipeline and compute stage. So this integration really is a synchronised ballet between the two; whatever happens in Blender needs to be reflected in Ragdoll and vice versa.
This is where the first obstacle becomes relevant; undo. See, whenever you undo in Blender, the memory address to every object and bone is erased. (My understanding is that Blender implements the Memento Pattern for undo/redo). We can keep track of the address to a Ragdoll object by storing it as a property on a Blender object; the Ragdoll memory addresses do not change. But we cannot store the equivalent address to a Blender object within a Ragdoll object, because it cannot be trusted.
We solved this by monitoring for undo, and doing an equivalent wipe on our end; setting a “dirty flag” for every object involved with Ragdoll and scanning the scene for the lost memory address via (1) a unique code (a uuid) placed on every bone and (2) the “session_uuid” used by the Blender source code internally. I’ll break this down further once our code is up on GitHub, but for the curious mind I encourage you to look into the ragdoll/vendor/bpx.py
library shipped with the Ragdoll Addon download above.
The next obstacle is Objects versus Bones. In Ragdoll, there is only 1 type of object, called an “entity”. In Blender, there are objects, and then there are bones. These act different, they have their own constraints and their own keyable channels and expose their position and orientation differently. Because of this, we’ve effectively integrated Ragdoll twice into one application. For the user, applying physics to bones and objects should be seamless; you won’t notice any difference. But it effectively doubled the time taken to integrate with Blender.
Ok, so that was one of the things that complicated and slowed down the development of Ragdoll, but the next interpretation of your question is slow as in poor run-time performance, which is also a thing.
Slow Run-time Performance
Run-time performance is in regards to the FPS you get during playback, and there are two reasons why this is slower than Ragdoll in Maya.
- Interpreted versus Compiled language
- Python-to-C++ and back again
Compiled languages take source code and translate them into machine code that runs directly on your CPU. It’s the purest form of performance; you literally trigger the compute units on your CPU to perform some action, like add or multiply numbers, call some function.
Interpreted langauges on the other hand is akin to asking someone else to run the code for you. Your source code is converted into function calls in the Python API which in turn translate into machine code. I’ll leave you to Google why this is, as this applies to not only Python versus C++ but any compiled versus interpreted language.
In the case of Ragdoll and Blender specifically, (2) is referring Ragdoll being a compiled Python extension (.pyd
) and the cost involved in passing data between Python and C++.
See, with a compiled language, accessing data means reading from a memory address of your RAM. It’s as fast as a computer is able to be; in the order of tens of gigabytes per second. Accessing a Blender property on the other hand is nowhere near this simple.
- Python calls
print(bpy.context.scene.object.someProperty)
- Since
object
is a C++ struct, a conversion of the request is made into C++
- C++ finds
someProperty
, which may involve lookup via a map of <string, value>
pairs
- C++ returns the value to Python, involving another conversion
- Python prints the value
This is already very slow. On the order of 100-1000x slower than accessing just the memory address of some data via a compiled language.
Now add to this that Ragdoll, liked Blender, is also a Python extension written in C++. So whenever we pass data from Blender to Ragdoll, we double the amount of work shown above.
Here’s some early performance tests to bring the point home.
# Current Case
>>> timeit.timeit('m["shapeRadius"].read()', setup='from ragdoll.vendor import bpx;m = bpx.ls(type="rdMarker")[0]', number=100000)
0.6791226999484934
# Without finding BpxProperty
>>> timeit.timeit('a.read()', setup='from ragdoll.vendor import bpx;m = bpx.ls(type="rdMarker")[0];a = m["shapeRadius"]', number=100000)
0.4856376000097953
# Static Blender Property
>>> timeit.timeit('m.rdMarker.shapeRadius', setup='import bpy;m = bpy.context.object', number=100000)
0.03511430002981797
# Dynamic Blender Property
>>> timeit.timeit('m["dynamic"]', setup='import bpy;m = bpy.context.object;m["dynamic"] = 5', number=100000)
0.004500499984715134
# Plain Python
>>> timeit.timeit('m.shapeRadius', setup='import bpy;m = type("Object", (object,), {"shapeRadius": 5})()', number=100000)
0.003075299959164113
In laymans terms, we’re accessing a plain Python property 100,000 times in 0.3 ms, compared to 35 ms to access a property within a Blender property group. Our work is not done however, since we do more logic on-top of this to deal with the undo and persistence issue mentioned above, resulting in the same workload taking 680 ms; a 2000x slowdown from just accessing the pure Python attribute - which in itself is still not as fast as accessing memory in a compiled language, probably still 10-100x slower.
So, that’s the short version of this answer.
There is much more to tell, so feel free to ask about any particular area that interests you. And, if you spot something we’ve gotten wrong - especially in bpx.py
which encapsulates all of our performance findings and optimisations, and will make more visible on GitHub shortly - do let us know. We are still new to Blender and expect to make more discoveries as time goes on.