Skip to content

⚡️ Speed up method Info.parseMultiResponse by 28%#108

Open
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-Info.parseMultiResponse-mmchopdy
Open

⚡️ Speed up method Info.parseMultiResponse by 28%#108
codeflash-ai[bot] wants to merge 1 commit intofix/add-mockito-test-dependencyfrom
codeflash/optimize-Info.parseMultiResponse-mmchopdy

Conversation

@codeflash-ai
Copy link
Copy Markdown

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

📄 28% (0.28x) speedup for Info.parseMultiResponse in client/src/com/aerospike/client/Info.java

⏱️ Runtime : 503 microseconds 392 microseconds (best of 169 runs)

📝 Explanation and details

The optimized parseMultiResponse reduces runtime from 503 µs to 392 µs (≈28% speedup). It copies instance fields into final local variables (buf, len), pre-sizes the HashMap with an initial capacity of 8, and tightens the inner loop to check bounds and newline in a single condition. These changes remove repeated instance field loads, avoid HashMap rehashing during growth, and eliminate an extra branch per byte in the hot parsing loop, which lowers CPU and allocation overhead on larger responses. The only trade-off is a small upfront HashMap allocation for tiny responses, which is negligible compared to the savings for typical workloads.

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 93.8%
🌀 Click to see Generated Regression Tests
package com.aerospike.client;

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

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import com.aerospike.client.Info;
import com.aerospike.client.AerospikeException;
// Performance comparison:
// InfoTest.testTrailingNameWithoutTerminator_ReturnsNameWithNull#4: 0.001ms -> 0.001ms (2.2% faster)
// InfoTest.testLargeNumberOfEntries_PerformanceAndCorrectness#7: 0.303ms -> 0.195ms (35.7% faster)
// InfoTest.testMultipleEntries_MixedNullsAndValues#3: 0.000ms -> 0.000ms (7.9% faster)
// InfoTest.testErrorResponse_ThrowsAerospikeException#6: 0.000ms -> 0.000ms (-11.9% faster)
// InfoTest.testEmptyBuffer_ReturnsEmptyMap#5: 0.000ms -> 0.000ms (32.7% faster)
// InfoTest.testNameWithValue_ReturnsValue#2: 0.000ms -> 0.000ms (2.4% faster)
// InfoTest.testSingleName_NoValue_ReturnsNullValue#1: 0.000ms -> 0.000ms (2.8% faster)
// InfoTest.testEmptyValueAfterTab_expectedNullValue#6: 0.000ms -> 0.000ms (4.3% faster)
// InfoTest.testEmptyBuffer_expectedEmptyMap#5: 0.000ms -> 0.000ms (-8.0% faster)
// InfoTest.testMultipleMixed_expectedCorrectMap#3: 0.000ms -> 0.000ms (-47.7% faster)
// InfoTest.testErrorResponse_expectedAerospikeException#7: 0.117ms -> 0.108ms (7.4% faster)
// InfoTest.testSingleNameWithoutValue_expectedNullValue#1: 0.000ms -> 0.000ms (9.9% faster)
// InfoTest.testTrailingNameWithoutNewline_expectedNullValue#4: 0.000ms -> 0.000ms (21.8% faster)
// InfoTest.testSingleNameWithValue_expectedValue#2: 0.000ms -> 0.000ms (38.3% faster)
// InfoTest.testLargeNumberOfEntries_performanceAndCorrectness#8: 0.079ms -> 0.086ms (-8.6% faster)

public class InfoTest {

    private Info infoInstance; // can be used when needed

    @Before
    public void setUp() {
        // Intentionally left blank. Individual tests create their own Info instances
        // using the internal byte[] constructor to exercise parseMultiResponse.
    }

    @Test
    public void testSingleNameWithoutValue_expectedNullValue() {
        // "node\n" -> key "node" with null value
        byte[] buf = "node\n".getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();
        assertEquals(1, resp.size());
        assertTrue(resp.containsKey("node"));
        assertNull(resp.get("node"));
    }

    @Test
    public void testSingleNameWithValue_expectedValue() {
        // "name\tvalue\n" -> key "name" with value "value"
        byte[] buf = "name\tvalue\n".getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();
        assertEquals(1, resp.size());
        assertEquals("value", resp.get("name"));
    }

    @Test
    public void testMultipleMixed_expectedCorrectMap() {
        // Mix of entries:
        // "a\n" -> a:null
        // "b\tv\n" -> b:"v"
        // "c\t\n" -> c:null (empty value)
        // "d\tval with spaces\n" -> d:"val with spaces"
        StringBuilder sb = new StringBuilder();
        sb.append("a\n");
        sb.append("b\tv\n");
        sb.append("c\t\n");
        sb.append("d\tval with spaces\n");
        byte[] buf = sb.toString().getBytes(StandardCharsets.UTF_8);

        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();

        assertEquals(4, resp.size());
        assertTrue(resp.containsKey("a"));
        assertNull(resp.get("a"));

        assertTrue(resp.containsKey("b"));
        assertEquals("v", resp.get("b"));

        assertTrue(resp.containsKey("c"));
        assertNull(resp.get("c"));

        assertTrue(resp.containsKey("d"));
        assertEquals("val with spaces", resp.get("d"));
    }

    @Test
    public void testTrailingNameWithoutNewline_expectedNullValue() {
        // "last" with no trailing newline should be treated as a name with null value.
        byte[] buf = "last".getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();
        assertEquals(1, resp.size());
        assertTrue(resp.containsKey("last"));
        assertNull(resp.get("last"));
    }

    @Test
    public void testEmptyBuffer_expectedEmptyMap() {
        byte[] buf = new byte[0];
        Info info = new Info(buf, 0);
        Map<String,String> resp = info.parseMultiResponse();
        assertNotNull(resp);
        assertTrue(resp.isEmpty());
    }

    @Test
    public void testEmptyValueAfterTab_expectedNullValue() {
        // "key\t\n" -> key:null (empty value after tab)
        byte[] buf = "key\t\n".getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();
        assertEquals(1, resp.size());
        assertTrue(resp.containsKey("key"));
        assertNull(resp.get("key"));
    }

    @Test(expected = AerospikeException.class)
    public void testErrorResponse_expectedAerospikeException() {
        // To trigger checkError, the instance buffer must start with "ERROR"
        // and parseMultiResponse must encounter a tab which causes checkError() to be invoked.
        // Construct: "ERROR:2:boom\tname\tvalue\n"
        // parseMultiResponse will see the first '\t' after the "ERROR..." and call checkError,
        // which should detect the "ERROR" at the beginning and throw AerospikeException.
        byte[] buf = "ERROR:2:boom\tname\tvalue\n".getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        // Expect AerospikeException to be thrown during parsing
        info.parseMultiResponse();
    }

    @Test
    public void testLargeNumberOfEntries_performanceAndCorrectness() {
        // Build many simple name-only entries to validate scaling and correctness.
        // Use a modest size to keep test quick but still larger than trivial.
        final int COUNT = 2000;
        StringBuilder sb = new StringBuilder(COUNT * 6);
        for (int i = 0; i < COUNT; i++) {
            sb.append("n").append(i).append("\n");
        }
        byte[] buf = sb.toString().getBytes(StandardCharsets.UTF_8);
        Info info = new Info(buf, buf.length);
        Map<String,String> resp = info.parseMultiResponse();
        assertEquals(COUNT, resp.size());
        // spot-check a few entries
        assertNull(resp.get("n0"));
        assertNull(resp.get("n" + (COUNT - 1)));
        assertTrue(resp.containsKey("n100"));
    }
}

To edit these changes git checkout codeflash/optimize-Info.parseMultiResponse-mmchopdy and push.

Codeflash Static Badge

The optimized parseMultiResponse reduces runtime from 503 µs to 392 µs (≈28% speedup). It copies instance fields into final local variables (buf, len), pre-sizes the HashMap with an initial capacity of 8, and tightens the inner loop to check bounds and newline in a single condition. These changes remove repeated instance field loads, avoid HashMap rehashing during growth, and eliminate an extra branch per byte in the hot parsing loop, which lowers CPU and allocation overhead on larger responses. The only trade-off is a small upfront HashMap allocation for tiny responses, which is negligible compared to the savings for typical workloads.
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 March 4, 2026 20:28
@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