⚡️ Speed up function decode_hex by 49%
#54
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
📄 49% (0.49x) speedup for
decode_hexinpython/ccxt/static_dependencies/ethereum/utils/hexadecimal.py⏱️ Runtime :
132 microseconds→88.3 microseconds(best of250runs)📝 Explanation and details
The optimization achieves a 49% speedup by eliminating function call overhead and inlining critical checks in hot paths.
Key Optimizations Applied:
Inlined
is_text()function: Replacedis_text(value)calls with directisinstance(value, str)checks. The line profiler showsis_text()was called 120 times with significant overhead (48,326 nanoseconds total). This eliminated both the function call and tuple lookup againsttext_types.Inlined prefix removal logic: Instead of calling
remove_0x_prefix()andis_0x_prefixed(), the optimized code directly checksvalue.startswith(("0x", "0X"))and performs string slicing inline. This eliminates two nested function calls perdecode_hex()invocation.Removed unnecessary
HexStrwrapper: Eliminated theHexStr(value)conversion sinceHexStris just astrsubclass, avoiding object creation overhead.Performance Impact Analysis:
The line profiler reveals the optimization targets were well-chosen:
decode_hex()spent 32.1% of time onis_text()check and 55.6% onremove_0x_prefix()Test Case Benefits:
The optimization shows consistent improvements across all test scenarios:
This optimization is particularly valuable for cryptocurrency libraries like CCXT where hex decoding is frequently performed in transaction processing and address validation workflows.
✅ Correctness verification report:
🌀 Generated Regression Tests and Runtime
import binascii
function to test
from typing import Any
imports
import pytest # used for our unit tests
from ccxt.static_dependencies.ethereum.utils.hexadecimal import decode_hex
class HexStr(str):
pass
from ccxt.static_dependencies.ethereum.utils.hexadecimal import decode_hex
unit tests
--------------------
Basic Test Cases
--------------------
def test_decode_hex_basic_lowercase():
# Test decoding a basic lowercase hex string with 0x prefix
codeflash_output = decode_hex('0x68656c6c6f') # 2.40μs -> 1.42μs (69.3% faster)
def test_decode_hex_basic_uppercase():
# Test decoding a basic uppercase hex string with 0X prefix
codeflash_output = decode_hex('0X68656C6C6F') # 2.00μs -> 1.28μs (56.2% faster)
def test_decode_hex_no_prefix():
# Test decoding a hex string without any prefix
codeflash_output = decode_hex('68656c6c6f') # 1.61μs -> 900ns (78.6% faster)
def test_decode_hex_empty_string():
# Test decoding an empty string
codeflash_output = decode_hex('') # 1.55μs -> 940ns (64.6% faster)
def test_decode_hex_single_byte():
# Test decoding a single byte
codeflash_output = decode_hex('0x41') # 1.94μs -> 1.19μs (63.2% faster)
def test_decode_hex_even_length():
# Test decoding an even-length hex string
codeflash_output = decode_hex('0x41424344') # 1.85μs -> 1.09μs (68.7% faster)
def test_decode_hex_odd_length():
# Test decoding an odd-length hex string (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0x123') # 2.40μs -> 1.70μs (41.3% faster)
def test_decode_hex_non_hex_characters():
# Test decoding a string with non-hex characters (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0xZZZZ') # 2.42μs -> 1.67μs (44.9% faster)
def test_decode_hex_mixed_case():
# Test decoding a hex string with mixed case letters
codeflash_output = decode_hex('0xAaBbCcDd') # 1.94μs -> 1.24μs (56.2% faster)
--------------------
Edge Test Cases
--------------------
def test_decode_hex_non_string_input():
# Test passing non-string input (should raise TypeError)
with pytest.raises(TypeError):
decode_hex(12345) # 936ns -> 751ns (24.6% faster)
def test_decode_hex_bytes_input():
# Test passing bytes input (should raise TypeError)
with pytest.raises(TypeError):
decode_hex(b'68656c6c6f') # 876ns -> 692ns (26.6% faster)
def test_decode_hex_none_input():
# Test passing None input (should raise TypeError)
with pytest.raises(TypeError):
decode_hex(None) # 823ns -> 700ns (17.6% faster)
def test_decode_hex_only_prefix():
# Test passing only the prefix (should return empty bytes)
codeflash_output = decode_hex('0x') # 2.41μs -> 1.33μs (80.5% faster)
def test_decode_hex_prefix_and_space():
# Test passing prefix and spaces (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0x ') # 2.50μs -> 1.72μs (45.6% faster)
def test_decode_hex_with_whitespace():
# Test hex with whitespace inside (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0x68 65 6c') # 2.52μs -> 1.74μs (44.6% faster)
def test_decode_hex_with_newline():
# Test hex with newline character (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0x6865\n6c6f') # 2.23μs -> 1.65μs (35.3% faster)
def test_decode_hex_with_leading_trailing_spaces():
# Test hex with leading/trailing spaces (should raise error)
with pytest.raises(binascii.Error):
decode_hex(' 68656c6c6f ') # 2.09μs -> 1.46μs (43.0% faster)
def test_decode_hex_with_special_characters():
# Test hex with special non-hex characters (should raise error)
with pytest.raises(binascii.Error):
decode_hex('0x68$56c6c6f') # 2.31μs -> 1.64μs (41.3% faster)
def test_decode_hex_long_prefix():
# Test hex with more than one "0x" prefix (should treat as part of hex, likely error)
with pytest.raises(binascii.Error):
decode_hex('0x0x68656c6c6f') # 2.35μs -> 1.59μs (48.3% faster)
def test_decode_hex_zero_length():
# Test decoding a zero-length string (should return empty bytes)
codeflash_output = decode_hex('') # 1.58μs -> 988ns (59.7% faster)
def test_decode_hex_prefix_only_uppercase():
# Test decoding only prefix in uppercase (should return empty bytes)
codeflash_output = decode_hex('0X') # 1.90μs -> 1.15μs (65.4% faster)
def test_decode_hex_non_ascii_hex():
# Test decoding a hex string with non-ascii characters (should raise error)
with pytest.raises(UnicodeEncodeError):
decode_hex('0x6865é6c6f') # 3.79μs -> 2.95μs (28.4% faster)
--------------------
Large Scale Test Cases
--------------------
def test_decode_hex_large_even_length():
# Test decoding a large even-length hex string (1000 bytes)
hex_str = '0x' + 'ab' * 1000 # 2000 hex digits, 1000 bytes
expected_bytes = b'\xab' * 1000
codeflash_output = decode_hex(hex_str) # 3.15μs -> 2.42μs (30.1% faster)
def test_decode_hex_large_no_prefix():
# Test decoding a large hex string without prefix
hex_str = 'cd' * 1000
expected_bytes = b'\xcd' * 1000
codeflash_output = decode_hex(hex_str) # 2.45μs -> 1.81μs (35.2% faster)
def test_decode_hex_large_uppercase():
# Test decoding a large uppercase hex string
hex_str = '0X' + 'EF' * 1000
expected_bytes = b'\xef' * 1000
codeflash_output = decode_hex(hex_str) # 2.96μs -> 2.13μs (38.8% faster)
def test_decode_hex_large_mixed_case():
# Test decoding a large mixed-case hex string
hex_str = '0x' + ''.join(['aB' if i % 2 == 0 else 'Cd' for i in range(1000)])
expected_bytes = b''.join([b'\xab' if i % 2 == 0 else b'\xcd' for i in range(1000)])
codeflash_output = decode_hex(hex_str) # 3.00μs -> 2.17μs (38.8% faster)
def test_decode_hex_large_odd_length():
# Test decoding a large odd-length hex string (should raise error)
hex_str = '0x' + 'ab' * 999 + 'a' # 19992+1 = 19992+1 = 19992+1 = 19992+1 = 1999*2+1 = odd
with pytest.raises(binascii.Error):
decode_hex(hex_str) # 2.73μs -> 1.86μs (46.8% faster)
def test_decode_hex_large_non_hex():
# Test decoding a large string with a non-hex character in the middle (should raise error)
hex_str = '0x' + 'ab' * 500 + 'ZZ' + 'cd' * 499
with pytest.raises(binascii.Error):
decode_hex(hex_str) # 3.07μs -> 2.25μs (36.7% faster)
def test_decode_hex_large_with_spaces():
# Test decoding a large string with spaces (should raise error)
hex_str = '0x' + ('ab ' * 500)
with pytest.raises(binascii.Error):
decode_hex(hex_str.strip()) # 2.48μs -> 1.65μs (50.8% faster)
def test_decode_hex_large_with_newline():
# Test decoding a large string with a newline (should raise error)
hex_str = '0x' + ('ab' * 500) + '\n' + ('cd' * 499)
with pytest.raises(binascii.Error):
decode_hex(hex_str) # 2.38μs -> 1.73μs (37.0% faster)
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import binascii
function to test
from typing import Any
imports
import pytest # used for our unit tests
from ccxt.static_dependencies.ethereum.utils.hexadecimal import decode_hex
class HexStr(str):
pass
from ccxt.static_dependencies.ethereum.utils.hexadecimal import decode_hex
unit tests
--- Basic Test Cases ---
def test_decode_hex_basic_lowercase():
# Test decoding a simple lowercase hex string
codeflash_output = decode_hex("68656c6c6f") # 1.65μs -> 1.01μs (63.4% faster)
def test_decode_hex_basic_uppercase():
# Test decoding a simple uppercase hex string
codeflash_output = decode_hex("48656C6C6F") # 1.51μs -> 842ns (78.9% faster)
def test_decode_hex_with_0x_prefix_lowercase():
# Test decoding a hex string with '0x' prefix (lowercase)
codeflash_output = decode_hex("0x68656c6c6f") # 1.93μs -> 1.16μs (66.7% faster)
def test_decode_hex_with_0x_prefix_uppercase():
# Test decoding a hex string with '0X' prefix (uppercase)
codeflash_output = decode_hex("0X68656c6c6f") # 1.88μs -> 1.17μs (60.5% faster)
def test_decode_hex_with_mixed_case():
# Test decoding a hex string with mixed case
codeflash_output = decode_hex("0x48656c6c6F") # 1.79μs -> 1.05μs (70.6% faster)
def test_decode_hex_empty_string():
# Test decoding an empty string
codeflash_output = decode_hex("") # 1.50μs -> 847ns (76.7% faster)
def test_decode_hex_empty_string_with_0x():
# Test decoding an empty string with '0x' prefix
codeflash_output = decode_hex("0x") # 1.80μs -> 1.03μs (75.8% faster)
def test_decode_hex_single_byte():
# Test decoding a single byte
codeflash_output = decode_hex("ff") # 1.54μs -> 930ns (65.4% faster)
def test_decode_hex_single_byte_with_0x():
# Test decoding a single byte with '0x' prefix
codeflash_output = decode_hex("0xff") # 1.88μs -> 1.14μs (65.3% faster)
--- Edge Test Cases ---
def test_decode_hex_non_str_input_int():
# Test that non-str input raises TypeError (int)
with pytest.raises(TypeError):
decode_hex(123) # 995ns -> 747ns (33.2% faster)
def test_decode_hex_non_str_input_bytes():
# Test that non-str input raises TypeError (bytes)
with pytest.raises(TypeError):
decode_hex(b"68656c6c6f") # 874ns -> 716ns (22.1% faster)
def test_decode_hex_non_str_input_list():
# Test that non-str input raises TypeError (list)
with pytest.raises(TypeError):
decode_hex(["68", "65", "6c", "6c", "6f"]) # 1.05μs -> 670ns (57.5% faster)
def test_decode_hex_odd_length_string():
# Test decoding a hex string of odd length should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("abc") # 2.39μs -> 1.55μs (54.3% faster)
def test_decode_hex_odd_length_with_0x():
# Test decoding an odd length hex string with '0x' prefix should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("0xabc") # 2.46μs -> 1.62μs (51.7% faster)
def test_decode_hex_non_hex_characters():
# Test decoding a string with non-hex characters should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("zzzz") # 2.14μs -> 1.48μs (44.3% faster)
def test_decode_hex_non_hex_characters_with_0x():
# Test decoding a string with non-hex characters and '0x' prefix should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("0xzzzz") # 2.40μs -> 1.61μs (48.6% faster)
def test_decode_hex_zero_length_with_0x():
# Test decoding zero-length string with '0x' prefix
codeflash_output = decode_hex("0x") # 1.94μs -> 1.16μs (67.7% faster)
def test_decode_hex_zero_length_with_0X():
# Test decoding zero-length string with '0X' prefix
codeflash_output = decode_hex("0X") # 1.78μs -> 1.09μs (63.4% faster)
def test_decode_hex_leading_trailing_spaces():
# Test decoding a hex string with spaces should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex(" 68656c6c6f ") # 2.00μs -> 1.40μs (42.7% faster)
def test_decode_hex_with_newline():
# Test decoding a hex string with newline should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("68656c6c6f\n") # 2.00μs -> 1.34μs (48.9% faster)
def test_decode_hex_with_special_characters():
# Test decoding a hex string with special characters should raise binascii.Error
with pytest.raises(binascii.Error):
decode_hex("0x68!56c6c6f") # 2.42μs -> 1.55μs (55.9% faster)
def test_decode_hex_with_only_0x():
# Test decoding a string that is only '0x'
codeflash_output = decode_hex("0x") # 1.88μs -> 1.10μs (70.8% faster)
def test_decode_hex_with_only_0X():
# Test decoding a string that is only '0X'
codeflash_output = decode_hex("0X") # 1.82μs -> 1.05μs (73.3% faster)
def test_decode_hex_with_leading_zeros():
# Test decoding a hex string with leading zeros
codeflash_output = decode_hex("000001") # 1.58μs -> 964ns (63.6% faster)
def test_decode_hex_with_leading_zeros_and_0x():
# Test decoding a hex string with leading zeros and '0x' prefix
codeflash_output = decode_hex("0x000001") # 1.91μs -> 1.08μs (75.9% faster)
def test_decode_hex_with_trailing_zeros():
# Test decoding a hex string with trailing zeros
codeflash_output = decode_hex("010000") # 1.45μs -> 922ns (56.7% faster)
--- Large Scale Test Cases ---
def test_decode_hex_large_string():
# Test decoding a large hex string (1000 bytes)
hex_str = "aa" * 1000 # 1000 bytes of 0xaa
expected = b"\xaa" * 1000
codeflash_output = decode_hex(hex_str) # 2.51μs -> 1.85μs (35.2% faster)
def test_decode_hex_large_string_with_0x():
# Test decoding a large hex string (1000 bytes) with '0x' prefix
hex_str = "0x" + ("aa" * 1000)
expected = b"\xaa" * 1000
codeflash_output = decode_hex(hex_str) # 3.07μs -> 2.33μs (32.0% faster)
def test_decode_hex_large_random_bytes():
# Test decoding a large hex string of random bytes
import random
random_bytes = bytes(random.getrandbits(8) for _ in range(1000))
hex_str = random_bytes.hex()
codeflash_output = decode_hex(hex_str) # 2.48μs -> 1.82μs (36.5% faster)
def test_decode_hex_large_random_bytes_with_0x():
# Test decoding a large hex string of random bytes with '0x' prefix
import random
random_bytes = bytes(random.getrandbits(8) for _ in range(1000))
hex_str = "0x" + random_bytes.hex()
codeflash_output = decode_hex(hex_str) # 3.02μs -> 2.20μs (37.5% faster)
def test_decode_hex_large_string_with_uppercase():
# Test decoding a large uppercase hex string (1000 bytes)
hex_str = "FF" * 1000
expected = b"\xff" * 1000
codeflash_output = decode_hex(hex_str) # 2.51μs -> 1.83μs (37.1% faster)
def test_decode_hex_large_string_with_mixed_case():
# Test decoding a large mixed-case hex string (1000 bytes)
hex_str = "".join(["aA" if i % 2 == 0 else "Bb" for i in range(500)])
expected = b"".join([b"\xaa" if i % 2 == 0 else b"\xbb" for i in range(500)])
codeflash_output = decode_hex(hex_str) # 2.07μs -> 1.36μs (52.8% faster)
def test_decode_hex_large_string_with_0x_and_mixed_case():
# Test decoding a large mixed-case hex string (1000 bytes) with '0x' prefix
hex_str = "0x" + "".join(["aA" if i % 2 == 0 else "Bb" for i in range(500)])
expected = b"".join([b"\xaa" if i % 2 == 0 else b"\xbb" for i in range(500)])
codeflash_output = decode_hex(hex_str) # 2.70μs -> 1.86μs (44.7% faster)
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-decode_hex-mhw6e57eand push.