使用性能测试 API#

本指南面向需要通过 Python 运行受支持的性能测试、读取带类型的结果,或添加性能测试数据生成器的自动化开发者。日常测试先参阅 运行性能测试 中的命令行工作流和结果解读。要单独测量某个资产方法、缓存属性或传感器更新,使用 编写微基准测试 。

运行一个工作流#

对受支持工作流使用带类型的请求。它采用与命令行相同的启动、任务、计时、数据结构和输出行为。以下运行时示例在预热 50 步后测量 1000 步,禁用可视化器,并同时输出稳定数据结构和便于阅读的摘要:

from pathlib import Path

from isaaclab.benchmark import (
    BenchmarkLauncherConfig,
    BenchmarkOutputConfig,
    BenchmarkRuntimeRequest,
    run_runtime_benchmark,
)

result = run_runtime_benchmark(
    BenchmarkRuntimeRequest(
        task="Isaac-Cartpole-Direct",
        num_envs=4096,
        num_steps=1000,
        warmup_steps=50,
        seed=42,
        presets=("newton_mjwarp",),
        output=BenchmarkOutputConfig(
            path=Path("results/runtime"),
            formatters=("schema", "summary"),
        ),
        launcher=BenchmarkLauncherConfig(visualizers=()),
    )
)

print(f"Total FPS: {result.bundle.runtime.total_fps.mean:,.0f}")
for output_path in result.output_paths:
    print(output_path)

通过 Isaac Lab Python 包装脚本运行该文件:

./isaaclab.sh -p runtime_benchmark.py

命令打印摘要报告。 result.output_paths 中的路径指向已写入的数据结构 JSON 和摘要 JSON。自动化中应使用这些路径,而不是自行重建带时间戳的文件名。

当任务的 benchmark_mode 不为 None ,且设置 ISAACLAB_RENDER_PROFILE=1 或 ISAACLAB_PHYSICS_PROFILE=1 时,运行时工作流还会在 result.bundle.extra 中提供标量性能摘要: physics_mean_ms 、 physics_std_ms 、 physics_max_ms 、 physics_calls 及对应的 render_* 键。它们报告采样中每次调用的平均耗时、标准差、最大耗时 [ms] 和调用次数。禁用的范围不会生成对应键。这些摘要会写入 schema 和 OmniPerf 输出,数据结构版本仍为 1.4。按顺序保留的原始样本位于 <output_path>/profile_timings.json ,供本地分析使用。

选择请求#

根据要测量的问题,选择对应请求及专用运行器。如果通用分派更方便, run_benchmark() 可接受任意请求类型。

要回答的问题

请求

运行器

环境步进能力

BenchmarkRuntimeRequest

run_runtime_benchmark()

应用与任务启动

BenchmarkStartupRequest

run_startup_benchmark()

强化学习训练与学习过程

BenchmarkTrainingRequest

run_training_benchmark()

已训练策略回放

BenchmarkPlayRequest

run_play_benchmark()

以下示例复用运行时工作流的输出和启动器对象。

分析启动性能

启动性能分析记录每个阶段的实际耗时,以及耗时最多的函数:

from pathlib import Path

from isaaclab.benchmark import (
    BenchmarkLauncherConfig,
    BenchmarkOutputConfig,
    BenchmarkStartupRequest,
    run_startup_benchmark,
)

result = run_startup_benchmark(
    BenchmarkStartupRequest(
        task="Isaac-Cartpole-Direct",
        num_envs=4096,
        top_n=20,
        presets=("newton_mjwarp",),
        output=BenchmarkOutputConfig(
            path=Path("results/startup"),
            formatters=("schema", "summary"),
        ),
        launcher=BenchmarkLauncherConfig(visualizers=()),
    )
)
print(f"Environment creation: {result.bundle.phases['env_creation'].total_time_s:.3f} s")
运行训练

训练返回所选强化学习库生成的检查点:

from pathlib import Path

from isaaclab.benchmark import (
    BenchmarkLauncherConfig,
    BenchmarkOutputConfig,
    BenchmarkTrainingRequest,
    run_training_benchmark,
)

result = run_training_benchmark(
    BenchmarkTrainingRequest(
        backend="rsl_rl",
        task="Isaac-Cartpole-Direct",
        num_envs=4096,
        max_iterations=500,
        warmup_steps=50,
        seed=42,
        presets=("newton_mjwarp",),
        output=BenchmarkOutputConfig(
            path=Path("results/training"),
            formatters=("schema", "summary"),
        ),
        launcher=BenchmarkLauncherConfig(visualizers=()),
    )
)
print(f"Collection FPS: {result.bundle.runtime.collection_fps.mean:,.0f}")
print(f"Checkpoint: {result.bundle.checkpoint_path}")
运行回放

在 新进程 中,将已保存的 checkpoint_path 传给回放请求:

from pathlib import Path

from isaaclab.benchmark import (
    BenchmarkLauncherConfig,
    BenchmarkOutputConfig,
    BenchmarkPlayRequest,
    run_play_benchmark,
)

checkpoint = Path("checkpoint.txt").read_text().strip()
result = run_play_benchmark(
    BenchmarkPlayRequest(
        backend="rsl_rl",
        task="Isaac-Cartpole-Direct",
        checkpoint=checkpoint,
        num_envs=4096,
        num_steps=1000,
        warmup_steps=50,
        seed=42,
        presets=("newton_mjwarp",),
        output=BenchmarkOutputConfig(
            path=Path("results/play"),
            formatters=("schema", "summary"),
        ),
        launcher=BenchmarkLauncherConfig(visualizers=()),
    )
)
print(f"Environment + inference FPS: {result.bundle.runtime.collection_fps.mean:,.0f}")

配置请求#

所有请求共享任务标识、可选环境数量和随机种子、任务配置、输出配置与启动器配置。

共享请求字段#

字段

用途

task

已注册的 Gym 任务标识,例如 Isaac-Cartpole-Direct 。

num_envs 和 seed

环境数量与随机种子。比较时,两者都应保持固定。

presets

带类型的任务预设,例如 ("newton_mjwarp",) 。

hydra_args

额外的 Hydra 覆盖项,以字符串元组表示。

output

BenchmarkOutputConfig ,控制输出目录和格式化器集合。

launcher

BenchmarkLauncherConfig ,控制设备、相机、可视化器、Kit、实时串流和日志选项。

工作流专用字段#

工作流程

字段

含义

运行时

num_steps, warmup_steps

测量的环境步数,以及之前不计入测量的步数。

启动

top_n, whitelist_config

每个阶段保留的分析函数数量,以及可选过滤条件。

训练

backend, max_iterations, warmup_steps

强化学习库、学习迭代次数,以及不计入环境步计时的初始步数。

回放

backend, checkpoint, num_steps, warmup_steps

强化学习库、策略、测量的推理步数,以及不计入测量的初始步数。

训练还提供学习、视频、传感器采集与收敛相关选项。训练和回放也提供 backend_args ,用于确实只属于某个强化学习库的选项。受支持选项应优先使用带类型的字段、预设和 hydra_args 。 backend_args 绕过通用请求约定。完整字段列表见 isaaclab.benchmark API 参考。

空的 visualizers 元组显式禁用全部可视化器; None 保留任务与环境的默认设置。启用相机、渲染、实时串流、确定性行为或动画录制会改变负载,比较的运行必须保持这些设置一致。

选择输出格式#

在 BenchmarkOutputConfig 中选择一个或多个格式化器名称:

格式化器

约定

schema

写入稳定的带类型结果包,适合程序化比较和长期结果存储。

summary

打印精简的终端报告,并写入扁平指标 JSON 文件。

json

写入更底层的阶段、测量与元数据表示。

osmo

为 Osmo 使用方写入按阶段组织的 KPI 文件。

omniperf

写入 OmniPerf KPI 表示。

需要审阅或发布的结果应使用 ("schema", "summary") 。摘要提供即时反馈,schema 保留供分析使用的带类型数据。使用多个格式化器时,各输出文件名包含格式化器名称,因此 JSON 输出不会互相覆盖。

读取结果#

每个工作流都返回 BenchmarkResult ,包含两个成员:

应读取带类型的字段,而不是解析终端文本,或依赖 bundle.extra 中的键:

# Runtime or play
total_fps = result.bundle.runtime.total_fps.mean
step_fps = result.bundle.runtime.environment_step_timing.environment_step_fps.mean

# Startup
scene_creation_s = result.bundle.phases["env_creation"].total_time_s

# Training
collection_fps = result.bundle.runtime.collection_fps.mean
final_reward = result.bundle.learning.reward.final_raw
checkpoint = result.bundle.checkpoint_path

# Play, when at least one episode completed
mean_return = result.bundle.reward.mean if result.bundle.reward is not None else None

通用的 run 、 versions 和 hardware 字段保存比较所需的上下文。 runtime 区分采集吞吐量、总吞吐量、启动时间和环境步计时。测量定义见 运行性能测试 ;所有数据结构字段见公共 API 参考。

处理错误与进程生命周期#

如果工作流解析器拒绝带类型请求,API 抛出 ValueError ;如果工作流退出却没有返回结果,则抛出 RuntimeError 。无效输出配置也会报错,例如格式化器元组为空或包含未知名称。

每个工作流应在独立进程中运行,使各性能测试拥有完整、独立的仿真器生命周期,避免状态在工作流之间遗留,也与命令行执行模型一致。进程启动时间与稳态吞吐量分别报告。开始回放前,先保存 result.bundle.checkpoint_path 。

警告

measure_synchronized_step_breakdown=True 在环境与仿真步边界插入设备同步。这会使计算串行化,改变被测调度,Newton 尤其明显。该次运行的所有速率均属诊断数据,不能作为吞吐量报告。 SimulationContext.step() 之外的时间包含必要的动作、执行器、状态、管理器、重置、包装器和同步工作,不能直接视为可移除的 Isaac Lab 开销。

添加自定义数据生成器#

如果数据生成器的生命周期无法用受支持的工作流表示,可使用 BaseIsaacLabBenchmark 。框架提供阶段、带类型的测量与元数据记录、系统记录器和格式化器,但计时边界必须由你定义。

测量与元数据类型#

类型

用途

SingleMeasurement

带有单位的单个标量,例如总时长或完成的操作数。

StatisticalMeasurement

重复样本的均值、标准差、样本数和单位。

BooleanMeasurement

状态或正确性结果。

DictMeasurement

非标量样本的结构化测量数据。

ListMeasurement

保留的样本序列或学习序列。

StringMetadata, IntMetadata, FloatMetadata, DictMetadata

比较测量结果所需的配置与身份信息。

以下可执行示例测量 CPU 负载,记录样本分布与配置,更新系统记录器,并确保最终完成性能测试的收尾:

import statistics
import time

from isaaclab.benchmark import (
    BaseIsaacLabBenchmark,
    IntMetadata,
    StatisticalMeasurement,
)

num_iterations = 100
benchmark = BaseIsaacLabBenchmark(
    benchmark_name="custom_cpu_workload",
    formatter_type=["json", "summary"],
    output_path="results/custom",
    output_prefix="custom_cpu_workload",
    use_recorders=True,
    workflow_metadata={
        "metadata": [{"name": "num_iterations", "data": num_iterations}]
    },
)

output_paths = ()
try:
    for _ in range(10):
        sum(value * value for value in range(10_000))

    samples_ms = []
    for _ in range(num_iterations):
        start_ns = time.perf_counter_ns()
        sum(value * value for value in range(10_000))
        samples_ms.append((time.perf_counter_ns() - start_ns) / 1.0e6)

    benchmark.add_measurement(
        "workload",
        measurement=StatisticalMeasurement(
            name="duration",
            mean=statistics.fmean(samples_ms),
            std=statistics.stdev(samples_ms),
            n=len(samples_ms),
            unit="ms",
        ),
        metadata=IntMetadata(name="warmup_iterations", data=10),
    )
    benchmark.update_manual_recorders()
finally:
    output_paths = benchmark.finalize()

for output_path in output_paths:
    print(output_path)

选择有意义的阶段名,并明确单位。采样前先预热,报告均值与标准差时同时报告样本数。对于 GPU 计算,说明指标是提交延迟,还是设备同步后的完成延迟,并有意识地选择同步边界。

自定义数据生成器创建稳定工作流结果包时,应先调用 attach_bundle(bundle) ,再调用 finalize() ,并选择 schema 。否则,使用底层 json 表示。 finalize() 停止可选的 Kit 帧耗时记录器,收集记录器数据,写入所有选定格式,并返回写入路径。

use_recorders=True (默认值)时,基类采集 CPU、GPU、内存及版本元数据。在 finalize() 前至少调用一次 update_manual_recorders() ;长时间负载需要当前利用率样本时,也应在运行中更新。

只有 Kit 应用正在运行,且负载需要物理、渲染、应用或 GPU 帧耗时数据时,才设置 frametime_recorders=True 。框架启用可用的 Isaac Sim 性能测试服务,并跳过不可用记录器。帧耗时插桩本身也是负载的一部分,比较时应保持设置不变。

选择底层运行器#

MethodBenchmarkRunner 在不同输入模式、预热步数和实例数量下,重复运行某个方法或属性。使用 measure_latency() 定义成对的主机提交与设备同步计时边界;使用 LatencyBenchmarkRunner 报告这些结构化延迟样本。这些单项结果都不能预测端到端环境或训练吞吐量。命令矩阵与扩展协议见 编写微基准测试 。

故障排除#

通过 ./isaaclab.sh -p 运行脚本,确保 Isaac Lab 和仿真器 Python 环境已启用。如果缺少输出,检查返回的 output_paths ,确认自定义数据生成器调用了 finalize() 。格式化器名称使用小写且区分大小写,有效名称为 schema 、 summary 、 json 、 osmo 和 omniperf 。

缺少 GPU 元数据通常表示 nvidia-smi 或 CUDA 设备发现不可用。缺少 Kit 帧耗时测量,则可能是对应性能测试服务不存在,这并不影响非帧耗时阶段的有效性。

新增受支持的端到端工作流时,应保持带类型请求的构建、分派、schema 输出、摘要输出及命令行行为一致。对于单项操作,先参照 编写微基准测试 ,再添加新运行器或计时约定。