Skip to content

⚡️ Speed up method Key.equals by 6%#110

Open
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-Key.equals-mmckvkhu
Open

⚡️ Speed up method Key.equals by 6%#110
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-Key.equals-mmckvkhu

Conversation

@codeflash-ai
Copy link
Copy Markdown

@codeflash-ai codeflash-ai bot commented Mar 4, 2026

📄 6% (0.06x) speedup for Key.equals in client/src/com/aerospike/client/Key.java

⏱️ Runtime : 82.3 microseconds 77.4 microseconds (best of 169 runs)

📝 Explanation and details

This change reduces Key.equals runtime from 82.3 µs to 77.4 µs (≈6% speedup). It replaces Arrays.equals with an inlined fast-path that checks for identical digest references, handles nulls and differing lengths, and then compares bytes in a tight loop using local byte[] references. By avoiding the extra method call and enabling early short-circuiting and a simpler loop the JVM can eliminate extra checks and JIT the hot path more effectively, lowering per-call overhead. The trade-off is a small increase in method size and local complexity, but unit tests that exercise null, large and mutated digests confirm the expected behavior while delivering the measurable performance gain.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 27 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 75.0%
🌀 Click to see Generated Regression Tests
package com.aerospike.client;

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

import java.util.Arrays;
// Performance comparison:
// KeyTest.testEquals_ConsistentResults_OnRepeatedCalls#12: 0.001ms -> 0.001ms (-6.0% faster)
// KeyTest.testEquals_DifferentNamespace_False#8: 0.001ms -> 0.000ms (18.3% faster)
// KeyTest.testEquals_SameObject_True#1: 0.000ms -> 0.000ms (-17.1% faster)
// KeyTest.testEquals_NullNamespace_ThrowsNullPointerException#9: 0.042ms -> 0.040ms (4.8% faster)
// KeyTest.testEquals_DifferentClass_False#3: 0.000ms -> 0.000ms (43.4% faster)
// KeyTest.testEquals_SameNamespaceAndDigest_True#4: 0.000ms -> 0.000ms (-15.5% faster)
// KeyTest.testEquals_Null_False#2: 0.000ms -> 0.000ms (34.6% faster)
// KeyTest.testEquals_DigestMutation_AffectsEquality#14: 0.000ms -> 0.000ms (-14.1% faster)
// KeyTest.testEquals_DigestMutation_AffectsEquality#15: 0.000ms -> 0.000ms (-15.9% faster)
// KeyTest.testEquals_OneNullDigest_False#11: 0.000ms -> 0.000ms (2.5% faster)
// KeyTest.testEquals_SymmetricEquality_True#5: 0.000ms -> 0.000ms (1.1% faster)
// KeyTest.testEquals_SymmetricEquality_True#6: 0.000ms -> 0.000ms (-35.7% faster)
// KeyTest.testEquals_DifferentDigest_False#7: 0.000ms -> 0.000ms (-22.5% faster)
// KeyTest.testEquals_BothNullDigest_ButNamespaceEqual_True#10: 0.000ms -> 0.000ms (11.8% faster)
// KeyTest.testEquals_LargeDigest_EqualityWorks#13: 0.004ms -> 0.003ms (24.2% faster)
// KeyTest.testNullObject_equalsFalse#7: 0.000ms -> 0.000ms (26.6% faster)
// KeyTest.testDifferentClass_equalsFalse#6: 0.000ms -> 0.000ms (-7.1% faster)
// KeyTest.testNullNamespace_throwsNullPointerException#8: 0.029ms -> 0.027ms (7.0% faster)
// KeyTest.testDifferentNamespace_equalsFalse#5: 0.000ms -> 0.000ms (19.9% faster)
// KeyTest.testSameReference_equalsTrue#1: 0.000ms -> 0.000ms (20.6% faster)
// KeyTest.testSetNameIgnoredInEquality_equalsTrue#9: 0.000ms -> 0.000ms (29.7% faster)
// KeyTest.testLargeKey_equalityOnLargeInput#12: 0.000ms -> 0.000ms (-51.7% faster)
// KeyTest.testUserKeyDoesNotAffectEquality_equalsTrue#10: 0.000ms -> 0.000ms (-10.1% faster)
// KeyTest.testEqualNamespaceAndDigest_equalsTrue#2: 0.000ms -> 0.000ms (-15.8% faster)
// KeyTest.testEqualNamespaceAndDigest_equalsTrue#3: 0.000ms -> 0.000ms (-40.7% faster)
// KeyTest.testDifferentDigest_equalsFalse#4: 0.000ms -> 0.000ms (-3.9% faster)
// KeyTest.testByteSegmentConstructor_equalsTrue#11: 0.000ms -> 0.000ms (-8.4% faster)

/**
 * Unit tests for com.aerospike.client.Key.equals(Object)
 */
public class KeyTest {
	private Key commonKey;

	@Before
	public void setUp() throws Exception {
		// Typical key used across multiple tests
		commonKey = new Key("testNS", "testSet", "userKey");
	}

	@Test
	public void testEquals_SameObject_True() {
		// Same reference must be equal
		assertTrue(commonKey.equals(commonKey));
	}

	@Test
	public void testEquals_Null_False() {
		// Comparing to null must return false (no exception)
		assertFalse(commonKey.equals(null));
	}

	@Test
	public void testEquals_DifferentClass_False() {
		// Comparing to an object of different class must return false
		assertFalse(commonKey.equals("some string"));
	}

	@Test
	public void testEquals_SameNamespaceAndDigest_True() throws Exception {
		// Create a second Key with the exact same digest and namespace.
		// Use the digest from commonKey to ensure equality based on digest+namespace.
		Key sameDigestKey = new Key(commonKey.namespace, commonKey.digest, "differentSet", null);
		assertTrue(commonKey.equals(sameDigestKey));
	}

	@Test
	public void testEquals_SymmetricEquality_True() throws Exception {
		// Verify symmetry: a.equals(b) == b.equals(a)
		Key a = new Key("symNS", "s", "k");
		Key b = new Key("symNS", a.digest, "otherSet", null);
		assertTrue(a.equals(b));
		assertTrue(b.equals(a));
	}

	@Test
	public void testEquals_DifferentDigest_False() throws Exception {
		// Two keys in same namespace but different user key -> different digest -> not equal
		Key k1 = new Key("nsA", "setA", "keyOne");
		Key k2 = new Key("nsA", "setA", "keyTwo");
		assertFalse(k1.equals(k2));
	}

	@Test
	public void testEquals_DifferentNamespace_False() throws Exception {
		// Same digest but different namespace -> not equal
		Key k1 = new Key("ns1", "setX", "myKey");
		Key k2 = new Key("ns2", k1.digest, "setX", null);
		assertFalse(k1.equals(k2));
	}

	@Test(expected = NullPointerException.class)
	public void testEquals_NullNamespace_ThrowsNullPointerException() {
		// If namespace is null, equals will attempt to call namespace.equals(...) and throw NPE.
		byte[] digest = new byte[] {1, 2, 3};
		Key kWithNullNamespace = new Key(null, digest, "s", null);
		Key otherWithNullNamespace = new Key(null, digest, "t", null);
		// This should throw NullPointerException because namespace is null.
		kWithNullNamespace.equals(otherWithNullNamespace);
	}

	@Test
	public void testEquals_BothNullDigest_ButNamespaceEqual_True() {
		// If both digests are null, Arrays.equals(null, null) is true, so equality falls back to namespace comparison.
		Key a = new Key("nsNullDigest", null, "s", null);
		Key b = new Key("nsNullDigest", null, "other", null);
		assertTrue(a.equals(b));
	}

	@Test
	public void testEquals_OneNullDigest_False() {
		// One digest null and the other non-null -> Arrays.equals returns false
		Key a = new Key("nsNullOne", null, "s", null);
		Key b = new Key("nsNullOne", new byte[] {10, 11}, "s", null);
		assertFalse(a.equals(b));
	}

	@Test
	public void testEquals_ConsistentResults_OnRepeatedCalls() throws Exception {
		// Repeated calls must return the same result consistently
		Key a = new Key("consNS", "s", "k1");
		Key b = new Key("consNS", a.digest, "other", null);
		for (int i = 0; i < 5; i++) {
			assertTrue(a.equals(b));
		}
	}

	@Test
	public void testEquals_LargeDigest_EqualityWorks() {
		// Large digest arrays should be compared correctly (performance not measured here).
		byte[] large = new byte[100_000];
		for (int i = 0; i < large.length; i++) {
			large[i] = (byte) (i % 128);
		}
		byte[] largeCopy = Arrays.copyOf(large, large.length);

		Key largeA = new Key("bigNS", large, "s", null);
		Key largeB = new Key("bigNS", largeCopy, "s2", null);

		assertTrue(largeA.equals(largeB));
	}

	@Test
	public void testEquals_DigestMutation_AffectsEquality() throws Exception {
		// Ensure equality is based on the current content of digest arrays.
		byte[] digest = new byte[] {5, 6, 7};
		Key a = new Key("mutNS", digest, "s", null);
		byte[] copy = Arrays.copyOf(digest, digest.length);
		Key b = new Key("mutNS", copy, "s", null);
		assertTrue(a.equals(b));

		// Mutate original digest array used to construct 'a'.
		digest[0] = 99;
		// Since Key stores the reference, the mutation should affect equality.
		// Depending on implementation, this test documents expected behavior.
		boolean stillEqual = a.equals(b);
		// Either case is acceptable behavior in general; assert that mutation can lead to inequality.
		// We check inequality here to highlight that mutating a digest affects equality.
		assertFalse("Mutating the digest array used to construct the Key should affect equality", stillEqual);
	}
}

To edit these changes git checkout codeflash/optimize-Key.equals-mmckvkhu and push.

Codeflash Static Badge

This change reduces Key.equals runtime from 82.3 µs to 77.4 µs (≈6% speedup). It replaces Arrays.equals with an inlined fast-path that checks for identical digest references, handles nulls and differing lengths, and then compares bytes in a tight loop using local byte[] references. By avoiding the extra method call and enabling early short-circuiting and a simpler loop the JVM can eliminate extra checks and JIT the hot path more effectively, lowering per-call overhead. The trade-off is a small increase in method size and local complexity, but unit tests that exercise null, large and mutated digests confirm the expected behavior while delivering the measurable performance gain.
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 March 4, 2026 21:58
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants