Created
January 21, 2026 14:05
-
-
Save masqu3rad3/21a48d2ec3366e0dd62c587b3d07eadd to your computer and use it in GitHub Desktop.
Benchmark for comparing compund versus split connections
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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