Skip to content

Conversation

@benglin
Copy link
Contributor

@benglin benglin commented Nov 26, 2025

Purpose

Fixes severe performance degradation when marshaling large numbers of geometry objects with identical coordinate values (e.g., 100,000 Point objects at the same location). The issue was caused by hash collisions in CLRObjectMap dictionary lookups, resulting in O(n) performance instead of O(1).


Performance Impact

Scenario Before Fix After Fix Improvement
100K Points (identical coordinates) ~67,547 ms ~178 ms 379x faster
100K Points (different coordinates) ~159 ms ~159 ms No change (baseline)
Hash Collision Rate 100% ~0.08% Near zero collisions

Root Cause: ReferenceEqualityComparer.GetHashCode() was using obj.GetHashCode(), which for geometry types like Point computes hash codes based on coordinate values. When multiple objects have identical coordinates, they produce identical hash codes, causing 100% collision rate and forcing the dictionary to traverse long collision chains.

For example, Point.ComputeHashCode() in LibG computes hash codes based on coordinate values:

protected override int ComputeHashCode()
{
    int hash = 17;
    hash = hash * 23 + X.GetHashCode();
    hash = hash * 23 + Y.GetHashCode();
    hash = hash * 23 + Z.GetHashCode();
    return hash;
}

When 100,000 Point objects all have identical coordinates (e.g., (0, 0, 0)), they all produce the same hash code, causing every dictionary lookup to traverse the entire collision chain, degrading performance from O(1) to O(n).

Solution: Changed ReferenceEqualityComparer.GetHashCode() to use RuntimeHelpers.GetHashCode(obj), which returns the object's identity hash code. This ensures well-distributed hash codes that match the reference equality semantics already used by ReferenceEquals().

Impact: The fix applies to all geometry types (Point, Line, Surface, Solid, etc.) and any other objects that may have value-based hash codes that collide.

Safety Note: This fix is safe and does not affect object equality semantics in Dynamo. The CLRObjectMap dictionary already uses reference equality (object.ReferenceEquals) for comparisons, meaning two Point objects with identical coordinates but different instances are treated as different objects. The fix aligns the hash code computation with this existing reference equality behavior, ensuring consistent semantics while eliminating performance-degrading collisions.

Testing

  • Verified performance improvement: marshaling 100,000 Point objects with identical coordinates improved from ~67,547ms to ~178ms (379x faster, now comparable to baseline performance)
  • Hash collision rate reduced from 100% to ~0.08% (near zero)
  • Fix applies to all object types, not just Point
  • Added new test cases to cover the code changes
reference-equality-comparer-tests

Declarations

Check these if you believe they are true

Release Notes

Fixed severe performance degradation when marshaling large numbers of geometry objects with identical coordinate values. The fix improves dictionary lookup performance by using identity-based hash codes that match reference equality semantics, eliminating hash collisions that caused O(n) lookup performance.

Reviewers

(Reviewer to be assigned)

Additional Notes

Technical Details:

  • Modified ReferenceEqualityComparer.GetHashCode() in CLRObjectMarshaler.cs to use RuntimeHelpers.GetHashCode(obj) instead of obj.GetHashCode()
  • This change aligns hash code computation with the existing reference equality semantics (object.ReferenceEquals)
  • No behavior change: two objects with identical values but different instances were already treated as different (reference equality), and this remains true
  • The fix ensures each object instance gets a unique, well-distributed identity hash code, eliminating collisions

FYIs

(Optional) Names of anyone else you wish to be notified of

Copilot AI review requested due to automatic review settings November 26, 2025 08:31
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-6732

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical performance issue in CLRObjectMarshaler where dictionary lookups degraded from O(1) to O(n) when marshaling large numbers of geometry objects with identical coordinate values. The root cause was hash collisions in ReferenceEqualityComparer, which used value-based hash codes instead of identity-based hash codes.

Key Changes:

  • Modified ReferenceEqualityComparer.GetHashCode() to use RuntimeHelpers.GetHashCode(obj) instead of obj.GetHashCode()
  • Updated XML documentation to clarify the comparer's purpose and hash code behavior
  • Added using System.Runtime.CompilerServices directive

Performance Impact:

  • 425x speedup for marshaling 100K Point objects with identical coordinates (67,547ms → 159ms)
  • Hash collision rate reduced from 100% to ~0.01%

Copy link
Contributor

@aparajit-pratap aparajit-pratap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benglin I'm curious if the GetHashCode implementation should be changed in classes like Point.cs as well.

Copy link
Contributor

@aparajit-pratap aparajit-pratap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benglin can you add a unit test for this? Thank you for the fix!

@benglin
Copy link
Contributor Author

benglin commented Nov 26, 2025

curious if the GetHashCode implementation should be changed?

Ah no @aparajit-pratap, Point.GetHashCode() should not be changed. It is correctly implemented for value-based equality (same coordinates ==> same hash). The issue was a semantic mismatch: CLRObjectMap uses reference equality but was calling obj.GetHashCode() (value-based). The fix belongs in ReferenceEqualityComparer to align hash codes with reference equality. Changing Point.GetHashCode() would break value-based equality used elsewhere in the codebase.

can you add a unit test for this?

Sure, I will be adding tests within /test/Engine/ProtoTest/FFITests directory tomorrow. Thanks for reviewing! 🙏

…int values'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants