FIDL 基准评分准则

构建器、编码解码微基准

每个绑定都必须实现构建器、编码和解码基准。可以对这些操作的子步骤或变体进行基准测试,但应该有一个基准符合以下绑定-绑定比较要求。

Builder

此基准测试会填充要传递给编码器的对象的绑定表示法。有时可以采用多种方式构建这些对象,一般准则是使用最快的方法构建用户仍然自然的对象。您可以针对不同的构建器方法记录多个值,但应选择一个作为主要值进行比较。

初始设置

设置时间应从构建时间中排除。它应仅包含构建所需的最少步骤。

句柄创建被视为设置时间,不包含在构建器基准测试中。

析构函数

对于构建流程需要创建的对象,其任何析构函数都应包含在该时间中。任何其他析构函数(例如设置步骤中的析构函数)都应从构建器时间中排除。

虽然句柄是在设置期间创建的,但句柄会被构建对象使用,并且在清理构建的对象时将调用析构函数。

编码

Encode 接受构建器阶段构建的对象,并将其编码为传输格式的字节和句柄。按照 FIDL 传输格式的规定,编码必须验证消息。在此基准测试期间分配的任何缓冲区都应为绑定所需的最小大小。编码应尽可能重复使用并遵循与实际绑定代码完全相同的步骤。

初始设置

设置时间应从编码时间中排除。它应仅包含编码所需的最少步骤。

析构函数

该时间应包含编码过程中需要创建的对象的所有析构函数。应排除 build 或设置的析构函数,包括在 build 基准测试期间测量的已构建对象的析构函数。这意味着,句柄创建时间和销毁时间都不包含在基准中。

解码

Decode 获取已编码的字节,然后对其进行处理,并将其解码到适当的绑定对象中。按照 FIDL 有线格式的规定,解码必须验证消息。在此基准测试期间分配的任何缓冲区都应为绑定所需的最小大小。解码时应尽可能重复使用并遵循与实际绑定代码完全相同的步骤。

初始设置

设置时间应从构建时间中排除。它应仅包含构建所需的最少步骤。

析构函数

解码过程中需要创建的对象的任何析构函数都应包含在该时间中。不应包含设置、构建或编码过程中的任何其他析构函数。

测量

对于每个基准,我们想要记录:

  • 实际用时:实际用时,以纳秒 (ns) 为单位
  • 分配:堆分配数(计数)
  • AllocationsBytes:堆分配的字节总数(以字节为单位)
  • StackDepth:最大堆栈深度(以字节为单位),即起始堆栈大小中的最低点与达到的最大堆栈大小之间的差值

WallTime 将是大多数优化措施的主要焦点,因此它的优先级最高。不过,WallTime 受堆分配数量的影响,尤其是意外分配,因此堆分配是次要焦点。除了优化 WallTime 外,深层堆栈对于 Rust 和 C++ 可能也是一个问题,因此在这些语言中,还应跟踪 FIDL 对堆栈深度的贡献。

基准套件

基准测试套件中的基准应易于描述、理解和讨论。它们通常应分为两类:

  • 有条不紊地创建的合成基准。这些特征应分离出特定特征或特征组合,以针对绑定中假设的缺陷选择。应选择基准的参数(大小即元素数量),以便与其他基准保持一致,以方便进行比较。

合成基准不应是特征的任意组合,可能很难确定它们衡量的内容、是否值得优化,或者衡量结果对实际性能的影响。

合成基准的示例包括包含 16、32、256、1024 个基元字段的表、大小为 16、32、256、1024 的字节矢量,以及具有交替对或 uint8、uint64 的结构体。

  • 基于实际 fidl 类型的基准。这些基准用于衡量实际性能。这些指标对于前瞻性优化和跟踪回归都很有用。

回归基准的示例包括 fuchsia.io/File.ReadAt 响应或 fuchsia.posix.socket/DatagramSocket.SendMsg 请求。

GIDL 生成

GIDL 生成应该用于根据一组标准化基准生成每个绑定的基准测试代码。

针对所有基准的要求

Chromeperf 集成

所有基准测试都应导出到 chromeperf 并使用 test_suite:fuchsia.fidl_microbenchmarks。

Builder / 编码 / 解码基准的 Chromeperf 路径

  • 构建器基准路径 [Binding Flavour]/Builder/[Benchmark Path]/[Measurement]
  • 对基准路径 [Binding Flavour]/Encode/[Benchmark Path]/[Measurement] 进行编码
  • 解码基准路径 [Binding Flavour]/Decode/[Benchmark Path]/[Measurement]

示例:

Go/Builder/ByteArray/16/WallTime

绑定名称是以下之一:LLCPPHLCPPRustGoDart(区分大小写)

基准路径是用于标识特定基准的字符串。它们可以包含斜杠,例如“ByteArray/1”。每个单词都采用大写驼峰式大小写。基准测试的名称及其部分名称应为单数,例如 ManyStructField(而不是 ManyStructFields)。

测量名称为以下之一:WallTimeAllocationsAllocatedBytesStackDepth(区分大小写)

其他基准的 Chromeperf 路径

结构为 [Overall Category]/[Specific Benchmark]/[Subbenchmarks]/[Measurements]

一般而言,从最不具体到较具体的从左向右移动。

一些详细信息:

  • 如果基准专用于某个绑定,则应在路径中为其添加 [Binding Flavour]/ 前缀(例如 Go/)。如果基准与特定绑定无关,则不应使用任何绑定前缀(例如 Count/Memcpy/)。如果基准性能绑定变种有一个实例,则应以绑定变种为前缀。
  • 子基准(例如尺寸)显示在主基准之后,按重要性从高到低的顺序从左到右排列。
  • 子步骤显示在测量结果之前(C++ perftest 结构要求)。
  • 如果基准可能具有多种衡量类型,则应指定衡量类型。否则,可以省略。
  • 当基准包含参数时,如果参数名称不明确或有多个参数,则应指定参数名称。如果该参数很明显,则可以省略。参数名称应为值的前缀,采用大写驼峰式大小写形式(例如 Len256、Concurrency100)

示例:

  • LLCPP/Encode/ByteArray/16/Step.Encode/WallTime
    • 从绑定开始,然后是基准类型(编码)
    • 基准名称后跟尺寸和子基准名称 (16)
    • 后跟子步骤名称
    • 最后,还有一个衡量指标
  • Go/Roundtrip/AsciiString/Len256/Concurrency100
    • 以绑定开头,然后是基准类型(往返)
    • 基准测试名称后跟两个已命名的子基准参数
    • 不进行衡量,因为预计只会衡量实际用时
  • Memcpy/ByteArray/16
    • 从基准类型开始。无绑定,因为不适用
    • 后跟基准和子基准名称
    • 未进行衡量,因为预计只会衡量实际用时。

文件路径

基准应在 src/tests/benchmarks/fidl 中创建。如果基准代码的一部分必须存在于其他位置,则应设法在 src/tests/benchmarks/fidl 中创建可执行文件和构建目标(请参阅 dart 中的封装容器)。

Builder/编码/解码基准可以存在于绑定专用目录 src/tests/benchmarks/fidl/{go,rust,dart,hlcpp,llcpp} 中。

其他基准应放置在与基准类型对应的子文件夹中,例如 src/tests/benchmarks/fidl/roundtrip。在极少数情况下,如果需要访问生成的特定类型,则可以将这些类型放在其他位置(例如,在 llcpp 内访问 memcpy benchmark 以访问 llcpp 类型以测试大小正确的 memcpy)。这应被视为技术债务,并最终移至专门的基准和文件夹。