Jump to content

Blender 3D: Blending Into Python/Optimize

From Wikibooks, open books for an open world

Writing Optimal Exporters and Importers for Blender:Python

[edit | edit source]

If you are starting out in writing a script, don't take these guidelines too seriously, its worth getting things working and then sight a need for optimization before you begin to look for areas to optimize your script.

If you're making a tool for others, keep in mind that they might use it with larger datasets than those you have been testing with. So before releasing it can be good time to try to optimize.

For more generic python performance tips see http://wiki.python.org/moin/PythonSpeed/PerformanceTips

Getting Objects (Exporting/General Blender)

[edit | edit source]

Blender.Object.Get() is often used to get all objects for exporting. This is bad practice since Object.Get() will return objects from every scene in Blender, this is almost never what the user wants and could result in overlapping data, but also could take a long time if the user has 2 or more large scenes consuming a lot of memory.

Instead use Blender.Scene.GetCurrent().getChildren() This returns all objects from the current scene.

Another alternative is to use Blender.Object.GetSelected() which returns selected objects on visible layers in the current scene.

Getting Mesh data (Exporting Only)

[edit | edit source]

Meshes are not just thin wrappers like most of the other Types in Blender so you want to avoid meshObject.getData() and meshObject.data as much as possible, its best practice only calling each mesh data once.

To get the name of the data the object references do not use ob.getData().name simply to get the name of the object. Instead use obj.getData(1) ... meaning obj.getData(1=name_only) this is nice because it works for all data types, its just that

Note, recently (as of 15/10/05) the addition of the Mesh module (a thin wrapper around Blenders mesh data) means that you can do a meshObject.getData(mesh=1) without the problem NMesh has, however this is new and doesn't support all the NMesh functions.

Use new python functionality mesh.transform() (Exporting Only)

[edit | edit source]

As of Blender 2.37 nmesh.transform(ob.matrix) can be used to transform an NMesh by a 4x4 transformation matrix.
On my system it is over 100 times faster than transforming each vert's location and normal by a matrix within python. Though the overall speed gained by using this in my obj exporter was about %5.

make sure that if you are exporting vertex normals add a 1,
mesh.transform(matrix, recalc_normals=1) This avoids un-needed vertex normal transforming.

Beware of python hogging memory. (python generally)

[edit | edit source]

You may ignore this point, for small applications, but I have found that for larger scenes this can become a problem if not allowed for early on.


A List in python (and therefore blender:python) is never deallocated, however python can re-use the memory whilst the script is running. Basically making large lists in python and a lot of recursive variable assignment can leak memory that can only be regained with a restart. - *If you can- avoid making large lists.

This is Fixed in Python 2.4.1 which will compile with blender.

Lists (Python General)

[edit | edit source]

List Lookup

[edit | edit source]

In Python there are some tricky list functions that save you having to search through the list.
Even though you're not looping on the list data python is, so you need to be aware of functions that will slow down your script by searching the whole list.

myList.count(listItem)
myList.index(listItem)
myList.remove(listItem)
if listItem in myList: ...

The above functions performs a full loop on the list, so if you can avoid using them on large lists, your script will run faster.

Modifying Lists

[edit | edit source]

In python we can add and remove from a list, This is slower when lists are modified in length at the start of the list, since all the data after the index of modification needs to be moved up or down 1 place.

Of course the most simple way to add onto the end of the list is to use myList.append(listItem) or myList.extend(someList) and the fastest way to remove an item is myList.pop()

To use an index you can use myList.insert(index, listItem) and pop also takes indicies for list removal, but these are slower.

Sometimes its faster (but more memory hungry) to just rebuild the list.
Say we want to remove all triangle faces in a list.

Rather then

fIdx = len(mesh.faces) # Loop backwards
while fIdx: # While the value is not 0
	fIDx -=1

	if len(mesh.faces[fIDx].v) == 3:
		mesh.pop(fIdx) # Remove the tri

It is faster to build a new list with list comprehension.

mesh.faces = [f for f in mesh.faces if len(f.v) != 3]

Adding list items

[edit | edit source]

If you have a list that you want to add onto another list, rather then appending in a for loop, use

myList.extend([li1, li2...])

instead of...

for l in someList:
	myList.append(l)

Note that insert can be used when needed, but it is slower than append especially when inserting at the start of a long list.
This example shows a very suboptimal way of making a reversed list.

for l in someList:
	myList.insert(0,l)


Removing List Items

[edit | edit source]

Use myList.pop(i) rather then myList.remove(listItem)
This requires you to have the index of the list Item but is faster since remove needs to search the list.
Using pop for removing list items favors a while loops instead of a for loop.

Here is an example of how to remove items in 1 loop, removing the last items first, which is faster (as explained above)

listIndex = len(myList)
while listIndex:
	listIndex -=1
	if myList[listIndex].someTestProperty == 1:
		myList.pop(listIndex)


A fast way of removing items, where you can mess up the list order, is to swap 2 list items, so the item you remove is always last.

popIndex = 5

# Swap so the popIndex is last.
myList[-1], myList[popIndex] = myList[popIndex], myList[-1]

# Remove last item (popIndex)
myList.pop()

When removing many items in a large list this can provide a good speedup.

Avoid Copying Lists

[edit | edit source]

When passing a list/dictionary to a function, it is better to have the function modify the list rather then returning a new list. This means python dosn't need to create a new list in memory.
Functions that modify a list in their place are more efficient then functions that create new lists.
normalize(vec) faster: no re-assignment ...is faster then.
vec = normalize(vec) slower, only use for functions that are used to make new, unlinked lists.

Also be note that passing a sliced list makes a copy of the list in python memory eg..
foobar(mylist[4:-1])
If mylist was a large list of floats, a copy could use a lot of extra memory.

Dictionaries (Python General)

[edit | edit source]

Dict Lookup

[edit | edit source]

When you access a dictionary item, someDict['foo'] A lookup is performed, pythons lookups are very fast, but if your are accessing this data in a loop its better to make a variable that can be referenced faster.

for key in someDict.keys():
...wip

Strings

[edit | edit source]

Pathnames (Exporting)

[edit | edit source]

Blender pathnames are not compatible with path names from other generic python modules.
python native functions don't understand blenders // as being the current file dir in blender and # to be the current framenumber.
A common example is where your exporting a scene and want to export the image paths with it.
Blender.sys.expandpath(img.filename)
Will give you the absolute path, so you can pass the path to functions from other python modules.

Correct string parsing (Import/Exporting)

[edit | edit source]

Since many file formats are ASCII, the way you parse/export strings can make a large difference in how fast your program runs.
When importing strings to make into blender there are a few ways to parse the string.

Passing String

[edit | edit source]

Use float(string) rather then eval(string) and if you know the value will be an int then int(string) float() will work for an int too but its faster if ints are converted as ints.

Checking String Start/End

[edit | edit source]

If your checking the start of a string for a keyword, use...

if line.startswith('vert '):

...rather then

if line[0:5] == 'vert ':


Using Startswith is slightly faster (about 5%).
myString.endswith('foobar') can be used for line endings too.
Also, if your unsure whether the text is upper or lower case use lower or upper string function. Eg. if line.upper().startswith('VERT ')

Writing Strings to a File (Python General)

[edit | edit source]

Here are 3 ways of joining multiple strings into 1 string for writing
This really applies to any area of your code that involves a lot of string joining.

Pythons string addition. Dont use if you can help it, especially in the main data writing loop.

file.write(str1 + ' ' + str2 + ' ' + str3 + '\n')


String formatting. Use this when you're writing string data from floats and int's

file.write('%s %s %s\n' % (str1, str2, str3))


Pythons string joining function. To join a list of strings

file.write(' '.join([str1, str2, str3, '\n']))


join is fastest on many strings, string formatting is quite fast too (better for converting data types). And string arithmetic is slowest.

Other

[edit | edit source]

Profile your code (Python General)

[edit | edit source]

Simply timing sections of your code will point you to the part that needs optimizing. Eg

time1 = Blender.sys.time()

for i in range(10):
	x = i*2

print Blender.sys.time() - time1

Psyco JIT Compiler (Python General)

[edit | edit source]

A python module called psyco exists that can dynamically compile some of python functions. Most of the speed gains affect algorithms written in python, so importers and exporters have less to gain from them then scripts that deal with 3d math.

For many scripts, a no brainier way of using psyco is to do a.

import psyco
psyco.full()

Psyco can also profile your code.

import psyco
psyco.profile()


For scripts to be distributed it is polite to try loading psyco to avoid raising an error.

try:
	import psyco
	psyco.full()
except:
	print 'For optimal performance on an x86 CPU, install psyco'

Note that psyco reports to use a lot of system memory, if you run out of ram it may be best not to use psyco.

Use Try/Except Sparingly

[edit | edit source]

The try function is useful to save time writing code to check a condition,
However 'try' is about 10 times slower than 'if', so don't use 'try' in areas of your code that execute in a loop that runs many times (1000's or more).

There are cases where using 'try' is faster than checking whether the condition will raise an error, so it is worth experimenting.

Python Objects

[edit | edit source]

Use "is" instead of "=="

[edit | edit source]

In some cases "is" can be used instead of "==" for comparison, "is not" for "!=".
"==" Checks whether the 2 variables are the same value, where as "is" tests that the python object is the same, shares the same memory - are both an instance of the same object. (Python Object, not Blender a Object)
The advantage of using "is" is that its faster since it dosent need to compare as much data.

Here is a benchmark test that compares a python class and a blender vector- one the same and one different.

from Blender import *

print '\n\nStarting benchmark.'

# Test class
class a:
        pass

class1 = a()
class2 = a()
vec1 = Mathutils.Vector(1,2,3)
vec2 = Mathutils.Vector(1,2,4)

f = 400000

t = sys.time()
for i in range(f):
        class1 is class1
        class1 is class2
        vec1 is vec1
        vec1 is vec2
print ' Is Banchmark %.6f' % (sys.time() - t)


t = sys.time()
for i in range(f):
        class1 == class1
        class1 == class2
        vec1 == vec1
        vec1 == vec2

print ' == Benchmark %.6f' % (sys.time() - t)
print 'Done\n'

My Computer showed the following results. I should do more tests- but this is representative of other tests.

Starting benchmark.
 Is Banchmark 0.284363
 == Benchmark 3.367173
Done


The same test with vec1, vec2, class1, class2 as ints and floats.
"is" still faster.

Starting benchmark.
 Is Banchmark 0.272853
 == Benchmark 0.322123
Done