Skip to content

Instantly share code, notes, and snippets.

@masqu3rad3
Created January 21, 2026 14:05
Show Gist options
  • Select an option

  • Save masqu3rad3/21a48d2ec3366e0dd62c587b3d07eadd to your computer and use it in GitHub Desktop.

Select an option

Save masqu3rad3/21a48d2ec3366e0dd62c587b3d07eadd to your computer and use it in GitHub Desktop.
Benchmark for comparing compund versus split connections
import maya.cmds as cmds
import time
import random
import gc
# ---------------------------------------------------------------------
# CONFIGURATION
# ---------------------------------------------------------------------
CHAIN_LENGTH = 4000 # Depth of the chain.
# NOTE: "Atomic" test will create CHAIN_LENGTH * 3 nodes.
START_FRAME = 0
END_FRAME = 120
TOTAL_FRAMES = END_FRAME - START_FRAME
# ---------------------------------------------------------------------
# HELPERS
# ---------------------------------------------------------------------
def get_mult_linear_type():
try:
version_str = cmds.about(version=True)
year = int(version_str.split('.')[0].split()[0])
return "multDL" if year >= 2026 else "multDoubleLinear"
except Exception:
return "multDoubleLinear"
MULT_LINEAR_NODE = get_mult_linear_type()
def get_node_complexity(node_name):
return len(cmds.listAttr(node_name) or [])
# ---------------------------------------------------------------------
# CHAIN FUNCTIONS
# ---------------------------------------------------------------------
# These functions accept a LIST of plugs (previous outputs)
# and return a LIST of plugs (current outputs) to maintain the chain.
def chain_compound_md(prev_plugs, index):
"""
Test 1: Vector -> Vector connection.
Connects [prev.translate] -> [curr.input1]
"""
node = cmds.createNode("multiplyDivide", name=f"link_{index}_MD")
# prev_plugs[0] is expected to be a compound vector plug (e.g. .output or .translate)
cmds.connectAttr(prev_plugs[0], f"{node}.input1")
# Set operation to Multiply (1) and second input
cmds.setAttr(f"{node}.operation", 1)
cmds.setAttr(f"{node}.input2", 1.001, 1.001, 1.001, type="double3")
return [f"{node}.output"]
def chain_split_md(prev_plugs, index):
"""
Test 2: Split plugs on single node.
Connects [prev.x, prev.y, prev.z] -> [curr.input1X, 1Y, 1Z]
"""
node = cmds.createNode("multiplyDivide", name=f"link_{index}_MD_Split")
cmds.setAttr(f"{node}.operation", 1)
axes = ['X', 'Y', 'Z']
# prev_plugs is expected to be list of 3 scalar plugs
for i, ax in enumerate(axes):
cmds.connectAttr(prev_plugs[i], f"{node}.input1{ax}")
cmds.setAttr(f"{node}.input2{ax}", 1.001)
return [f"{node}.output{ax}" for ax in axes]
def chain_atomic_mdl(prev_plugs, index):
"""
Test 3: 3 Separate Scalar Nodes.
Connects [prev.x, prev.y, prev.z] -> [NodeA.input1, NodeB.input1, NodeC.input1]
"""
axes = ['X', 'Y', 'Z']
out_plugs = []
for i, ax in enumerate(axes):
# Create a dedicated node for this axis
node = cmds.createNode(MULT_LINEAR_NODE, name=f"link_{index}_{ax}")
cmds.connectAttr(prev_plugs[i], f"{node}.input1")
cmds.setAttr(f"{node}.input2", 1.001)
out_plugs.append(f"{node}.output")
return out_plugs
# ---------------------------------------------------------------------
# ENGINE
# ---------------------------------------------------------------------
def run_benchmark():
tests = {
"1. Compound MD (Vector)": {
"func": chain_compound_md,
"source_plugs": ["translate"], # Will be appended to driver name
"target_plugs": ["translate"],
"nodes_per_link": 1
},
"2. Split MD (3 Plugs)": {
"func": chain_split_md,
"source_plugs": ["tx", "ty", "tz"],
"target_plugs": ["tx", "ty", "tz"],
"nodes_per_link": 1
},
"3. Atomic DL (3 Nodes)": {
"func": chain_atomic_mdl,
"source_plugs": ["tx", "ty", "tz"],
"target_plugs": ["tx", "ty", "tz"],
"nodes_per_link": 3
}
}
print(f"\n{'='*105}")
print(f"VECTOR vs SCALAR BENCHMARK: Maya {cmds.about(version=True)}")
print(f"Chain Depth: {CHAIN_LENGTH} steps")
print(f"{'='*105}")
results = []
for label, data in tests.items():
chain_func = data['func']
# 1. CLEANUP
cmds.file(new=True, force=True)
gc.collect()
# Create Driver/Driven
driver = cmds.polyCube(name="source_driver")[0]
driven = cmds.polyCube(name="final_target")[0]
# Animate Driver
cmds.setKeyframe(f"{driver}.translate", v=0, t=0)
cmds.setKeyframe(f"{driver}.translate", v=100, t=120)
# 2. BUILD CHAIN
start_build = time.time()
# Initialize Current Plugs based on test config
current_plugs = [f"{driver}.{p}" for p in data['source_plugs']]
total_nodes = 0
for i in range(CHAIN_LENGTH):
# Pass the list of plugs to the function
current_plugs = chain_func(current_plugs, i)
total_nodes += data['nodes_per_link']
# Connect final link to the driven object
target_plugs = [f"{driven}.{p}" for p in data['target_plugs']]
for src, dst in zip(current_plugs, target_plugs):
cmds.connectAttr(src, dst)
end_build = time.time()
build_time = end_build - start_build
# 3. EVALUATION
# Force initial pull
cmds.getAttr(f"{driven}.translate")
start_eval = time.time()
for f in range(START_FRAME, END_FRAME):
cmds.currentTime(f)
# Pull data
cmds.getAttr(f"{driven}.translate")
end_eval = time.time()
eval_time = end_eval - start_eval
fps = TOTAL_FRAMES / eval_time if eval_time > 0 else 0
results.append({
"Label": label,
"Nodes Created": total_nodes,
"Build Time": build_time,
"FPS": fps
})
print(f"Finished {label:<25} | FPS: {fps:.2f}")
# 4. REPORT
print(f"\n{'='*105}")
print(f"{'METHOD':<25} | {'TOTAL NODES':<12} | {'BUILD (s)':<10} | {'FPS':<10} | {'EFFICIENCY'}")
print(f"{'-'*105}")
# Sort by FPS
results.sort(key=lambda x: x['FPS'], reverse=True)
for r in results:
# Efficiency metric: FPS per 1000 nodes processed
# This helps visualize if the 'atomic' method is actually faster per-node, even if total time is similar.
eff = r['FPS']
print(f"{r['Label']:<25} | {r['Nodes Created']:<12} | {r['Build Time']:.4f} {'s':<5} | {r['FPS']:.2f} {'':<5} |")
print(f"{'='*105}\n")
print("NOTE: 'Compound' moves data as one vector block. 'Atomic' splits it into 3 independent scalar streams.")
run_benchmark()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment