109 lines
2.9 KiB
Python
109 lines
2.9 KiB
Python
import json
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
import numpy as np
|
|
|
|
import ray
|
|
import ray._private.worker
|
|
|
|
NUM_WORKERS = 10
|
|
OBJECT_SIZE = 1024 * 1024 # 1 MiB, above the 100 KB inlining threshold
|
|
|
|
|
|
@ray.remote(num_cpus=1)
|
|
def produce_block():
|
|
return np.zeros(OBJECT_SIZE, dtype=np.uint8)
|
|
|
|
|
|
@ray.remote(num_cpus=1)
|
|
def consume_block(block_ref):
|
|
return len(block_ref)
|
|
|
|
|
|
def test_callback_pipeline(num_blocks, timeout_s=60):
|
|
core_worker = ray._private.worker.global_worker.core_worker
|
|
|
|
latencies = []
|
|
drop_times = {}
|
|
lock = threading.Lock()
|
|
done = threading.Event()
|
|
|
|
def on_freed(id_bytes):
|
|
with lock:
|
|
latencies.append(time.perf_counter() - drop_times[id_bytes])
|
|
if len(latencies) == num_blocks:
|
|
done.set()
|
|
|
|
refs = [
|
|
produce_block.options(scheduling_strategy="SPREAD").remote()
|
|
for _ in range(num_blocks)
|
|
]
|
|
ray.wait(refs, num_returns=len(refs))
|
|
|
|
# live_refs keeps each block ref alive until its consumer completes.
|
|
live_refs = {}
|
|
for ref in refs:
|
|
assert core_worker.add_object_out_of_scope_callback(ref, on_freed)
|
|
consumer = consume_block.remote(ref)
|
|
live_refs[consumer] = ref
|
|
del refs
|
|
|
|
# Release each ref as its consumer completes.
|
|
pending = list(live_refs.keys())
|
|
while pending:
|
|
done_list, pending = ray.wait(pending, num_returns=1)
|
|
for consumer in done_list:
|
|
ref = live_refs.pop(consumer)
|
|
drop_times[ref.binary()] = time.perf_counter()
|
|
del ref
|
|
|
|
if not done.wait(timeout=timeout_s):
|
|
raise TimeoutError(
|
|
f"Only {len(latencies)}/{num_blocks} callbacks fired within {timeout_s}s"
|
|
)
|
|
|
|
latencies.sort()
|
|
p95 = latencies[int(len(latencies) * 0.95)]
|
|
print(f" {num_blocks} blocks: p95={p95:.4f}s")
|
|
return p95
|
|
|
|
|
|
ray.init(address="auto")
|
|
|
|
# Warm up gRPC connections and worker pools.
|
|
ray.get(
|
|
[
|
|
produce_block.options(scheduling_strategy="SPREAD").remote()
|
|
for _ in range(NUM_WORKERS)
|
|
]
|
|
)
|
|
|
|
p95_100 = test_callback_pipeline(100)
|
|
p95_1k = test_callback_pipeline(1000)
|
|
|
|
print("\nSummary:")
|
|
print(f" 100 blocks: p95={p95_100:.4f}s")
|
|
print(f" 1k blocks: p95={p95_1k:.4f}s")
|
|
|
|
if "TEST_OUTPUT_JSON" in os.environ:
|
|
with open(os.environ["TEST_OUTPUT_JSON"], "w") as out_file:
|
|
results = {
|
|
"p95_100": p95_100,
|
|
"p95_1k": p95_1k,
|
|
"perf_metrics": [
|
|
{
|
|
"perf_metric_name": "callback_p95_latency_100_blocks_s",
|
|
"perf_metric_value": p95_100,
|
|
"perf_metric_type": "LATENCY",
|
|
},
|
|
{
|
|
"perf_metric_name": "callback_p95_latency_1k_blocks_s",
|
|
"perf_metric_value": p95_1k,
|
|
"perf_metric_type": "LATENCY",
|
|
},
|
|
],
|
|
}
|
|
json.dump(results, out_file)
|