Performance Comparison
The table below compares the performance of maya.cmds
, pymel
, AL.omx
, maya.api.OpenMaya (om2)
,
when they are dealing with 100+, 1000+, 10000+ nodes. It also shows the performance difference between
AL.omx
in and out of immediate mode.
100+ nodes (seconds) |
cmds |
pymel |
omx(immediate) |
omx |
om2 |
---|---|---|---|---|---|
creation |
0.1837 |
0.2977 |
0.0825 |
0.061 |
0.0746 |
edit |
0.0677 |
0.3975 |
0.1692 |
0.0536 |
0.0270 |
rename |
0.0115 |
0.0310 |
0.016 |
0.0055 |
0.0053 |
query |
0.0082 |
0.1422 |
0.0146 |
0.0101 |
0.0057 |
delete |
0.1156 |
0.1205 |
0.0133 |
0.0069 |
0.0059 |
total |
0.3868 |
0.9890 |
0.2956 |
0.1371 |
0.1185 |
1000+ nodes (seconds) |
cmds |
pymel |
omx(immediate) |
omx |
om2 |
---|---|---|---|---|---|
creation |
1.6022 |
2.8140 |
0.9777 |
0.6206 |
0.2677 |
edit |
0.7045 |
4.1965 |
1.5021 |
0.4553 |
0.1740 |
rename |
0.1246 |
0.3209 |
0.1138 |
0.0606 |
0.0434 |
query |
0.0819 |
1.4160 |
0.1128 |
0.1231 |
0.0432 |
delete |
0.8158 |
0.6460 |
0.1044 |
0.0777 |
0.0459 |
total |
3.3291 |
9.3934 |
2.8108 |
1.3372 |
0.5742 |
10000+ nodes(seconds) |
cmds |
pymel |
omx(immediate) |
omx |
om2 |
---|---|---|---|---|---|
creation |
15.7968 |
27.2677 |
9.2528 |
7.2431 |
2.4226 |
edit |
7.0859 |
41.1648 |
13.5523 |
4.8801 |
1.7376 |
rename |
1.4020 |
3.4139 |
1.1828 |
0.6959 |
0.5311 |
query |
0.8260 |
14.0232 |
1.2039 |
1.275 |
0.4394 |
delete |
6.2583 |
5.6137 |
1.6232 |
0.9046 |
0.5580 |
total |
31.369 |
91.4833 |
26.815 |
14.9987 |
5.6887 |
The graph below shows the time taken to run each code snippet as the node count scales.
Performance Measuring Code
Common utils used by each script, it needs to be executed first:
# execute these codes once before actual performance testing codes.
import random
import time
def getRandomPosition(furthestDist):
return [(random.random() - 0.5) * furthestDist for _ in range(3)]
def getRandomScale():
return [random.random() * 2.0 for _ in range(3)]
def getRandomColor():
return random.randint(0, 31)
def getRandomIndex(maxValue):
return random.randint(0, maxValue)
def getMaxValue(locCount):
return int(locCount / 10)
class PerfMeasurement:
def __init__(self, label):
self._label = label
self._gap = 0.0
def __enter__(self,):
self._start = time.time()
self._gap = 0.0
def __exit__(self, *_, **__):
self._gap = time.time() - self._start
print(f"{self._label} took {round(self._gap, 4)} seconds.")
def timeConsumed(self):
return self._gap
class TotalPerfMeasurement:
def __init__(self, label):
self._measurers = []
self._label = label
def add(self, label):
measurement = PerfMeasurement(label)
self._measurers.append(measurement)
return measurement
def __enter__(self,):
print("-" * 20)
return self
def __exit__(self, *_, **__):
total = 0.0
for m in self._measurers:
total = total + m.timeConsumed()
print(f"{self._label} took {round(total, 4)} seconds.")
print("-" * 20)
NUM_NODES_LIST = (100, 1000, 10000)
REFINED_NUM_NODES_LIST = (10, 50, 100, 500, 1000, 5000, 10000, 50000)
maya.cmds:
from maya import cmds
# execute the code from common in maya script editor first.
def createInCmds(locCount):
maxValue = getMaxValue(locCount)
controller = cmds.joint()
cmds.addAttr(controller, ln="flash", at="long", min=0, max=maxValue)
stars = [None] * locCount
toDelete = [None] * locCount
parent = cmds.createNode("transform", n="stars")
for i in range(locCount):
condition = cmds.createNode("condition")
(loc,) = cmds.spaceLocator()
cmds.parent(loc, parent)
cmds.addAttr(loc, ln="flashIndex", at="long", min=0, max=maxValue)
(testDel,) = cmds.spaceLocator()
cmds.parent(testDel, loc)
cmds.objExists(testDel)
stars[i] = (loc, condition)
toDelete[i] = testDel
return controller, stars, toDelete
def editInCmds(controller, stars):
maxValue = getMaxValue(len(stars))
cmds.setAttr(f"{controller}.radius", 10)
cmds.setAttr(f"{controller}.flash", keyable=True)
cmds.setKeyframe(f"{controller}.flash", time=(1,), value=0)
cmds.setKeyframe(f"{controller}.flash", time=(120,), value=maxValue)
for loc, condition in stars:
cmds.setAttr(f"{condition}.colorIfTrue", 1.0, 1.0, 1.0)
cmds.setAttr(f"{condition}.colorIfFalse", 0.0, 0.0, 0.0)
cmds.setAttr(f"{loc}.overrideEnabled", True)
cmds.setAttr(f"{loc}.overrideColor", getRandomColor())
pos = getRandomPosition(maxValue)
cmds.move(pos[0], pos[1], pos[2], loc)
cmds.setAttr(f"{loc}.s", *getRandomScale())
cmds.setAttr(f"{loc}.displayHandle", lock=True)
cmds.setAttr(f"{loc}.overrideDisplayType", lock=True)
cmds.setAttr(f"{loc}.overrideDisplayType", lock=False)
cmds.setAttr(f"{loc}.flashIndex", getRandomIndex(maxValue))
cmds.connectAttr(f"{controller}.r", f"{loc}.r")
cmds.connectAttr(f"{controller}.overrideShading", f"{loc}.overrideShading")
cmds.disconnectAttr(f"{controller}.overrideShading", f"{loc}.overrideShading")
cmds.connectAttr(f"{controller}.flash", f"{condition}.firstTerm")
cmds.connectAttr(f"{loc}.flashIndex", f"{condition}.secondTerm")
cmds.connectAttr(f"{condition}.outColorR", f"{loc}.visibility")
def renameInCmds(nodesToRename):
for node in nodesToRename:
cmds.rename(node, f"{node}New")
cmds.rename(f"{node}New", node)
def queryInCmds(controller, stars):
cmds.listConnections(f"{controller}.flash")
for loc, _ in stars:
cmds.objExists(loc)
cmds.getAttr(f"{loc}.t")
cmds.getAttr(f"{loc}.wm[0]")
cmds.getAttr(f"{loc}.overrideDisplayType", lock=True)
def deleteInCmds(nodesToDelete):
for toDel in nodesToDelete:
cmds.delete(toDel)
def categorizedPerformanceTestInCmds():
for num in NUM_NODES_LIST:
cmds.file(new=True, force=True)
with TotalPerfMeasurement(f"Deal with {num} nodes in maya.cmds") as measure:
with measure.add(f"Create {num}+ nodes in maya.cmds"):
controller, stars, nodes = createInCmds(num)
with measure.add(f"Edit {num}+ nodes in maya.cmds"):
editInCmds(controller, stars)
with measure.add(f"Rename {num} nodes in maya.cmds"):
renameInCmds(nodes)
with measure.add(f"Query {num}+ nodes in maya.cmds"):
queryInCmds(controller, stars)
with measure.add(f"Remove {num} nodes in maya.cmds"):
deleteInCmds(nodes)
def totalPerformanceTestInCmds():
for num in REFINED_NUM_NODES_LIST:
cmds.file(new=True, force=True)
with PerfMeasurement(f"Deal with {num} nodes in maya.cmds"):
controller, stars, nodes = createInCmds(num)
editInCmds(controller, stars)
renameInCmds(nodes)
queryInCmds(controller, stars)
deleteInCmds(nodes)
if __name__ == "__main__":
categorizedPerformanceTestInCmds()
# totalPerformanceTestInCmds()
PyMel:
import pymel.core as pmc
# execute the code from common in maya script editor first.
def createInPyMel(locCount):
maxValue = getMaxValue(locCount)
controller = pmc.joint()
controller.addAttr("flash", attributeType="long", min=0, max=maxValue)
parent = pmc.createNode("transform", n="stars")
stars = [None] * locCount
toDelete = [None] * locCount
for i in range(locCount):
condition = pmc.createNode("condition")
loc = pmc.spaceLocator()
pmc.parent(loc, parent)
loc.addAttr("flashIndex", at="long", min=0, max=maxValue)
testDel = pmc.spaceLocator()
pmc.parent(testDel, loc)
pmc.objExists(testDel)
stars[i] = (loc, condition)
toDelete[i] = testDel
return controller, stars, toDelete
def editInPyMel(controller, stars):
maxValue = getMaxValue(len(stars))
controller.radius.set(10)
controller.flash.setKeyable(True)
pmc.setKeyframe(controller.flash, time=(1,), value=0)
pmc.setKeyframe(controller.flash, time=(120,), value=maxValue)
for loc, condition in stars:
condition.colorIfTrue.set([1.0, 1.0, 1.0])
condition.colorIfFalse.set([0.0, 0.0, 0.0])
loc.overrideEnabled.set(True)
loc.overrideColor.set(getRandomColor())
loc.t.set(getRandomPosition(maxValue))
loc.s.set(getRandomScale())
loc.displayHandle.lock()
loc.overrideDisplayType.lock()
loc.overrideDisplayType.unlock()
loc.flashIndex.set(getRandomIndex(maxValue))
controller.r.connect(loc.r)
controller.overrideShading.connect(loc.overrideShading)
controller.overrideShading.disconnect(loc.overrideShading)
controller.flash.connect(condition.firstTerm)
loc.flashIndex.connect(condition.secondTerm)
condition.outColorR.connect(loc.visibility)
def renameInPyMel(nodesToRename):
for node in nodesToRename:
node.rename(f"{node}New")
node.rename(str(node))
def queryInPyMel(controller, stars):
controller.flash.outputs()
for loc, _ in stars:
pmc.objExists(loc)
loc.t.get()
loc.wm[0].get()
loc.overrideDisplayType.isLocked()
def deleteInPyMel(nodesToDelete):
for toDel in nodesToDelete:
pmc.delete(toDel)
def categorizedPerformanceTestInPyMel():
for num in NUM_NODES_LIST:
pmc.system.newFile(force=True)
with TotalPerfMeasurement(f"Deal with {num} nodes in PyMel") as measure:
with measure.add(f"Create {num}+ nodes in PyMel"):
controller, stars, nodes = createInPyMel(num)
with measure.add(f"Edit {num}+ nodes in PyMel"):
editInPyMel(controller, stars)
with measure.add(f"Rename {num} nodes in PyMel"):
renameInPyMel(nodes)
with measure.add(f"Query {num}+ nodes in PyMel"):
queryInPyMel(controller, stars)
with measure.add(f"Remove {num} nodes in PyMel"):
deleteInPyMel(nodes)
def totalPerformanceTestInPyMel():
for num in REFINED_NUM_NODES_LIST:
pmc.system.newFile(force=True)
with PerfMeasurement(f"Deal with {num} nodes in PyMel"):
controller, stars, nodes = createInPyMel(num)
editInPyMel(controller, stars)
renameInPyMel(nodes)
queryInPyMel(controller, stars)
deleteInPyMel(nodes)
if __name__ == "__main__":
categorizedPerformanceTestInPyMel()
# totalPerformanceTestInPyMel()
AL.OMX:
from AL import omx
from maya import cmds
from maya.api import OpenMaya as om2
# execute the code from common in maya script editor first.
def createInOMX(locCount, immediate):
maxValue = getMaxValue(locCount)
modifier = omx.newModifier()
if immediate:
# usually you use omx.currentModifier() but it is not guaranteed
# to be a immediate one, like in our case here.
modifier._immediate = immediate
controller = omx.createDagNode("joint", nodeName="controller")
fnAttr = om2.MFnNumericAttribute()
attrObj = fnAttr.create("flash", "flash", om2.MFnNumericData.kInt)
fnAttr.setMin(0)
fnAttr.setMax(maxValue)
modifier.addAttribute(controller.object(), attrObj)
parent = omx.createDagNode("transform", nodeName="stars")
stars = [None] * locCount
toDelete = [None] * locCount
for i in range(locCount):
condition = omx.createDGNode("condition")
loc = omx.createDagNode("transform", parent=parent)
omx.createDagNode("locator", parent=loc)
attrObj = fnAttr.create("flashIndex", "flashIndex", om2.MFnNumericData.kInt)
fnAttr.setMin(0)
fnAttr.setMax(maxValue)
modifier.addAttribute(loc.object(), attrObj)
testDel = omx.createDagNode("transform", parent=parent)
omx.createDagNode("locator", parent=testDel)
testDel.isValid()
stars[i] = (loc, condition)
toDelete[i] = testDel
modifier.doIt()
return controller, stars, toDelete
def editInOMX(controller, stars):
maxValue = getMaxValue(len(stars))
modifier = omx.currentModifier()
controller.radius.setInt(10)
controller.flash.isKeyable = True
modifier.commandToExecute(f"setKeyframe -attribute flash -t 1 -v 0 {controller}")
modifier.commandToExecute(
f"setKeyframe -attribute flash -t 120 -v {maxValue} {controller}"
)
for loc, condition in stars:
condition.colorIfTrue.setCompoundDouble((1.0, 1.0, 1.0))
condition.colorIfFalse.setCompoundDouble((0.0, 0.0, 0.0))
loc.overrideEnabled.setBool(True)
loc.overrideColor.setInt(getRandomColor())
loc.t.setCompoundDouble(getRandomPosition(maxValue))
loc.s.setCompoundDouble(getRandomScale())
# here we don't care about plug state change undoability as the whole
# node creation is done in the same XModifier.
# otherwise we would use loc.displayHandle.setLocked(True)
loc.displayHandle.isLocked = True
loc.overrideDisplayType.isLocked = True
loc.overrideDisplayType.isLocked = False
loc.flashIndex.setInt(getRandomIndex(maxValue))
controller.r.connectTo(loc.r)
controller.overrideShading.connectTo(loc.overrideShading)
loc.overrideShading.disconnectFromSource()
controller.flash.connectTo(condition.firstTerm)
loc.flashIndex.connectTo(condition.secondTerm)
condition.outColorR.connectTo(loc.visibility)
modifier.doIt()
def renameInOMX(nodesToRename):
modifier = omx.currentModifier()
for node in nodesToRename:
transformName = str(node)
modifier.renameNode(node.object(), f"{transformName}New")
modifier.renameNode(node.object(), f"{transformName}")
modifier.doIt()
def queryInOMX(controller, stars):
controller.flash.destinations()
for loc, _ in stars:
loc.isValid()
loc.t.get()
loc.wm[0].get()
loc.overrideDisplayType.isLocked
def deleteInOMX(nodesToDelete):
modifier = omx.currentModifier()
for toDel in nodesToDelete:
modifier.deleteNode(toDel.object())
modifier.doIt()
def categorizedPerformanceTestInOMX():
for num in NUM_NODES_LIST:
for immediate in (True, False):
cmds.file(new=True, force=True)
with TotalPerfMeasurement(
f"Deal with {num} nodes in AL.omx, immediate={immediate}"
) as measure:
with measure.add(
f"Create {num}+ nodes in AL.omx, immediate={immediate}"
):
controller, stars, nodes = createInOMX(num, immediate=immediate)
with measure.add(f"Edit {num}+ nodes in AL.omx, immediate={immediate}"):
editInOMX(controller, stars)
with measure.add(
f"Rename {num} nodes in AL.omx, immediate={immediate}"
):
renameInOMX(nodes)
with measure.add(
f"Query {num}+ nodes in AL.omx, immediate={immediate}"
):
queryInOMX(controller, stars)
with measure.add(
f"Remove {num} nodes in AL.omx, immediate={immediate}"
):
deleteInOMX(nodes)
def totalPerformanceTestInOMX():
for immediate in (True, False):
for num in REFINED_NUM_NODES_LIST:
cmds.file(new=True, force=True)
with PerfMeasurement(
f"Deal with {num} nodes in AL.omx, immediate={immediate}"
):
controller, stars, nodes = createInOMX(num, immediate=immediate)
editInOMX(controller, stars)
renameInOMX(nodes)
queryInOMX(controller, stars)
deleteInOMX(nodes)
if __name__ == "__main__":
categorizedPerformanceTestInOMX()
# totalPerformanceTestInOMX()
maya.api.OpenMaya (OM2):
from maya.api import OpenMaya as om2
from maya import cmds
# execute the code from common in maya script editor first.
def createInOM2(modifier, locCount):
maxValue = getMaxValue(locCount)
controller = modifier.createNode("joint")
modifier.renameNode(controller, "controller")
fnAttr = om2.MFnNumericAttribute()
attrObj = fnAttr.create("flash", "flash", om2.MFnNumericData.kInt)
fnAttr.setMin(0)
fnAttr.setMax(maxValue)
modifier.addAttribute(controller, attrObj)
parent = modifier.createNode("transform")
modifier.renameNode(parent, "stars")
stars = [None] * locCount
toDelete = [None] * locCount
for i in range(locCount):
condition = om2.MDGModifier.createNode(modifier, "condition")
loc = modifier.createNode("transform", parent=parent)
modifier.createNode("locator", parent=loc)
attrObj = fnAttr.create("flashIndex", "flashIndex", om2.MFnNumericData.kInt)
fnAttr.setMin(0)
fnAttr.setMax(maxValue)
modifier.addAttribute(loc, attrObj)
testDel = modifier.createNode("transform", parent=parent)
modifier.createNode("locator", parent=testDel)
om2.MObjectHandle(testDel).isValid()
stars[i] = (loc, condition)
toDelete[i] = testDel
modifier.doIt()
return controller, stars, toDelete
def editInOM2(modifier, controller, stars):
maxValue = getMaxValue(len(stars))
controllerFn = om2.MFnDependencyNode(controller)
plug = controllerFn.findPlug("radius", True)
modifier.newPlugValueInt(plug, 10)
plug = controllerFn.findPlug("flash", True)
plug.isKeyable = True
modifier.commandToExecute(
f"setKeyframe -attribute flash -t 1 -v 0 {controllerFn.name()}"
)
modifier.commandToExecute(
f"setKeyframe -attribute flash -t 120 -v {maxValue} {controllerFn.name()}"
)
colorIfTrueNames = ("colorIfTrueR", "colorIfTrueG", "colorIfTrueB")
colorIfFalseNames = ("colorIfFalseR", "colorIfFalseG", "colorIfFalseB")
translation = ("tx", "ty", "tz")
scale = ("sx", "sy", "sz")
for loc, condition in stars:
locFn = om2.MFnDependencyNode(loc)
conditionFn = om2.MFnDependencyNode(condition)
for trueAttr in colorIfTrueNames:
plug = conditionFn.findPlug(trueAttr, True)
modifier.newPlugValueDouble(plug, 1.0)
for falseAttr in colorIfFalseNames:
plug = conditionFn.findPlug(falseAttr, True)
modifier.newPlugValueDouble(plug, 0.0)
plug = locFn.findPlug("overrideEnabled", True)
modifier.newPlugValueBool(plug, True)
plug = locFn.findPlug("overrideColor", True)
modifier.newPlugValueInt(plug, getRandomColor())
for name, value in zip(translation, getRandomPosition(maxValue)):
plug = locFn.findPlug(name, True)
modifier.newPlugValueDouble(plug, value)
for name, value in zip(scale, getRandomScale()):
plug = locFn.findPlug(name, True)
modifier.newPlugValueDouble(plug, value)
plug = locFn.findPlug("displayHandle", True)
plug.isLocked = True
plug = locFn.findPlug("overrideDisplayType", True)
plug.isLocked = True
plug.isLocked = False
plug = locFn.findPlug("flashIndex", True)
modifier.newPlugValueInt(plug, getRandomIndex(maxValue))
src = controllerFn.findPlug("r", True)
dst = locFn.findPlug("r", True)
modifier.connect(src, dst)
src = controllerFn.findPlug("overrideShading", True)
dst = locFn.findPlug("overrideShading", True)
modifier.connect(src, dst)
modifier.disconnect(src, dst)
src = controllerFn.findPlug("flash", True)
dst = conditionFn.findPlug("firstTerm", True)
modifier.connect(src, dst)
src = locFn.findPlug("flashIndex", True)
dst = conditionFn.findPlug("secondTerm", True)
modifier.connect(src, dst)
src = conditionFn.findPlug("outColorR", True)
dst = locFn.findPlug("visibility", True)
modifier.connect(src, dst)
modifier.doIt()
def renameInOM2(modifier, nodesToRename):
for node in nodesToRename:
transformName = str(node)
modifier.renameNode(node, f"{transformName}New")
modifier.renameNode(node, f"{transformName}")
modifier.doIt()
def queryInOM2(controller, stars):
translation = ("tx", "ty", "tz")
controllerFn = om2.MFnDependencyNode(controller)
controllerFn.findPlug("flash", True).destinations()
for loc, _ in stars:
om2.MObjectHandle(loc).isValid()
locFn = om2.MFnDependencyNode(loc)
[locFn.findPlug(n, True).asDouble() for n in translation]
wm0Plug = locFn.findPlug("wm", True).elementByLogicalIndex(0)
attrData = om2.MFnMatrixData(wm0Plug.asMObject()).matrix()
[attrData[i] for i in range(len(attrData))]
locFn.findPlug("overrideDisplayType", True).isLocked
def deleteInOM2(modifier, nodesToDelete):
for toDel in nodesToDelete:
modifier.deleteNode(toDel)
modifier.doIt()
def categorizedPerformanceTestInOM2():
for num in NUM_NODES_LIST:
cmds.file(new=True, force=True)
with TotalPerfMeasurement(f"Deal with {num} nodes in om2") as measure:
modifier = om2.MDagModifier()
with measure.add(f"Create {num}+ nodes in om2"):
controller, stars, nodes = createInOM2(modifier, num)
with measure.add(f"Edit {num}+ nodes in om2"):
editInOM2(modifier, controller, stars)
with measure.add(f"Rename {num} nodes in om2"):
renameInOM2(modifier, nodes)
with measure.add(f"Query {num}+ nodes in om2"):
queryInOM2(controller, stars)
with measure.add(f"Remove {num} nodes in om2"):
deleteInOM2(modifier, nodes)
def totalPerformanceTestInOM2():
for num in REFINED_NUM_NODES_LIST:
cmds.file(new=True, force=True)
with PerfMeasurement(f"Deal with {num} nodes in om2"):
modifier = om2.MDagModifier()
controller, stars, nodes = createInOM2(modifier, num)
editInOM2(modifier, controller, stars)
renameInOM2(modifier, nodes)
queryInOM2(controller, stars)
deleteInOM2(modifier, nodes)
if __name__ == "__main__":
categorizedPerformanceTestInOM2()
# totalPerformanceTestInOM2()