FIDL 基准评分准则

构建器、编码解码微基准

每个绑定都必须实现 Builder、Encode 和 Decode 基准。对这些子步骤或变体进行基准比较是可以的,但应有一个基准符合以下要求,以便进行绑定到绑定的比较。

Builder

此基准会填充将传递给编码器的对象的绑定表示法。有时,构建这些对象的方法可能不止一种,一般准则是使用最快的对象构建方法,同时仍要让用户感觉自然。可以针对不同的 build 技术记录多个值,但应选择一个作为比较的主要值。

设置

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

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

析构函数

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

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

编码

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

设置

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

析构函数

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

Decode

解码会获取编码后的字节和句柄,并将其解码为相应的绑定对象。如 FIDL 有线格式中所指定,解码必须验证消息。在此基准测试期间分配的任何缓冲区都应是绑定所需的最小大小。解码应尽可能重用并遵循与实际绑定代码完全相同的步骤。

设置

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

析构函数

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

测量结果

对于每个基准,我们希望记录以下信息:

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

WallTime 将是大多数优化的主要重点,因此它具有最高优先级。不过,WallTime 会受到堆分配数量的影响,尤其是意外的堆分配,因此堆分配是次要关注点。除了优化 WallTime 之外,深堆栈也是 Rust 和 C++ 的问题,因此在这些语言中,还应跟踪 FIDL 对堆栈深度的贡献。

Benchmark Suite

基准测试套件中的基准测试应易于描述、理解和讨论。这些数据通常应来自以下两类:

  • 有条不紊地创建合成基准。这些特征应能隔离特定特征或特征组合,以针对绑定中的假设弱点。应选择与基准参数(以元素数量表示的大小)相匹配的其他基准,以便于比较。

合成基准不应是功能的任意组合,否则很难确定它们衡量的是什么、是否值得优化,以及衡量结果对实际性能的影响。

合成基准的示例包括具有 16、32、256、1024 个原始字段的表格、大小为 16、32、256、1024 的字节向量、具有交替对或 uint8、uint64 的结构体。

  • 基于实际 FIDL 类型的基准。这些指标用于衡量实际应用中的性能。这些指标有助于进行前瞻性优化和跟踪回归。

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

GIDL 生成

GIDL 生成应用于从标准化的基准集生成每个绑定的基准代码。

所有基准的要求

Chromeperf 集成

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

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 中创建可执行文件和 build 目标(请参阅 Dart 中的封装容器)。

构建器/编码/解码基准可以位于特定于绑定的目录 src/tests/benchmarks/fidl/{go,rust,dart,hlcpp,llcpp} 中。

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