Skip to content

Commit 0a3ac38

Browse files
committed
Reduce memory usage of tests
- eth/vm/memory.py:Memory.read() used to return a bytes() initialized with the requested memory, this created an often unnecessary copy of the data. It now returns a memoryview(), which is a python wrapper around a pointer to the raw data.
1 parent 90dc15d commit 0a3ac38

16 files changed

+84
-29
lines changed

eth/precompiles/ecadd.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def ecadd(computation: BaseComputation) -> BaseComputation:
3232
computation.consume_gas(constants.GAS_ECADD, reason='ECADD Precompile')
3333

3434
try:
35-
result = _ecadd(computation.msg.data)
35+
result = _ecadd(computation.msg.data_as_bytes)
3636
except ValidationError:
3737
raise VMError("Invalid ECADD parameters")
3838

eth/precompiles/ecmul.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def ecmul(computation: BaseComputation) -> BaseComputation:
3232
computation.consume_gas(constants.GAS_ECMUL, reason='ECMUL Precompile')
3333

3434
try:
35-
result = _ecmull(computation.msg.data)
35+
result = _ecmull(computation.msg.data_as_bytes)
3636
except ValidationError:
3737
raise VMError("Invalid ECMUL parameters")
3838

eth/precompiles/ecpairing.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
VMError,
2020
)
2121

22+
from eth.typing import (
23+
BytesOrView,
24+
)
25+
2226
from eth.utils.bn128 import (
2327
validate_point,
2428
FQP_point_to_FQ2_point,
@@ -60,7 +64,7 @@ def ecpairing(computation: BaseComputation) -> BaseComputation:
6064
return computation
6165

6266

63-
def _ecpairing(data: bytes) -> bool:
67+
def _ecpairing(data: BytesOrView) -> bool:
6468
exponent = bn128.FQ12.one()
6569

6670
processing_pipeline = (

eth/precompiles/ecrecover.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@
2727

2828
def ecrecover(computation: BaseComputation) -> BaseComputation:
2929
computation.consume_gas(constants.GAS_ECRECOVER, reason="ECRecover Precompile")
30-
raw_message_hash = computation.msg.data[:32]
30+
data = computation.msg.data_as_bytes
31+
raw_message_hash = data[:32]
3132
message_hash = pad32r(raw_message_hash)
3233

33-
v_bytes = pad32r(computation.msg.data[32:64])
34+
v_bytes = pad32r(data[32:64])
3435
v = big_endian_to_int(v_bytes)
3536

36-
r_bytes = pad32r(computation.msg.data[64:96])
37+
r_bytes = pad32r(data[64:96])
3738
r = big_endian_to_int(r_bytes)
3839

39-
s_bytes = pad32r(computation.msg.data[96:128])
40+
s_bytes = pad32r(data[96:128])
4041
s = big_endian_to_int(s_bytes)
4142

4243
try:

eth/precompiles/identity.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ def identity(computation: BaseComputation) -> BaseComputation:
1414

1515
computation.consume_gas(gas_fee, reason="Identity Precompile")
1616

17-
computation.output = computation.msg.data
17+
computation.output = computation.msg.data_as_bytes
1818
return computation

eth/precompiles/modexp.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,14 @@ def modexp(computation: BaseComputation) -> BaseComputation:
125125
"""
126126
https://github.com/ethereum/EIPs/pull/198
127127
"""
128-
gas_fee = _compute_modexp_gas_fee(computation.msg.data)
128+
data = computation.msg.data_as_bytes
129+
130+
gas_fee = _compute_modexp_gas_fee(data)
129131
computation.consume_gas(gas_fee, reason='MODEXP Precompile')
130132

131-
result = _modexp(computation.msg.data)
133+
result = _modexp(data)
132134

133-
_, _, modulus_length = _extract_lengths(computation.msg.data)
135+
_, _, modulus_length = _extract_lengths(data)
134136

135137
# Modulo 0 is undefined, return zero
136138
# https://math.stackexchange.com/questions/516251/why-is-n-mod-0-undefined

eth/typing.py

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757

5858
GenesisDict = Dict[str, Union[int, BlockNumber, bytes, Hash32]]
5959

60+
BytesOrView = Union[bytes, memoryview]
61+
6062
Normalizer = Callable[[Dict[Any, Any]], Dict[str, Any]]
6163

6264
RawAccountDetails = TypedDict('RawAccountDetails',

eth/validation.py

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
UINT_256_MAX,
4141
)
4242

43+
from eth.typing import (
44+
BytesOrView,
45+
)
46+
4347
if TYPE_CHECKING:
4448
from eth.vm.base import BaseVM # noqa: F401
4549

@@ -51,6 +55,14 @@ def validate_is_bytes(value: bytes, title: str="Value") -> None:
5155
)
5256

5357

58+
def validate_is_bytes_or_view(value: BytesOrView, title: str="Value") -> None:
59+
if isinstance(value, (bytes, memoryview)):
60+
return
61+
raise ValidationError(
62+
"{title} must be bytes or memoryview. Got {0}".format(type(value), title=title)
63+
)
64+
65+
5466
def validate_is_integer(value: Union[int, bool], title: str="Value") -> None:
5567
if not isinstance(value, int) or isinstance(value, bool):
5668
raise ValidationError(

eth/vm/computation.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
Halt,
3030
VMError,
3131
)
32+
from eth.typing import (
33+
BytesOrView,
34+
)
3235
from eth.tools.logging import (
3336
ExtendedDebugLogger,
3437
)
@@ -243,12 +246,18 @@ def memory_write(self, start_position: int, size: int, value: bytes) -> None:
243246
"""
244247
return self._memory.write(start_position, size, value)
245248

246-
def memory_read(self, start_position: int, size: int) -> bytes:
249+
def memory_read(self, start_position: int, size: int) -> memoryview:
247250
"""
248-
Read and return ``size`` bytes from memory starting at ``start_position``.
251+
Read and return a view of ``size`` bytes from memory starting at ``start_position``.
249252
"""
250253
return self._memory.read(start_position, size)
251254

255+
def memory_read_bytes(self, start_position: int, size: int) -> bytes:
256+
"""
257+
Read and return ``size`` bytes from memory starting at ``start_position``.
258+
"""
259+
return self._memory.read_bytes(start_position, size)
260+
252261
#
253262
# Gas Consumption
254263
#
@@ -360,7 +369,7 @@ def prepare_child_message(self,
360369
gas: int,
361370
to: Address,
362371
value: int,
363-
data: bytes,
372+
data: BytesOrView,
364373
code: bytes,
365374
**kwargs: Any) -> Message:
366375
"""

eth/vm/logic/context.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def calldataload(computation: BaseComputation) -> None:
4242
"""
4343
start_position = computation.stack_pop(type_hint=constants.UINT256)
4444

45-
value = computation.msg.data[start_position:start_position + 32]
45+
value = computation.msg.data_as_bytes[start_position:start_position + 32]
4646
padded_value = value.ljust(32, b'\x00')
4747
normalized_value = padded_value.lstrip(b'\x00')
4848

@@ -68,7 +68,9 @@ def calldatacopy(computation: BaseComputation) -> None:
6868

6969
computation.consume_gas(copy_gas_cost, reason="CALLDATACOPY fee")
7070

71-
value = computation.msg.data[calldata_start_position: calldata_start_position + size]
71+
value = computation.msg.data_as_bytes[
72+
calldata_start_position: calldata_start_position + size
73+
]
7274
padded_value = value.ljust(size, b'\x00')
7375

7476
computation.memory_write(mem_start_position, size, padded_value)

eth/vm/logic/logging.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def log_XX(computation: BaseComputation, topic_count: int) -> None:
3030
)
3131

3232
computation.extend_memory(mem_start_position, size)
33-
log_data = computation.memory_read(mem_start_position, size)
33+
log_data = computation.memory_read_bytes(mem_start_position, size)
3434

3535
computation.add_log_entry(
3636
account=computation.msg.storage_address,

eth/vm/logic/memory.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def mload(computation: BaseComputation) -> None:
3232

3333
computation.extend_memory(start_position, 32)
3434

35-
value = computation.memory_read(start_position, 32)
35+
value = computation.memory_read_bytes(start_position, 32)
3636
computation.stack_push(value)
3737

3838

eth/vm/logic/sha3.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def sha3(computation: BaseComputation) -> None:
1212

1313
computation.extend_memory(start_position, size)
1414

15-
sha3_bytes = computation.memory_read(start_position, size)
15+
sha3_bytes = computation.memory_read_bytes(start_position, size)
1616
word_count = ceil32(len(sha3_bytes)) // 32
1717

1818
gas_cost = constants.GAS_SHA3WORD * word_count

eth/vm/logic/system.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ def return_op(computation: BaseComputation) -> None:
3232

3333
computation.extend_memory(start_position, size)
3434

35-
output = computation.memory_read(start_position, size)
36-
computation.output = bytes(output)
35+
computation.output = computation.memory_read_bytes(start_position, size)
3736
raise Halt('RETURN')
3837

3938

@@ -42,8 +41,7 @@ def revert(computation: BaseComputation) -> None:
4241

4342
computation.extend_memory(start_position, size)
4443

45-
output = computation.memory_read(start_position, size)
46-
computation.output = bytes(output)
44+
computation.output = computation.memory_read_bytes(start_position, size)
4745
raise Revert(computation.output)
4846

4947

@@ -163,7 +161,9 @@ def __call__(self, computation: BaseComputation) -> None:
163161
computation.stack_push(0)
164162
return
165163

166-
call_data = computation.memory_read(stack_data.memory_start, stack_data.memory_length)
164+
call_data = computation.memory_read_bytes(
165+
stack_data.memory_start, stack_data.memory_length
166+
)
167167

168168
create_msg_gas = self.max_child_gas_modifier(
169169
computation.get_gas_remaining()

eth/vm/memory.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,16 @@ def extend(self, start_position: int, size: int) -> None:
3232
return
3333

3434
size_to_extend = new_size - len(self)
35-
self._bytes.extend(itertools.repeat(0, size_to_extend))
35+
try:
36+
self._bytes.extend(itertools.repeat(0, size_to_extend))
37+
except BufferError:
38+
# we can't extend the buffer (which might involve relocating it) if a
39+
# memoryview (which stores a pointer into the buffer) has been created by
40+
# read() and not released. Callers of read() will never try to write to the
41+
# buffer so we're not missing anything by making a new buffer and forgetting
42+
# about the old one. We're keeping too much memory around but this is still a
43+
# net savings over having read() return a new bytes() object every time.
44+
self._bytes = self._bytes + bytearray(size_to_extend)
3645

3746
def __len__(self) -> int:
3847
return len(self._bytes)
@@ -51,8 +60,14 @@ def write(self, start_position: int, size: int, value: bytes) -> None:
5160
for idx, v in enumerate(value):
5261
self._bytes[start_position + idx] = v
5362

54-
def read(self, start_position: int, size: int) -> bytes:
63+
def read(self, start_position: int, size: int) -> memoryview:
5564
"""
56-
Read a value from memory.
65+
Return a view into the memory
66+
"""
67+
return memoryview(self._bytes)[start_position:start_position + size]
68+
69+
def read_bytes(self, start_position: int, size: int) -> bytes:
70+
"""
71+
Read a value from memory and return a fresh bytes instance
5772
"""
5873
return bytes(self._bytes[start_position:start_position + size])

eth/vm/message.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
from eth.constants import (
66
CREATE_CONTRACT_ADDRESS,
77
)
8+
from eth.typing import (
9+
BytesOrView,
10+
)
811
from eth.validation import (
912
validate_canonical_address,
1013
validate_is_bytes,
14+
validate_is_bytes_or_view,
1115
validate_is_integer,
1216
validate_gte,
1317
validate_uint256,
@@ -31,7 +35,7 @@ def __init__(self,
3135
to: Address,
3236
sender: Address,
3337
value: int,
34-
data: bytes,
38+
data: BytesOrView,
3539
code: bytes,
3640
depth: int=0,
3741
create_address: Address=None,
@@ -51,7 +55,7 @@ def __init__(self,
5155
validate_uint256(value, title="Message.value")
5256
self.value = value
5357

54-
validate_is_bytes(data, title="Message.data")
58+
validate_is_bytes_or_view(data, title="Message.data")
5559
self.data = data
5660

5761
validate_is_integer(depth, title="Message.depth")
@@ -100,3 +104,7 @@ def storage_address(self, value: Address) -> None:
100104
@property
101105
def is_create(self) -> bool:
102106
return self.to == CREATE_CONTRACT_ADDRESS
107+
108+
@property
109+
def data_as_bytes(self) -> bytes:
110+
return bytes(self.data)

0 commit comments

Comments
 (0)