I know this is really specific, but I don't really know which community to approach here.
I am currently trying to speed up my Python addon for Blender using Numba. As you might know, functions sped up with Numba's njit
decorator refuse to take any argument that is not a numpy array or a Python primitive.
The solution to this problem is to write so called StructRefs.
If you want to try it out install Numba into your Blender-Python environment and paste the following code into your clipboard:
import sys
import subprocess
import bpy
# Blender's Python executable
pybin = bpy.app.binary_path_python
# Locate users site-packages (writable)
user_site = subprocess.check_output([pybin, "-m", "site", "--user-site"])
user_site = user_site.decode("utf8").rstrip("\n") # Convert to string and remove line-break
# Add user packages to sys.path (if it exits)
user_site_exists = user_site is not None
if user_site not in sys.path and user_site_exists:
sys.path.append(user_site)
import numpy as np
from numba import njit
from numba.core import types
from numba.experimental import structref
from numba.tests.support import skip_unless_scipy
# Define a StructRef.
# `structref.register` associates the type with the default data model.
# This will also install getters and setters to the fields of
# the StructRef.
@structref.register
class MyStructType(types.StructRef):
def preprocess_fields(self, fields):
# This method is called by the type constructor for additional
# preprocessing on the fields.
# Here, we don't want the struct to take Literal types.
return tuple((name, types.unliteral(typ)) for name, typ in fields)
# Define a Python type that can be use as a proxy to the StructRef
# allocated inside Numba. Users can construct the StructRef via
# the constructor for this type in python code and jit-code.
class MyStruct(structref.StructRefProxy):
def __new__(cls, name, vector):
# Overriding the __new__ method is optional, doing so
# allows Python code to use keyword arguments,
# or add other customized behavior.
# The default __new__ takes `*args`.
# IMPORTANT: Users should not override __init__.
return structref.StructRefProxy.__new__(cls, name, vector)
# By default, the proxy type does not reflect the attributes or
# methods to the Python side. It is up to users to define
# these. (This may be automated in the future.)
@property
def name(self):
# To access a field, we can define a function that simply
# return the field in jit-code.
# The definition of MyStruct_get_name is shown later.
return MyStruct_get_name(self)
@property
def vector(self):
# The definition of MyStruct_get_vector is shown later.
return MyStruct_get_vector(self)
@njit
def MyStruct_get_name(self):
# In jit-code, the StructRef's attribute is exposed via
# structref.register
return self.name
@njit
def MyStruct_get_vector(self):
return self.vector
# This associates the proxy with MyStructType for the given set of
# fields. Notice how we are not contraining the type of each field.
# Field types remain generic.
structref.define_proxy(MyStruct, MyStructType, ["name", "vector"])
print(MyStruct("a",np.array([])))
The code will run just fine once. Try to execute it a second time and Blender will throw a huge list of errors. Does anyone know why this particular combination of tools works so poorly?
EDIT: Here are the errors I am encountering:
Traceback (most recent call last):
File "/home/my_username/my_folder/work/Blender/Trees/test.blend/Text", line 84, in <module>
File "/home/my_username/my_folder/work/Blender/Trees/test.blend/Text", line 48, in __new__
File "/home/my_username/.local/lib/python3.7/site-packages/numba/experimental/structref.py", line 376, in __new__
return ctor(*args)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 434, in _compile_for_args
raise e
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 367, in _compile_for_args
return self.compile(tuple(argtypes))
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 819, in compile
cres = self._compiler.compile(args, return_type)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 78, in compile
status, retval = self._compile_cached(args, return_type)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 92, in _compile_cached
retval = self._compile_core(args, return_type)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/dispatcher.py", line 110, in _compile_core
pipeline_class=self.pipeline_class)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler.py", line 627, in compile_extra
return pipeline.compile_extra(func)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler.py", line 363, in compile_extra
return self._compile_bytecode()
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler.py", line 425, in _compile_bytecode
return self._compile_core()
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler.py", line 405, in _compile_core
raise e
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler.py", line 396, in _compile_core
pm.run(self.state)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_machinery.py", line 341, in run
raise patched_exception
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_machinery.py", line 332, in run
self._runPass(idx, pass_inst, state)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_lock.py", line 32, in _acquire_compile_lock
return func(*args, **kwargs)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_machinery.py", line 291, in _runPass
mutated |= check(pss.run_pass, internal_state)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/compiler_machinery.py", line 264, in check
mangled = func(compiler_state)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/typed_passes.py", line 442, in run_pass
NativeLowering().run_pass(state)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/typed_passes.py", line 372, in run_pass
lower.create_cpython_wrapper(flags.release_gil)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/lowering.py", line 244, in create_cpython_wrapper
release_gil=release_gil)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/cpu.py", line 161, in create_cpython_wrapper
builder.build()
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/callwrapper.py", line 122, in build
self.build_wrapper(api, builder, closure, args, kws)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/callwrapper.py", line 176, in build_wrapper
obj = api.from_native_return(retty, retval, env_manager)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/pythonapi.py", line 1388, in from_native_return
out = self.from_native_value(typ, val, env_manager)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/pythonapi.py", line 1402, in from_native_value
return impl(typ, val, c)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/experimental/structref.py", line 167, in box_struct_ref
ctor_pyfunc = c.pyapi.unserialize(c.pyapi.serialize_object(obj_ctor))
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/pythonapi.py", line 1363, in serialize_object
struct = self.serialize_uncached(obj)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/pythonapi.py", line 1334, in serialize_uncached
data = serialize.dumps(obj)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/serialize.py", line 168, in dumps
p.dump(obj)
File "/home/my_username/my_folder/blender-2.83.0-linux64/2.83/python/lib/python3.7/pickle.py", line 437, in dump
self.save(obj)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/serialize.py", line 305, in save
return super().save(obj)
File "/home/my_username/my_folder/blender-2.83.0-linux64/2.83/python/lib/python3.7/pickle.py", line 549, in save
self.save_reduce(obj=obj, *rv)
File "/home/my_username/my_folder/blender-2.83.0-linux64/2.83/python/lib/python3.7/pickle.py", line 638, in save_reduce
save(args)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/serialize.py", line 305, in save
return super().save(obj)
File "/home/my_username/my_folder/blender-2.83.0-linux64/2.83/python/lib/python3.7/pickle.py", line 504, in save
f(self, obj) # Call unbound method with explicit self
File "/home/my_username/my_folder/blender-2.83.0-linux64/2.83/python/lib/python3.7/pickle.py", line 774, in save_tuple
save(element)
File "/home/my_username/.local/lib/python3.7/site-packages/numba/core/serialize.py", line 314, in save
raise _TracedPicklingError(m)
numba.core.serialize._TracedPicklingError: Failed in nopython mode pipeline (step: nopython mode backend)
Failed to pickle because of
PicklingError: Can't pickle <class '__main__.MyStruct'>: it's not the same object as __main__.MyStruct
tracing...
[0]: <class 'method'>: 140247150191712
[1]: <class 'tuple'>: 140245505894272
[2]: <class 'type'>: 140247018660896
Error: Python script failed, check the message in the system console
EDIT2: Following Londo's suggestion I changed the last function define_proxy
to define_constructor
which left me with the following error message:
TypeError: cannot convert native numba.TreeNodeType(('location', int64), ('parent_obj_id', array(float64, 1d, C)), ('weight', int64), ('weight_factor', float64), ('child_indices', int64)) to Python object