Skip to content

⚡️ Speed up method LuaMap.diff by 10%#89

Open
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-LuaMap.diff-mmbeu7sd
Open

⚡️ Speed up method LuaMap.diff by 10%#89
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-LuaMap.diff-mmbeu7sd

Conversation

@codeflash-ai
Copy link
Copy Markdown

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

📄 10% (0.10x) speedup for LuaMap.diff in client/src/com/aerospike/client/lua/LuaMap.java

⏱️ Runtime : 849 microseconds 773 microseconds (best of 159 runs)

📝 Explanation and details

The change yields a measurable runtime win: 849 µs → 773 µs (≈9% faster) by reworking diff to reduce hash work and avoid repeated field accesses. Concretely, the implementation caches map2.map locally, precomputes HashMap capacity to avoid rehashing, does a single putAll(copy) of this.map, and then makes one pass over the other map removing common keys or inserting unique ones. This reduces the number of containsKey/remove/put hash lookups and mitigates costly rehash/resizing operations, resulting in fewer allocations and better cache behavior on the hot path. The trade-off is a slightly larger one-time allocation for the pre-sized target and a short-lived copy of this.map, which increases transient memory usage but keeps overall time complexity lower and reduces runtime for typical inputs.

Correctness verification report:

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

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

import java.util.HashMap;
import java.util.Map;

import org.luaj.vm2.LuaInteger;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaValue;
// Performance comparison:
// LuaMapTest.testDiff_TypicalInputs_ReturnsSymmetricDifferenceSize#1: 0.005ms -> 0.005ms (14.7% faster)
// LuaMapTest.testDiff_NoSharedKeys_ReturnsUnionSize#7: 0.003ms -> 0.005ms (-36.9% faster)
// LuaMapTest.testDiff_BothEmpty_ReturnsEmpty#4: 0.002ms -> 0.002ms (6.6% faster)
// LuaMapTest.testDiff_EmptyAndNonEmpty_ReturnsNonEmptySize#2: 0.003ms -> 0.003ms (-15.5% faster)
// LuaMapTest.testDiff_EmptyAndNonEmpty_ReturnsNonEmptySize#3: 0.002ms -> 0.002ms (13.4% faster)
// LuaMapTest.testDiff_LargeScale_PerformanceAndCorrectness#9: 0.449ms -> 0.393ms (12.4% faster)
// LuaMapTest.testDiff_IdenticalKeysDifferentValues_ReturnsEmpty#5: 0.002ms -> 0.002ms (-7.1% faster)
// LuaMapTest.testDiff_NullArgument_ThrowsNullPointerException#8: 0.056ms -> 0.057ms (-0.7% faster)
// LuaMapTest.testDiff_NullArgument_ThrowsNullPointerException#5: 0.046ms -> 0.039ms (15.8% faster)
// LuaMapTest.testDiff_WithNilValue_TreatedAsValueButKeySensitive#6: 0.001ms -> 0.002ms (-79.2% faster)
// LuaMapTest.testDiff_IdenticalMaps_ResultsEmpty#3: 0.002ms -> 0.002ms (5.0% faster)
// LuaMapTest.testDiff_DisjointMaps_ReturnsCombinedEntries#1: 0.002ms -> 0.003ms (-16.3% faster)
// LuaMapTest.testDiff_EmptyMaps_EmptyResult#4: 0.001ms -> 0.001ms (15.9% faster)
// LuaMapTest.testDiff_HandlesNilValues_PreservesNilValueEntries#6: 0.002ms -> 0.002ms (0.1% faster)
// LuaMapTest.testDiff_LargeMaps_CorrectResultSize#8: 0.272ms -> 0.257ms (5.5% faster)
// LuaMapTest.testDiff_OverlappingMaps_ExcludesCommonKeys#2: 0.001ms -> 0.000ms (34.7% faster)

/**
 * Unit tests for com.aerospike.client.lua.LuaMap.diff
 *
 * Note: These tests assume the presence of a concrete LuaInstance class with a public no-arg
 * constructor in the same package (com.aerospike.client.lua). The tests exercise the diff
 * behavior (symmetric difference on keys) observed in the implementation.
 */
public class LuaMapTest {
    private LuaInstance instance;

    @Before
    public void setUp() {
        // Assume LuaInstance has a public no-arg constructor in the same package.
        instance = new LuaInstance();
    }

    // Helper to build a LuaMap from a Map of String->Integer pairs
    private LuaMap buildLuaMapFromStrings(Map<String,Integer> entries) {
        Map<LuaValue,LuaValue> inner = new HashMap<LuaValue,LuaValue>();
        if (entries != null) {
            for (Map.Entry<String,Integer> e : entries.entrySet()) {
                LuaValue k = LuaString.valueOf(e.getKey());
                LuaValue v = LuaInteger.valueOf(e.getValue());
                inner.put(k, v);
            }
        }
        return new LuaMap(instance, inner);
    }

    // Helper to build a LuaMap from a Map of LuaValue->LuaValue pairs
    private LuaMap buildLuaMapFromLuaValues(Map<LuaValue,LuaValue> entries) {
        Map<LuaValue,LuaValue> inner = new HashMap<LuaValue,LuaValue>();
        if (entries != null) {
            inner.putAll(entries);
        }
        return new LuaMap(instance, inner);
    }

    @Test
    public void testDiff_TypicalInputs_ReturnsSymmetricDifferenceSize() {
        Map<String,Integer> m1 = new HashMap<String,Integer>();
        m1.put("a", 1);
        m1.put("b", 2);

        Map<String,Integer> m2 = new HashMap<String,Integer>();
        m2.put("b", 2);
        m2.put("c", 3);

        LuaMap lua1 = buildLuaMapFromStrings(m1);
        LuaMap lua2 = buildLuaMapFromStrings(m2);

        LuaMap diff = lua1.diff(lua2);

        // Expect keys: "a" and "c" => size 2
        assertEquals(2, diff.size().toint());
    }

    @Test
    public void testDiff_EmptyAndNonEmpty_ReturnsNonEmptySize() {
        Map<String,Integer> empty = new HashMap<String,Integer>();
        Map<String,Integer> nonEmpty = new HashMap<String,Integer>();
        nonEmpty.put("x", 10);
        nonEmpty.put("y", 20);

        LuaMap luaEmpty = buildLuaMapFromStrings(empty);
        LuaMap luaNonEmpty = buildLuaMapFromStrings(nonEmpty);

        LuaMap diff1 = luaEmpty.diff(luaNonEmpty);
        LuaMap diff2 = luaNonEmpty.diff(luaEmpty);

        // Both results should have the two keys from nonEmpty
        assertEquals(2, diff1.size().toint());
        assertEquals(2, diff2.size().toint());
    }

    @Test
    public void testDiff_BothEmpty_ReturnsEmpty() {
        LuaMap a = buildLuaMapFromStrings(new HashMap<String,Integer>());
        LuaMap b = buildLuaMapFromStrings(new HashMap<String,Integer>());

        LuaMap result = a.diff(b);

        assertEquals(0, result.size().toint());
    }

    @Test
    public void testDiff_IdenticalKeysDifferentValues_ReturnsEmpty() {
        // Keys are identical in both maps; diff only checks keys, not values.
        Map<LuaValue,LuaValue> m1 = new HashMap<LuaValue,LuaValue>();
        Map<LuaValue,LuaValue> m2 = new HashMap<LuaValue,LuaValue>();

        LuaValue keyA = LuaString.valueOf("k");
        m1.put(keyA, LuaInteger.valueOf(1));
        m2.put(keyA, LuaInteger.valueOf(999)); // different value, same key

        LuaMap lua1 = buildLuaMapFromLuaValues(m1);
        LuaMap lua2 = buildLuaMapFromLuaValues(m2);

        LuaMap diff = lua1.diff(lua2);

        // Since both contain the same key set, result should be empty.
        assertEquals(0, diff.size().toint());
    }

    @Test
    public void testDiff_WithNilValue_TreatedAsValueButKeySensitive() {
        Map<LuaValue,LuaValue> m1 = new HashMap<LuaValue,LuaValue>();
        Map<LuaValue,LuaValue> m2 = new HashMap<LuaValue,LuaValue>();

        LuaValue keyA = LuaString.valueOf("a");
        LuaValue keyB = LuaString.valueOf("b");

        // keyA present in m1 with NIL value, not present in m2
        m1.put(keyA, LuaValue.NIL);

        // keyB present in m2 with NIL value, not present in m1
        m2.put(keyB, LuaValue.NIL);

        LuaMap lua1 = buildLuaMapFromLuaValues(m1);
        LuaMap lua2 = buildLuaMapFromLuaValues(m2);

        LuaMap diff = lua1.diff(lua2);

        // Expect keys a and b -> size 2
        assertEquals(2, diff.size().toint());
    }

    @Test
    public void testDiff_NoSharedKeys_ReturnsUnionSize() {
        Map<String,Integer> m1 = new HashMap<String,Integer>();
        Map<String,Integer> m2 = new HashMap<String,Integer>();

        m1.put("a1", 1);
        m1.put("a2", 2);

        m2.put("b1", 3);
        m2.put("b2", 4);
        m2.put("b3", 5);

        LuaMap lua1 = buildLuaMapFromStrings(m1);
        LuaMap lua2 = buildLuaMapFromStrings(m2);

        LuaMap diff = lua1.diff(lua2);

        // No overlapping keys => size should be 2 + 3 = 5
        assertEquals(5, diff.size().toint());
    }

    @Test(expected = NullPointerException.class)
    public void testDiff_NullArgument_ThrowsNullPointerException() {
        Map<String,Integer> m1 = new HashMap<String,Integer>();
        m1.put("only", 1);

        LuaMap lua1 = buildLuaMapFromStrings(m1);

        // Passing null should cause NullPointerException because implementation accesses map2.map
        lua1.diff(null);
    }

    @Test
    public void testDiff_LargeScale_PerformanceAndCorrectness() {
        // Create two large maps with partial overlap.
        final int SIZE = 2000;
        final int OVERLAP = 1000; // keys [0, OVERLAP-1] overlap

        Map<LuaValue,LuaValue> m1 = new HashMap<LuaValue,LuaValue>(SIZE);
        Map<LuaValue,LuaValue> m2 = new HashMap<LuaValue,LuaValue>(SIZE);

        // m1 keys: 0..1999
        for (int i = 0; i < SIZE; i++) {
            m1.put(LuaString.valueOf("k" + i), LuaInteger.valueOf(i));
        }
        // m2 keys: 0..OVERLAP-1 and 2000..(2000+SIZE-OVERLAP-1)
        for (int i = 0; i < OVERLAP; i++) {
            m2.put(LuaString.valueOf("k" + i), LuaInteger.valueOf(i + 100000));
        }
        for (int i = OVERLAP; i < SIZE; i++) {
            // add non-overlapping keys for m2 by offsetting index
            m2.put(LuaString.valueOf("m" + i), LuaInteger.valueOf(i + 200000));
        }

        LuaMap lua1 = buildLuaMapFromLuaValues(m1);
        LuaMap lua2 = buildLuaMapFromLuaValues(m2);

        long start = System.currentTimeMillis();
        LuaMap diff = lua1.diff(lua2);
        long duration = System.currentTimeMillis() - start;

        // Expected symmetric difference size:
        // m1 unique keys = SIZE - OVERLAP
        // m2 unique keys = (SIZE - OVERLAP) + (OVERLAP keys are shared)
        // But given construction:
        // m1 unique = SIZE - OVERLAP
        // m2 unique = SIZE - OVERLAP (since half are overlapping, half are m-prefixed)
        // total = 2*(SIZE - OVERLAP)
        int expected = 2 * (SIZE - OVERLAP);
        assertEquals(expected, diff.size().toint());

        // Quick sanity that operation completed in reasonable time (not a strict performance test).
        // This guards against pathological O(n^2) implementations on small sizes.
        assertTrue("diff took too long: " + duration + "ms", duration < 5000);
    }
}

To edit these changes git checkout codeflash/optimize-LuaMap.diff-mmbeu7sd and push.

Codeflash Static Badge

The change yields a measurable runtime win: 849 µs → 773 µs (≈9% faster) by reworking diff to reduce hash work and avoid repeated field accesses. Concretely, the implementation caches map2.map locally, precomputes HashMap capacity to avoid rehashing, does a single putAll(copy) of this.map, and then makes one pass over the other map removing common keys or inserting unique ones. This reduces the number of containsKey/remove/put hash lookups and mitigates costly rehash/resizing operations, resulting in fewer allocations and better cache behavior on the hot path. The trade-off is a slightly larger one-time allocation for the pre-sized target and a short-lived copy of this.map, which increases transient memory usage but keeps overall time complexity lower and reduces runtime for typical inputs.
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 March 4, 2026 02:21
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Mar 4, 2026
Copy link
Copy Markdown

@misrasaurabh1 misrasaurabh1 left a comment

Choose a reason for hiding this comment

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

i think its better although would need a deeper inspection of different cases

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.

1 participant