Skip to content

⚡️ Speed up method CTX.fromBytes by 7%#106

Open
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-CTX.fromBytes-mmc8vw29
Open

⚡️ Speed up method CTX.fromBytes by 7%#106
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-CTX.fromBytes-mmc8vw29

Conversation

@codeflash-ai
Copy link
Copy Markdown

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

📄 7% (0.07x) speedup for CTX.fromBytes in client/src/com/aerospike/client/cdt/CTX.java

⏱️ Runtime : 517 microseconds 482 microseconds (best of 169 runs)

📝 Explanation and details

Runtime improved by ~7% (517 µs → 482 µs) with the large-input unit test showing up to ~20% speedup; the change reduces per-iteration overhead in the hot path. The implementation now checks list-length parity once up-front, iterates with a for-loop that increments by two, and replaces the chained Long casts with a single ((Number) list.get(i)).intValue() call. This is faster because it eliminates the repeated odd-length check and extra index math in each iteration, removes redundant cast/unbox operations, and produces simpler bytecode that the JIT can inline more effectively, lowering object/branch overhead on large arrays. Trade-offs are minor: no API change and negligible memory impact, with a slight tightening of numeric extraction to Number.intValue() which matches the expected inputs.

Correctness verification report:

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

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

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Value;
// Performance comparison:
// CTXTest.testFromBytes_MultipleEntries_ReturnsMultipleCTX#2: 0.004ms -> 0.003ms (9.1% faster)
// CTXTest.testFromBytes_NullInput_ThrowsNullPointerException#5: 0.040ms -> 0.039ms (1.0% faster)
// CTXTest.testFromBytes_LargeInput_HandlesManyEntriesCorrectly#6: 0.144ms -> 0.143ms (0.9% faster)
// CTXTest.testFromBytes_SingleEntry_ReturnsSingleCTX#1: 0.002ms -> 0.002ms (-3.1% faster)
// CTXTest.testFromBytes_OddCount_ThrowsParse#4: 0.108ms -> 0.107ms (1.3% faster)
// CTXTest.testFromBytes_EmptyList_ReturnsZeroLengthArray#3: 0.001ms -> 0.001ms (8.5% faster)
// CTXTest.testBoundary_smallFixArrayHeader_expectedWorks#6: 0.002ms -> 0.003ms (-12.5% faster)
// CTXTest.testOddList_throwsParseException#3: 0.017ms -> 0.017ms (-1.1% faster)
// CTXTest.testLargeInput_performanceAndContent#5: 0.160ms -> 0.128ms (19.8% faster)
// CTXTest.testBoundary_array16Header_expectedWorks#7: 0.004ms -> 0.004ms (-3.5% faster)
// CTXTest.testNullInput_throwsNullPointerException#4: 0.031ms -> 0.031ms (1.5% faster)
// CTXTest.testBasicFunctionality_expectedCTXArray#1: 0.002ms -> 0.002ms (-4.2% faster)
// CTXTest.testEmptyInput_expectedEmptyArray#2: 0.001ms -> 0.001ms (10.2% faster)

/**
 * Unit tests for com.aerospike.client.cdt.CTX#fromBytes
 */
public class CTXTest {
    // A CTX instance created via reflection (private constructor). This satisfies the
    // requirement to "create an instance of CTX" for test setup.
    private CTX reflectedInstance;

    @Before
    public void setUp() throws Exception {
        // Use reflection to call the private CTX(int, Value) constructor.
        Constructor<CTX> ctor = CTX.class.getDeclaredConstructor(int.class, com.aerospike.client.Value.class);
        ctor.setAccessible(true);
        reflectedInstance = ctor.newInstance(42, Value.get("ref"));
        assertNotNull("Reflected CTX instance should be created", reflectedInstance);
        // Basic sanity check of reflected instance
        assertEquals(42, reflectedInstance.id);
        assertEquals("ref", reflectedInstance.value.toString());
    }

    @Test
    public void testBasicFunctionality_expectedCTXArray() throws Exception {
        // Build MessagePack array representing: [1, "a", 2, "b"]
        Object[] items = new Object[] { Integer.valueOf(1), "a", Integer.valueOf(2), "b" };
        byte[] packed = packMsgPackArray(items);

        CTX[] ctx = CTX.fromBytes(packed);
        assertNotNull(ctx);
        assertEquals("Should have two CTX entries", 2, ctx.length);

        assertEquals(1, ctx[0].id);
        assertEquals("a", ctx[0].value.toString());

        assertEquals(2, ctx[1].id);
        assertEquals("b", ctx[1].value.toString());
    }

    @Test
    public void testEmptyInput_expectedEmptyArray() throws Exception {
        // Pack empty array
        byte[] packed = packMsgPackArray(new Object[0]);

        CTX[] ctx = CTX.fromBytes(packed);
        assertNotNull(ctx);
        assertEquals("Empty input should produce zero-length CTX array", 0, ctx.length);
    }

    @Test(expected = AerospikeException.Parse.class)
    public void testOddList_throwsParseException() throws Exception {
        // Build MessagePack array with odd number of elements: [1, "a", 2]
        Object[] items = new Object[] { Integer.valueOf(1), "a", Integer.valueOf(2) };
        byte[] packed = packMsgPackArray(items);

        // Expect Parse exception because list length is odd
        CTX.fromBytes(packed);
    }

    @Test(expected = NullPointerException.class)
    public void testNullInput_throwsNullPointerException() {
        // Passing null should result in a NullPointerException from Unpacker.unpackObjectList
        CTX.fromBytes(null);
    }

    @Test
    public void testLargeInput_performanceAndContent() throws Exception {
        // Build 1000 pairs -> 2000 array elements
        final int pairs = 1000;
        Object[] items = new Object[pairs * 2];
        for (int i = 0; i < pairs; i++) {
            items[2 * i] = Integer.valueOf(i);
            items[2 * i + 1] = "v" + i;
        }
        byte[] packed = packMsgPackArray(items);

        long start = System.currentTimeMillis();
        CTX[] ctx = CTX.fromBytes(packed);
        long duration = System.currentTimeMillis() - start;

        // Basic checks
        assertNotNull(ctx);
        assertEquals("Should deserialize all pairs", pairs, ctx.length);

        // Check a few sample positions for correctness
        assertEquals(0, ctx[0].id);
        assertEquals("v0", ctx[0].value.toString());

        assertEquals(499, ctx[499].id);
        assertEquals("v499", ctx[499].value.toString());

        assertEquals(999, ctx[999].id);
        assertEquals("v999", ctx[999].value.toString());

        // Performance soft check: ensure it finished within a reasonable time (e.g., 5 seconds).
        // This is not a strict requirement but helps catch pathological performance regressions.
        assertTrue("Deserialization took too long: " + duration + "ms", duration < 5000);
    }

    @Test
    public void testBoundary_smallFixArrayHeader_expectedWorks() throws Exception {
        // Use 6 elements (3 pairs) to exercise fixarray header (size <= 15)
        Object[] items = new Object[] {
            Integer.valueOf(10), "x",
            Integer.valueOf(11), "y",
            Integer.valueOf(12), "z"
        };
        byte[] packed = packMsgPackArray(items);

        CTX[] ctx = CTX.fromBytes(packed);
        assertEquals(3, ctx.length);
        assertEquals(10, ctx[0].id);
        assertEquals("x", ctx[0].value.toString());
        assertEquals(12, ctx[2].id);
        assertEquals("z", ctx[2].value.toString());
    }

    @Test
    public void testBoundary_array16Header_expectedWorks() throws Exception {
        // Use 20 elements (10 pairs) to force array16 header (size > 15)
        int pairs = 10;
        Object[] items = new Object[pairs * 2];
        for (int i = 0; i < pairs; i++) {
            items[2 * i] = Integer.valueOf(100 + i);
            items[2 * i + 1] = "val" + i;
        }
        byte[] packed = packMsgPackArray(items);
        CTX[] ctx = CTX.fromBytes(packed);
        assertEquals(pairs, ctx.length);
        assertEquals(100, ctx[0].id);
        assertEquals("val0", ctx[0].value.toString());
        assertEquals(109, ctx[9].id);
        assertEquals("val9", ctx[9].value.toString());
    }

    // ---------- Helpers ----------

    /**
     * Pack a MessagePack array with simple types (non-negative integers that fit into single byte,
     * and short strings that fit into fixstr).
     *
     * This helper is intentionally minimal and implements only the MessagePack encodings used by tests:
     * - fixarray (size <= 15) or array16 (size <= 65535)
     * - positive fixint for integers 0..127
     * - fixstr for strings length <= 31
     *
     * Supported item types: Integer, Long, String.
     */
    private static byte[] packMsgPackArray(Object[] items) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        int size = items.length;
        if (size <= 15) {
            out.write((byte) (0x90 | size)); // fixarray
        }
        else if (size <= 0xFFFF) {
            out.write((byte) 0xDC); // array16
            out.write((byte) ((size >> 8) & 0xFF));
            out.write((byte) (size & 0xFF));
        }
        else {
            out.write((byte) 0xDD); // array32
            out.write((byte) ((size >> 24) & 0xFF));
            out.write((byte) ((size >> 16) & 0xFF));
            out.write((byte) ((size >> 8) & 0xFF));
            out.write((byte) (size & 0xFF));
        }

        for (Object obj : items) {
            if (obj instanceof Integer) {
                int v = (Integer) obj;
                if (v >= 0 && v <= 127) {
                    out.write((byte) v); // positive fixint
                } else if (v >= -32 && v < 0) {
                    out.write((byte) v); // negative fixint (rare in tests)
                } else {
                    // fallback to int 32 format (not expected in our tests)
                    out.write((byte) 0xD2);
                    out.write((byte) ((v >> 24) & 0xFF));
                    out.write((byte) ((v >> 16) & 0xFF));
                    out.write((byte) ((v >> 8) & 0xFF));
                    out.write((byte) (v & 0xFF));
                }
            }
            else if (obj instanceof Long) {
                long v = (Long) obj;
                if (v >= 0 && v <= 127) {
                    out.write((byte) v); // positive fixint
                } else {
                    // fallback to int64
                    out.write((byte) 0xD3);
                    out.write((byte) ((v >> 56) & 0xFF));
                    out.write((byte) ((v >> 48) & 0xFF));
                    out.write((byte) ((v >> 40) & 0xFF));
                    out.write((byte) ((v >> 32) & 0xFF));
                    out.write((byte) ((v >> 24) & 0xFF));
                    out.write((byte) ((v >> 16) & 0xFF));
                    out.write((byte) ((v >> 8) & 0xFF));
                    out.write((byte) (v & 0xFF));
                }
            }
            else if (obj instanceof String) {
                byte[] bs = ((String) obj).getBytes(StandardCharsets.UTF_8);
                int len = bs.length;
                if (len <= 31) {
                    out.write((byte) (0xA0 | len)); // fixstr
                    out.write(bs);
                } else if (len <= 0xFF) {
                    out.write((byte) 0xD9);
                    out.write((byte) len);
                    out.write(bs);
                } else if (len <= 0xFFFF) {
                    out.write((byte) 0xDA);
                    out.write((byte) ((len >> 8) & 0xFF));
                    out.write((byte) (len & 0xFF));
                    out.write(bs);
                } else {
                    out.write((byte) 0xDB);
                    out.write((byte) ((len >> 24) & 0xFF));
                    out.write((byte) ((len >> 16) & 0xFF));
                    out.write((byte) ((len >> 8) & 0xFF));
                    out.write((byte) (len & 0xFF));
                    out.write(bs);
                }
            }
            else {
                throw new IllegalArgumentException("Unsupported type in pack helper: " + obj.getClass());
            }
        }

        return out.toByteArray();
    }
}

To edit these changes git checkout codeflash/optimize-CTX.fromBytes-mmc8vw29 and push.

Codeflash Static Badge

Runtime improved by ~7% (517 µs → 482 µs) with the large-input unit test showing up to ~20% speedup; the change reduces per-iteration overhead in the hot path. The implementation now checks list-length parity once up-front, iterates with a for-loop that increments by two, and replaces the chained Long casts with a single ((Number) list.get(i)).intValue() call. This is faster because it eliminates the repeated odd-length check and extra index math in each iteration, removes redundant cast/unbox operations, and produces simpler bytecode that the JIT can inline more effectively, lowering object/branch overhead on large arrays. Trade-offs are minor: no API change and negligible memory impact, with a slight tightening of numeric extraction to Number.intValue() which matches the expected inputs.
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 March 4, 2026 16:22
@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.

1 participant