We’ve been using a lot of Blender Properties in our application. We’ve found that “RNA” properties are very slow (and don’t scale well with size), so we’ve tried to start using “ID” properties … which have been better. But there still seems to be a scaling problem with them as well.
As an example, I wrote a small addon (included below) that just loops while adding elements to a dictionary and records the amount of time it takes. In the pure Python case (running in a Blender Addon, of course), I use a regular python dictionary:
s = {}
for i in range(int(numsamps)):
s['a'+str(i)] = 2 * i
In the Blender case, I create the dictionary as a Blender ID property and perform the exact same operations:
bpy.context.scene['s'] = {}
s = bpy.context.scene['s']
for i in range(int(numsamps)):
s['a'+str(i)] = 2 * i
Here are the results for different numbers of passes (given in seconds):
[TABLE=“class: grid, width: 500, align: center”]
Passes
Python
Blender
5000
0.01
0.08
10000
0.01
0.33
15000
0.00
0.70
20000
0.01
1.24
25000
0.02
1.93
30000
0.02
2.77
35000
0.01
3.78
40000
0.02
5.01
45000
0.03
9.50
50000
0.03
12.51
55000
0.02
16.30
60000
0.03
22.84
[/TABLE]
In this little example, the Blender dictionary is taking over 700 times longer than the regular Python dictionary … and it gets worse as the number of samples gets larger!! More importantly, the Python dictionary is relatively flat (as a dictionary should be), but the Blender dictionary appears to be taking exponentially longer as it holds more items.
Does anyone know what’s going on?
Here’s a screen shot of my testing addon:
Here’s the full source code of my testing addon:
"""
This program compares the speed of Python and Blender Dictionaries.
"""
bl_info = {
"version": "0.1",
"name": "Dictionary Speed Testing",
'author': 'BlenderHawk',
"location": "Properties > Scene",
"category": "Blender Experiments"
}
import bpy
from bpy.props import *
import time
########################################################
class AppPropertyGroup(bpy.types.PropertyGroup):
start_samps = IntProperty ( name="Start", min=0, default=1000, description="Starting Number of Samples" )
stop_samps = IntProperty ( name="Stop", min=0, default=32000, description="Stopping Number of Samples" )
step_by = FloatProperty ( name="By", min=1, default=1000, description="Step amount (added or multiplied" )
linear = BoolProperty ( name="Linear", default=True, description="Step by addition(T) or multiplication(F)" )
run_results = StringProperty ( name="Run Results", default="", description="Space separated results with '~' for new row" )
def clear_results (self, context):
self.run_results = ""
def speed_test (self, context):
numsamps = self.start_samps
while numsamps <= self.stop_samps:
if len(self.run_results) == 0:
# Add the header for each column
self.run_results = "Count Python Blender"
self.run_results = self.run_results + "~"
self.run_results = self.run_results + str(int(numsamps))
print ( "Running test with " + str(int(numsamps)) + " samples..." )
s = {}
start = time.clock()
for i in range(int(numsamps)):
s['a'+str(i)] = 2 * i
duration = time.clock() - start
self.run_results = self.run_results + " " + "%8.6g"%duration
print ( " Python took " + "%8.6g"%duration + " seconds" )
bpy.context.scene['s'] = {}
s = bpy.context.scene['s']
start = time.clock()
for i in range(int(numsamps)):
s['a'+str(i)] = 2 * i
duration = time.clock() - start
self.run_results = self.run_results + " " + "%8.6g"%duration
print ( " Blender took " + "%8.6g"%duration + " seconds" )
if self.linear:
numsamps = numsamps + self.step_by
else:
numsamps = numsamps * self.step_by
if len(self.run_results) > 0:
print ( "===== Results =====" )
result_rows = self.run_results.split('~')
for r in result_rows:
l = ""
result_cols = r.split()
for c in result_cols:
try:
cf = float(c)
l = l + " " + "%8.6g"%cf
except:
l = l + " " + "%8s"%c
print ( l )
def draw_self (self, layout):
row = layout.row()
row.label("Speed Testing:")
row = layout.row()
col = row.column()
col.prop(self,"start_samps")
col = row.column()
col.prop(self,"stop_samps")
col = row.column()
col.prop(self,"step_by")
col = row.column()
col.prop(self,"linear")
row = layout.row()
col = row.column()
col.operator("app.speed_test", text="Run Test")
col = row.column()
col.operator("app.clear_results", text="Clear Results")
if len(self.run_results) > 0:
row = layout.row()
row.label ( "===== Results =====" )
result_rows = self.run_results.split('~')
for r in result_rows:
row = layout.row()
result_cols = r.split()
for c in result_cols:
col = row.column()
col.label ( c )
########################################################
class APP_OT_clear_results_operator(bpy.types.Operator):
bl_idname = "app.clear_results"
bl_label = "Clear Results"
bl_description = "Clear Results"
bl_options = {'REGISTER'}
def execute(self, context):
context.scene.app.clear_results(context)
return {'FINISHED'}
class APP_OT_speed_test_operator(bpy.types.Operator):
bl_idname = "app.speed_test"
bl_label = "Speed Test"
bl_description = "Speed Test"
bl_options = {'REGISTER'}
def execute(self, context):
context.scene.app.speed_test(context)
return {'FINISHED'}
class APP_PT_Dictionary(bpy.types.Panel):
bl_label = "Speed Testing"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
context.scene.app.draw_self(self.layout)
def register():
print ("Registering ", __name__)
bpy.utils.register_module(__name__)
bpy.types.Scene.app = bpy.props.PointerProperty(type=AppPropertyGroup)
def unregister():
print ("Unregistering ", __name__)
del bpy.types.Scene.app
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()