Files
2026-07-13 13:17:40 +08:00

191 lines
5.9 KiB
Python

import os
import resource
from typing import List
import traceback
import numpy as np
import psutil
from benchmark import Benchmark
import ray
from ray._private.internal_api import memory_summary
from ray.data._internal.util import _check_pyarrow_version, GiB
from ray.data.block import Block, BlockMetadata
from ray.data.context import DataContext
from ray.data.datasource import Datasource, ReadTask
class RandomIntRowDatasource(Datasource):
"""An example datasource that generates rows with random int64 keys and a
row of the given byte size.
Examples:
>>> source = RandomIntRowDatasource()
>>> ray.data.read_datasource(source, n=10, row_size_bytes=2).take()
... {'c_0': 1717767200176864416, 'c_1': b"..."}
... {'c_0': 4983608804013926748, 'c_1': b"..."}
"""
def prepare_read(
self, parallelism: int, n: int, row_size_bytes: int
) -> List[ReadTask]:
_check_pyarrow_version()
import pyarrow
read_tasks: List[ReadTask] = []
block_size = max(1, n // parallelism)
row = np.random.bytes(row_size_bytes)
schema = pyarrow.schema(
[
pyarrow.field("c_0", pyarrow.int64()),
# NOTE: We use fixed-size binary type to avoid Arrow (list) offsets
# overflows when using non-fixed-size data-types (like string,
# binary, list, etc) whose size exceeds int32 limit (of 2^31-1)
pyarrow.field("c_1", pyarrow.binary(row_size_bytes)),
]
)
def make_block(count: int) -> Block:
return pyarrow.Table.from_arrays(
[
np.random.randint(
np.iinfo(np.int64).max, size=(count,), dtype=np.int64
),
[row for _ in range(count)],
],
schema=schema,
)
i = 0
while i < n:
count = min(block_size, n - i)
meta = BlockMetadata(
num_rows=count,
size_bytes=count * (8 + row_size_bytes),
input_files=None,
exec_stats=None,
)
read_tasks.append(
ReadTask(
lambda count=count: [make_block(count)],
meta,
schema=schema,
)
)
i += block_size
return read_tasks
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--num-partitions", help="number of partitions", default="50", type=str
)
parser.add_argument(
"--partition-size",
help="partition size (bytes)",
default="200e6",
type=str,
)
parser.add_argument(
"--shuffle", help="shuffle instead of sort", action="store_true"
)
# Use 100-byte records to approximately match Cloudsort benchmark.
parser.add_argument(
"--row-size-bytes",
help="Size of each row in bytes.",
default=100,
type=int,
)
parser.add_argument("--use-polars-sort", action="store_true")
parser.add_argument("--limit-num-blocks", type=int, default=None)
args = parser.parse_args()
if args.use_polars_sort and not args.shuffle:
print("Using polars for sort")
ctx = DataContext.get_current()
ctx.use_polars_sort = True
ctx = DataContext.get_current()
if args.limit_num_blocks is not None:
DataContext.get_current().set_config(
"debug_limit_shuffle_execution_to_num_blocks", args.limit_num_blocks
)
num_partitions = int(args.num_partitions)
partition_size = int(float(args.partition_size))
print(
f"Dataset size: {num_partitions} partitions, "
f"{partition_size / GiB}GB partition size, "
f"{num_partitions * partition_size / GiB}GB total"
)
def run_benchmark(args):
# Override target max-block size to avoid creating too many blocks
DataContext.get_current().target_max_block_size = 1 * GiB
source = RandomIntRowDatasource()
# Each row has an int64 key.
num_rows_per_partition = partition_size // (8 + args.row_size_bytes)
ds = ray.data.read_datasource(
source,
override_num_blocks=num_partitions,
n=num_rows_per_partition * num_partitions,
row_size_bytes=args.row_size_bytes,
)
if args.shuffle:
ds = ds.random_shuffle()
else:
ds = ds.sort(key="c_0")
exc = None
try:
ds = ds.materialize()
except Exception as e:
exc = e
ds_stats = ds.stats()
# TODO(swang): Add stats for OOM worker kills. This is not very
# convenient to do programmatically right now because it requires
# querying Prometheus.
print("==== Driver memory summary ====")
maxrss = int(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * 1e3)
print(f"max: {maxrss / 1e9}/GB")
process = psutil.Process(os.getpid())
rss = int(process.memory_info().rss)
print(f"rss: {rss / 1e9}/GB")
try:
print(memory_summary(stats_only=True))
except Exception:
print("Failed to retrieve memory summary")
print(traceback.format_exc())
print("")
if ds_stats is not None:
print(ds_stats)
results = {
"num_partitions": num_partitions,
"partition_size": partition_size,
"peak_driver_memory": maxrss,
}
# Wait until after the stats have been printed to raise any exceptions.
if exc is not None:
print(results)
raise exc
return results
benchmark = Benchmark()
benchmark.run_fn("main", run_benchmark, args)
benchmark.write_result()
ray.timeline("dump.json")