构建器、编码解码微基准
每个绑定都必须实现构建器、编码和解码基准。可以对这些操作的子步骤或变体进行基准测试,但应该有一个基准符合以下绑定-绑定比较要求。
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
绑定名称是以下之一:LLCPP
、HLCPP
、Rust
、Go
、Dart
(区分大小写)
基准路径是用于标识特定基准的字符串。它们可以包含斜杠,例如“ByteArray/1
”。每个单词都采用大写驼峰式大小写。基准测试的名称及其部分名称应为单数,例如 ManyStructField(而不是 ManyStructFields)。
测量名称为以下之一:WallTime
、Allocations
、AllocatedBytes
、StackDepth
(区分大小写)
其他基准的 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)。这应被视为技术债务,并最终移至专门的基准和文件夹。