Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Nov 13, 2025

📄 6% (0.06x) speedup for BCDataStream.write_int32 in electrum/transaction.py

⏱️ Runtime : 2.16 milliseconds 2.03 milliseconds (best of 118 runs)

📝 Explanation and details

The optimization introduces a pre-cached struct.Struct object (_STRUCT_I) for the '<i' format string, which is the most commonly used format in write_int32. This avoids the overhead of parsing the format string on every call.

Key changes:

  • Added _STRUCT_I = struct.Struct('<i') as a module-level constant
  • Modified _write_num to check if format == '<i' and use the cached struct's .pack() method directly
  • Falls back to struct.pack() for other formats

Why this is faster:
In Python, struct.pack(format, value) must parse the format string each time it's called. By pre-creating a struct.Struct object, the format parsing happens only once at module load time. The cached struct's .pack() method is significantly faster for repeated calls with the same format.

Performance characteristics:
The line profiler shows the optimization reduces time spent in the struct packing operation from 31.3% to 26% of total function time. While individual test cases show mixed results (some 2-13% slower for single calls due to the added format check), the large-scale tests demonstrate the real benefit: batch operations with 1000+ write_int32 calls show 6-8% speedup, which aligns with the overall 6% improvement.

Impact on workloads:
This optimization is particularly effective for Bitcoin transaction processing where write_int32 is called frequently in tight loops during serialization. The small overhead for single calls is negligible compared to the substantial gains when processing large batches of transactions or blockchain data, which is the typical use case for this code in the Electrum Bitcoin client.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 7066 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import struct

# imports
import pytest
from electrum.transaction import BCDataStream

# unit tests

# 1. Basic Test Cases

def test_write_int32_positive():
    # Test writing a simple positive int32 value
    stream = BCDataStream()
    stream.write_int32(42) # 2.01μs -> 2.09μs (3.79% slower)

def test_write_int32_negative():
    # Test writing a simple negative int32 value
    stream = BCDataStream()
    stream.write_int32(-42) # 1.37μs -> 1.40μs (2.14% slower)

def test_write_int32_zero():
    # Test writing zero
    stream = BCDataStream()
    stream.write_int32(0) # 1.26μs -> 1.39μs (9.09% slower)

def test_write_int32_multiple_writes():
    # Test writing multiple int32 values sequentially
    stream = BCDataStream()
    stream.write_int32(1) # 1.25μs -> 1.32μs (5.97% slower)
    stream.write_int32(-1) # 807ns -> 879ns (8.19% slower)
    stream.write_int32(123456) # 371ns -> 358ns (3.63% faster)
    expected = struct.pack('<i', 1) + struct.pack('<i', -1) + struct.pack('<i', 123456)

# 2. Edge Test Cases

def test_write_int32_min_value():
    # Test writing minimum int32 value
    stream = BCDataStream()
    min_int32 = -2**31
    stream.write_int32(min_int32) # 1.17μs -> 1.27μs (8.26% slower)

def test_write_int32_max_value():
    # Test writing maximum int32 value
    stream = BCDataStream()
    max_int32 = 2**31 - 1
    stream.write_int32(max_int32) # 1.18μs -> 1.27μs (7.15% slower)

def test_write_int32_overflow_positive():
    # Test writing value above int32 max should raise struct.error
    stream = BCDataStream()
    with pytest.raises(struct.error):
        stream.write_int32(2**31) # 2.65μs -> 2.81μs (6.00% slower)

def test_write_int32_overflow_negative():
    # Test writing value below int32 min should raise struct.error
    stream = BCDataStream()
    with pytest.raises(struct.error):
        stream.write_int32(-2**31 - 1) # 1.90μs -> 2.20μs (13.7% slower)

def test_write_int32_non_integer():
    # Test writing a non-integer value should raise struct.error
    stream = BCDataStream()
    with pytest.raises(struct.error):
        stream.write_int32("not an int") # 1.30μs -> 1.41μs (7.78% slower)

def test_write_int32_float():
    # Test writing a float value should raise struct.error
    stream = BCDataStream()
    with pytest.raises(struct.error):
        stream.write_int32(3.14) # 1.25μs -> 1.37μs (8.56% slower)

def test_write_int32_bool():
    # Test writing a boolean (should be treated as int, True=1, False=0)
    stream = BCDataStream()
    stream.write_int32(True) # 1.55μs -> 1.56μs (0.449% slower)
    stream2 = BCDataStream()
    stream2.write_int32(False) # 474ns -> 553ns (14.3% slower)

def test_write_int32_none():
    # Test writing None should raise struct.error
    stream = BCDataStream()
    with pytest.raises(struct.error):
        stream.write_int32(None) # 1.21μs -> 1.32μs (8.48% slower)

def test_write_int32_input_already_initialized():
    # Test writing when input is already a bytearray
    stream = BCDataStream()
    stream.input = bytearray(b'abc')
    stream.write_int32(123) # 1.29μs -> 1.42μs (8.69% slower)
    expected = b'abc' + struct.pack('<i', 123)

# 3. Large Scale Test Cases

def test_write_int32_large_batch():
    # Test writing a large number of int32 values (performance and correctness)
    stream = BCDataStream()
    values = list(range(-500, 500))  # 1000 values
    for v in values:
        stream.write_int32(v) # 306μs -> 282μs (8.45% faster)
    # Check a random value in the middle
    idx = 250
    start = idx * 4

def test_write_int32_large_batch_min_max():
    # Test writing a batch of min and max values
    stream = BCDataStream()
    min_int32 = -2**31
    max_int32 = 2**31 - 1
    for v in [min_int32, max_int32] * 500:  # 1000 values alternating
        stream.write_int32(v) # 301μs -> 284μs (6.00% faster)

def test_write_int32_performance_under_1000():
    # Test that writing 1000 int32 values is reasonably fast and correct
    import time
    stream = BCDataStream()
    values = [i for i in range(1000)]
    start_time = time.time()
    for v in values:
        stream.write_int32(v) # 301μs -> 278μs (8.17% faster)
    elapsed = time.time() - start_time
    # Check correctness
    for i, v in enumerate(values):
        pass
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import struct

# imports
import pytest
from electrum.transaction import BCDataStream

# unit tests

# ----------- BASIC TEST CASES -----------

def test_write_int32_basic_positive():
    """Test writing a basic positive int32 value."""
    ds = BCDataStream()
    ds.write_int32(12345678) # 2.06μs -> 2.11μs (2.37% slower)

def test_write_int32_basic_negative():
    """Test writing a basic negative int32 value."""
    ds = BCDataStream()
    ds.write_int32(-12345678) # 1.41μs -> 1.50μs (5.42% slower)

def test_write_int32_basic_zero():
    """Test writing zero."""
    ds = BCDataStream()
    ds.write_int32(0) # 1.22μs -> 1.40μs (12.8% slower)

def test_write_int32_multiple_writes():
    """Test writing multiple int32 values in sequence."""
    ds = BCDataStream()
    ds.write_int32(1) # 1.22μs -> 1.39μs (12.1% slower)
    ds.write_int32(2) # 753ns -> 803ns (6.23% slower)
    ds.write_int32(3) # 349ns -> 357ns (2.24% slower)
    expected = struct.pack('<i', 1) + struct.pack('<i', 2) + struct.pack('<i', 3)

def test_write_int32_return_none():
    """Test that write_int32 returns None as expected."""
    ds = BCDataStream()
    codeflash_output = ds.write_int32(42); ret = codeflash_output # 1.16μs -> 1.26μs (8.11% slower)

# ----------- EDGE TEST CASES -----------

def test_write_int32_min_int32():
    """Test writing minimum int32 value (-2**31)."""
    ds = BCDataStream()
    ds.write_int32(-2**31) # 1.18μs -> 1.31μs (9.56% slower)

def test_write_int32_max_int32():
    """Test writing maximum int32 value (2**31-1)."""
    ds = BCDataStream()
    ds.write_int32(2**31-1) # 1.16μs -> 1.33μs (12.7% slower)

def test_write_int32_below_min_int32_raises():
    """Test writing a value below min int32 raises struct.error."""
    ds = BCDataStream()
    with pytest.raises(struct.error):
        ds.write_int32(-2**31 - 1) # 2.44μs -> 2.76μs (11.5% slower)

def test_write_int32_above_max_int32_raises():
    """Test writing a value above max int32 raises struct.error."""
    ds = BCDataStream()
    with pytest.raises(struct.error):
        ds.write_int32(2**31) # 1.95μs -> 2.08μs (6.30% slower)

def test_write_int32_non_integer_type_raises():
    """Test writing a non-integer value raises TypeError."""
    ds = BCDataStream()
    with pytest.raises(TypeError):
        ds.write_int32("not an int")
    with pytest.raises(TypeError):
        ds.write_int32(3.14)
    with pytest.raises(TypeError):
        ds.write_int32(None)
    with pytest.raises(TypeError):
        ds.write_int32([1,2,3])

def test_write_int32_bool_type():
    """Test writing a boolean value (should treat as int: True==1, False==0)."""
    ds = BCDataStream()
    ds.write_int32(True) # 2.49μs -> 2.44μs (1.92% faster)
    ds.write_int32(False) # 826ns -> 887ns (6.88% slower)
    expected = struct.pack('<i', 1) + struct.pack('<i', 0)

def test_write_int32_preserves_existing_bytearray():
    """Test that write_int32 extends an existing input bytearray, not replaces."""
    ds = BCDataStream()
    ds.input = bytearray(b'abc')
    ds.write_int32(7) # 1.20μs -> 1.33μs (10.1% slower)

def test_write_int32_writes_are_little_endian():
    """Test that int32 values are written in little-endian order."""
    ds = BCDataStream()
    ds.write_int32(0x01020304) # 1.33μs -> 1.38μs (3.41% slower)

# ----------- LARGE SCALE TEST CASES -----------

def test_write_int32_large_scale_many_writes():
    """Test writing a large number of int32 values (1000 elements)."""
    ds = BCDataStream()
    values = list(range(1000))
    for v in values:
        ds.write_int32(v) # 304μs -> 285μs (6.57% faster)
    # Spot check a few values
    for idx in [0, 499, 999]:
        start = idx * 4
        expected = struct.pack('<i', values[idx])

def test_write_int32_large_scale_extreme_values():
    """Test writing a mix of min/max/zero/normal values in a large sequence."""
    ds = BCDataStream()
    values = [0, -2**31, 2**31-1, 42, -42] * 200  # 1000 elements
    for v in values:
        ds.write_int32(v) # 300μs -> 283μs (5.99% faster)

def test_write_int32_large_scale_performance():
    """Test that writing 1000 int32s is reasonably fast (not measured, but should not error)."""
    ds = BCDataStream()
    for i in range(1000):
        ds.write_int32(i) # 298μs -> 281μs (6.26% faster)

def test_write_int32_large_scale_preserves_order():
    """Test that values are written in correct order in large scale."""
    ds = BCDataStream()
    values = [100, 200, -300, 400, -500] * 200  # 1000 elements
    for v in values:
        ds.write_int32(v) # 300μs -> 283μs (5.93% faster)
    # Check a few positions for correct value
    for idx in [0, 199, 500, 999]:
        start = idx * 4
        expected = struct.pack('<i', values[idx])
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-BCDataStream.write_int32-mhxqh6sv and push.

Codeflash Static Badge

The optimization introduces a **pre-cached `struct.Struct` object** (`_STRUCT_I`) for the `'<i'` format string, which is the most commonly used format in `write_int32`. This avoids the overhead of parsing the format string on every call.

**Key changes:**
- Added `_STRUCT_I = struct.Struct('<i')` as a module-level constant
- Modified `_write_num` to check if `format == '<i'` and use the cached struct's `.pack()` method directly
- Falls back to `struct.pack()` for other formats

**Why this is faster:**
In Python, `struct.pack(format, value)` must parse the format string each time it's called. By pre-creating a `struct.Struct` object, the format parsing happens only once at module load time. The cached struct's `.pack()` method is significantly faster for repeated calls with the same format.

**Performance characteristics:**
The line profiler shows the optimization reduces time spent in the struct packing operation from 31.3% to 26% of total function time. While individual test cases show mixed results (some 2-13% slower for single calls due to the added format check), the **large-scale tests demonstrate the real benefit**: batch operations with 1000+ `write_int32` calls show **6-8% speedup**, which aligns with the overall 6% improvement.

**Impact on workloads:**
This optimization is particularly effective for Bitcoin transaction processing where `write_int32` is called frequently in tight loops during serialization. The small overhead for single calls is negligible compared to the substantial gains when processing large batches of transactions or blockchain data, which is the typical use case for this code in the Electrum Bitcoin client.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 November 13, 2025 17:59
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Nov 13, 2025
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: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant