构建器、对解码 Microbenchmark 进行编码
每个绑定都必须实现一个构建器、编码和解码基准。可以对这些子步骤或变体进行基准测试,但应该有一个基准符合以下与绑定-绑定比较的要求。
Builder
此基准测试会填充要传递给编码器的对象的绑定表示法。有时,可以通过多种方式构建这些对象,总的来说,应该使用最快的方法来构建对用户而言仍然很自然的对象的最快方法。对于不同的构建器方法,可以记录多个值,但应选择一个值作为比较的主值。
设置
设置时间应从构建时间中排除。其中应仅包含构建所需的最少步骤。
标识名创建被视为设置时间,不包含在构建器基准中。
析构函数
对于需要为构建流程创建的对象的任何析构函数,都应包含在构建流程中。构建步骤时应排除任何其他析构函数,例如设置步骤中的析构函数。
虽然句柄是在设置期间创建的,但句柄由构建的对象使用,并且在清除构建的对象时,系统会调用析构函数。
编码
Encode 获取构建器阶段构建的对象,并将其编码为线上格式的字节和句柄。如 FIDL 传输格式中所规定,编码必须验证消息。在此基准测试期间分配的任何缓冲区都应为绑定所需的最小大小。Encode 应尽可能重复使用并遵循与实际绑定代码完全相同的步骤。
设置
编码时间不应包含设置时间。其中应只包含编码所需的最少步骤。
析构函数
对于需要为编码过程创建的对象的任何析构函数,都应包含在时间中。应排除构建或设置的析构函数,包括在构建基准测试期间测量的构建对象的析构函数。这意味着基准测试中既不包含句柄创建时间和销毁时间。
解码
Decode 获取编码的字节和句柄,并将它们解码到相应的绑定对象中。如 FIDL 传输格式中所规定,解码必须验证消息。在此基准测试期间分配的所有缓冲区都应为绑定所需的最小大小。解码应尽可能重复使用并遵循与实际绑定代码完全相同的步骤。
设置
设置时间应从构建时间中排除。其中应仅包含构建所需的最少步骤。
析构函数
对于需要为解码过程创建的对象的任何析构函数,都应该包含在该时间中。不应包含设置、构建或编码的任何其他析构函数。
测量
对于每项基准,我们都希望记录:
- WallTime:实际用时(以纳秒为单位)
- Allocations:用计数来统计堆分配次数
- AlallocateBytes:分配的堆总字节数(以字节为单位)
- 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++ 性能测试结构要求这样做)。
- 如果基准可能存在多种衡量类型,则应指定衡量类型。否则,可以将其省略。
- 当基准测试存在参数时,如果参数名称不明确或有多个参数,则应指定参数名称。如果显而易见,则可以将其省略。参数名称应作为值的前缀,并采用大驼峰式大小写(例如 Len256、Concurrency100)
示例:
LLCPP/Encode/ByteArray/16/Step.Encode/WallTime
- 以绑定开头,然后是基准类型(编码)
- 基准名称后跟尺寸和子基准名称 (16)
- 然后是子步骤名称
- 最后是测量值
Go/Roundtrip/AsciiString/Len256/Concurrency100
- 从绑定开始,然后是基准类型(往返)
- 基准名称后跟两个已命名的 subbenchmark 参数
- 不衡量,因为预计只会衡量实际用时
Memcpy/ByteArray/16
- 从基准类型开始。无绑定,因为不适用
- 后跟基准和子基准名称
- 不衡量,因为预计只会衡量实际用时。
文件路径
应在 src/tests/benchmarks/fidl
中创建基准。如果部分基准测试代码必须存在于其他位置,则应尽力在 src/tests/benchmarks/fidl
中创建可执行文件和构建目标(请参阅 dart 中的封装容器)。
构建器/编码/解码基准测试可以位于绑定特定目录 src/tests/benchmarks/fidl/{go,rust,dart,hlcpp,llcpp}
中。
其他基准测试应放在与基准类型对应的子文件夹中。例如src/tests/benchmarks/fidl/roundtrip
。在极少数情况下,如果需要访问特定的生成类型,可以将这些类型放在其他位置(例如,在 llcpp 内的 memcpy 基准 中,访问 llcpp 类型以测试大小正确的 memcpy)。应将这种债务视为技术债务,并最终将其移至专用的基准和文件夹。