Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 6bf72a0

Browse files
committed
Addressed gemini comments
- Introduced RandomBytesIO for generating random bytes on-the-fly, reducing memory usage during uploads. - Updated upload logic in test_writes.py to utilize RandomBytesIO instead of os.urandom. - Adjusted download logic in test_reads.py to correctly handle byte ranges. - Cleaned up config.py and conftest.py by removing commented-out code and unnecessary lines. - Minor adjustments in config_writes.yaml for clarity.
1 parent e3797e4 commit 6bf72a0

6 files changed

Lines changed: 78 additions & 36 deletions

File tree

tests/perf/microbenchmarks/_utils.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Any, List
22
import statistics
3+
import io
4+
import os
35

46

57
def publish_benchmark_extra_info(
@@ -83,3 +85,67 @@ def publish_benchmark_extra_info(
8385
print(header)
8486
print(row)
8587
print("-" * 125)
88+
89+
class RandomBytesIO(io.RawIOBase):
90+
"""
91+
A file-like object that generates random bytes using os.urandom.
92+
It enforces a fixed size and an upper safety cap.
93+
"""
94+
# 10 GiB default safety cap
95+
DEFAULT_CAP = 10 * 1024 * 1024 * 1024
96+
97+
def __init__(self, size, max_size=DEFAULT_CAP):
98+
"""
99+
Args:
100+
size (int): The exact size of the virtual file in bytes.
101+
max_size (int): The maximum allowed size to prevent safety issues.
102+
"""
103+
if size is None:
104+
raise ValueError("Size must be defined (cannot be infinite).")
105+
106+
if size > max_size:
107+
raise ValueError(f"Requested size {size} exceeds the maximum limit of {max_size} bytes (10 GiB).")
108+
109+
self._size = size
110+
self._pos = 0
111+
112+
def read(self, n=-1):
113+
# 1. Handle "read all" (n=-1)
114+
if n is None or n < 0:
115+
n = self._size - self._pos
116+
117+
# 2. Handle EOF (End of File)
118+
if self._pos >= self._size:
119+
return b""
120+
121+
# 3. Clamp read amount to remaining size
122+
# This ensures we stop exactly at `size` bytes.
123+
n = min(n, self._size - self._pos)
124+
125+
# 4. Generate data
126+
data = os.urandom(n)
127+
self._pos += len(data)
128+
return data
129+
130+
def readable(self):
131+
return True
132+
133+
def seekable(self):
134+
return True
135+
136+
def tell(self):
137+
return self._pos
138+
139+
def seek(self, offset, whence=io.SEEK_SET):
140+
if whence == io.SEEK_SET:
141+
new_pos = offset
142+
elif whence == io.SEEK_CUR:
143+
new_pos = self._pos + offset
144+
elif whence == io.SEEK_END:
145+
new_pos = self._size + offset
146+
else:
147+
raise ValueError(f"Invalid whence: {whence}")
148+
149+
# Clamp position to valid range [0, size]
150+
self._pos = max(0, min(new_pos, self._size))
151+
return self._pos

tests/perf/microbenchmarks/config.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def _get_params() -> Dict[str, List[ReadParameters]]:
7373

7474
# Create a descriptive name for the parameter set
7575
name = f"{pattern}_{bucket_type}_{num_processes}p_{num_coros}c"
76-
# name = f"{workload_name}"
7776

7877
params[workload_name].append(
7978
ReadParameters(
@@ -90,7 +89,6 @@ def _get_params() -> Dict[str, List[ReadParameters]]:
9089
file_size_bytes=file_size_bytes,
9190
)
9291
)
93-
# print(params)
9492
return params
9593

9694

@@ -168,22 +166,3 @@ def get_write_params() -> Dict[str, List[WriteParameters]]:
168166
)
169167
)
170168
return params
171-
172-
173-
if __name__ == "__main__":
174-
import sys
175-
# params = _get_params()
176-
# print("Read params:")
177-
# print('keys (num_workload in params', len(params), sys.getsizeof(params))
178-
# if 'read_seq' in params:
179-
# print(params['read_seq'], len(params['read_seq']))
180-
181-
write_params = get_write_params()
182-
183-
print(write_params)
184-
print("\nWrite params:")
185-
print(
186-
"keys (num_workload in params", len(write_params), sys.getsizeof(write_params)
187-
)
188-
# if 'write_seq' in write_params:
189-
# print(write_params['write_seq'], len(write_params['write_seq']))

tests/perf/microbenchmarks/config_writes.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ common:
33
- "regional"
44
- "zonal"
55
file_sizes_mib:
6-
- 1024 # 1GB
7-
# chunk_sizes_mib: [1, 2, 8, 64, 100, 128]
6+
- 1024 # 1GiB
87
chunk_sizes_mib: [100]
98
rounds: 10
109

tests/perf/microbenchmarks/conftest.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
# from tests.system.conftest import blobs_to_delete
2-
3-
# __all__ = [
4-
5-
# "blobs_to_delete",
6-
# ]
7-
81
import contextlib
92
from typing import Any
103
from tests.perf.microbenchmarks.resource_monitor import ResourceMonitor
@@ -66,8 +59,11 @@ def publish_resource_metrics(benchmark: Any, monitor: ResourceMonitor) -> None:
6659

6760

6861
async def upload_appendable_object(bucket_name, object_name, object_size, chunk_size):
62+
# flush interval set to little over 1GiB to minimize number of flushes.
63+
# this method is to write "appendable" objects which will be used for
64+
# benchmarking reads, hence not concerned performance of writes here.
6965
writer = AsyncAppendableObjectWriter(
70-
AsyncGrpcClient().grpc_client, bucket_name, object_name, writer_options={"FLUSH_INTERVAL_BYTES": 1026 * 1024 * 1024}
66+
AsyncGrpcClient().grpc_client, bucket_name, object_name, writer_options={"FLUSH_INTERVAL_BYTES": 1026 * 1024 ** 2}
7167
)
7268
await writer.open()
7369
uploaded_bytes = 0

tests/perf/microbenchmarks/test_reads.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def download_chunks_using_json(_, json_client, filename, other_params, chunks):
7171
blob = bucket.blob(filename)
7272
start_time = time.monotonic_ns()
7373
for offset, size in chunks:
74-
_ = blob.download_as_bytes(start=offset, end=offset + size)
74+
_ = blob.download_as_bytes(start=offset, end=offset + size - 1)
7575
return (time.monotonic_ns() - start_time) / 1_000_000_000
7676

7777

tests/perf/microbenchmarks/test_writes.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from google.cloud.storage._experimental.asyncio.async_grpc_client import AsyncGrpcClient
1717
from google.cloud.storage._experimental.asyncio.async_appendable_object_writer import AsyncAppendableObjectWriter
1818

19-
from tests.perf.microbenchmarks._utils import publish_benchmark_extra_info
19+
from tests.perf.microbenchmarks._utils import publish_benchmark_extra_info, RandomBytesIO
2020
from tests.perf.microbenchmarks.conftest import publish_resource_metrics
2121
import tests.perf.microbenchmarks.config as config
2222

@@ -63,9 +63,11 @@ def upload_using_json(_, json_client, filename, other_params):
6363
bucket = json_client.bucket(other_params.bucket_name)
6464
blob = bucket.blob(filename)
6565
upload_size = other_params.file_size_bytes
66-
data = os.urandom(upload_size)
67-
68-
blob.upload_from_string(data)
66+
# Don't use BytesIO because it'll report high memory usage for large files.
67+
# `RandomBytesIO` generates random bytes on the fly.
68+
in_mem_file = RandomBytesIO(upload_size)
69+
# data = os.urandom(upload_size)
70+
blob.upload_from_file(in_mem_file)
6971

7072
end_time = time.monotonic_ns()
7173
elapsed_time = end_time - start_time

0 commit comments

Comments
 (0)