实验 1:第一个 Ray Task
这个实验的目标不是写复杂程序,而是观察 Ray 最核心的行为:远程调用立即返回 ObjectRef,真正的结果要通过 ray.get 获取。
准备环境
bash
uv venv
uv pip install -r examples/ray_demos/requirements.txt运行示例
bash
uv run python examples/ray_demos/01_tasks.py代码:
python
import time
import ray
ray.init()
@ray.remote
def slow_square(x: int) -> int:
time.sleep(0.5)
return x * x
start = time.perf_counter()
refs = [slow_square.remote(i) for i in range(8)]
print(ray.get(refs))
print(f"elapsed={time.perf_counter() - start:.2f}s")你应该看到什么
结果应该类似:
text
[0, 1, 4, 9, 16, 25, 36, 49]
elapsed=...耗时通常明显小于 8 * 0.5s,因为 8 个任务不是串行执行。
逐行解释
python
ray.init()启动或连接 Ray Runtime。单机模式下,它会在本机启动必要组件。
python
@ray.remote
def slow_square(x: int) -> int:
...这一步不是执行函数,而是把函数注册为远程函数。包装后的 slow_square 拥有 .remote() 方法。
python
refs = [slow_square.remote(i) for i in range(8)]每次 .remote() 都提交一个任务,并立即返回 ObjectRef。
python
print(ray.get(refs))ray.get 等待所有引用 ready,然后返回真实值列表。
串行陷阱
把代码改成这样:
python
results = []
for i in range(8):
results.append(ray.get(slow_square.remote(i)))这会慢很多。原因是你每提交一个任务就立刻等待结果,Driver 没有机会把多个任务同时提交给运行时。
和 Mini Ray 对照
运行教学版:
bash
uv run python examples/mini_ray_runtime/demos/01_tasks.pyMini Ray 的输出也会包含一个 metrics 字典,帮助你看到任务提交数、完成数和对象数。
小练习
- 把
time.sleep(0.5)改成time.sleep(0.05),观察并行收益是否变小。 - 把任务数量从 8 改成 100,观察调度开销和总耗时。
- 给远程函数加上
@ray.remote(num_cpus=2),看本机并发数量如何变化。
常见错误
| 错误 | 原因 | 处理 |
|---|---|---|
ModuleNotFoundError: ray | 没有安装 Ray | 执行 uv pip install -r examples/ray_demos/requirements.txt |
| 程序没有明显加速 | 任务太小或在循环里 ray.get | 增大任务粒度,先提交再统一等待 |
| CPU 占用不高 | 任务是 sleep 或 I/O 等待 | 改成 CPU 计算任务观察差异 |