3

我正在为 C 和 x64 程序集中的二进制搜索开发低级例程,并尝试测量搜索未缓存数组(RAM 中的数据)的确切执行时间。根据分支预测的“幸运”程度,在同一个数组中搜索不同目标所需的时间差异很大。我可以准确地测量最小和中值执行时间,但我发现很难测量最大执行时间。

问题是分支预测的最坏情况在时间上与平均情况加上处理器中断相当。最坏的情况和中断都是罕见的,但我还没有想出一个好的方法来区分一个罕见的事件和另一个。标准方法只是过滤掉所有“异常”的高测量值,但这只有在两者之间有明确的界限时才有效。

所以问题变成了,“我如何区分被中断的测量和合法地比其他测量花费更长的时间?

或者更一般地说,“如何在不预先假设硬最大值的情况下测量执行时间的完整分布?

内核是否存储任何我可以查询的关于是否发生中断的信息?我可以在测量之前和之后查询的东西会告诉我测量是否被中断?理想情况下,它会告诉我中断需要多长时间,但只要知道测量受到影响将是一个很好的开始。

也许除了(或代替)RDTSC,我可以使用 RDPMC 读取一个计数器,该计数器测量在 Ring 0(内核)而不是 Ring 3(用户)中花费的周期数?是否可能已经设置了一个计数器来执行此操作,还是我需要自己设置?我需要创建自己的内核模块来执行此操作,还是可以使用现有的 ioctls?

一些背景:

我主要在 Intel Skylake i7-6700 上运行 Ubuntu 14.03 Linux 4.2.0,但也在 Intel Sandy Bridge 和 Haswell 上进行测试。我已经尽我所能尽可能地减少系统上的抖动。我用CONFIG_NOHZ_FULL重新编译了一个无滴答内核,没有强制抢占,透明的大页面支持关闭,定时器频率为 100 Hz。

我已经停止了大多数不必要的进程,并删除了大多数不必要的内核模块。我正在使用cpuset / cset shield为单个进程保留一个 NoHZ 内核,并使用内核/调试/跟踪来验证我收到的中断很少。但我仍然得到足够的精确测量是困难的。也许更重要的是,我可以设想未来的长尾情况(一个很少需要调整大小的哈希表),能够区分有效和无效的测量值将非常有帮助

我正在使用英特尔在其白皮书中建议的技术测量 RDTSC/RDTSCP 的执行时间,并且通常获得我期望的准确性。我的测试涉及搜索 16 位值,并且我重复和单独地对不同长度的随机数组的 65536 个可能搜索中的每一个进行计时。为了防止处理器学习正确的分支预测,每次都以不同的顺序重复搜索。每次使用“CLFLUSH”搜索后,搜索的数组都会从缓存中删除。

这是一个研究项目,我的目标是了解这些问题。因此,我愿意采用可能被认为是愚蠢和极端的方法。自定义内核模块、保护模式 x64 程序集、未经测试的内核修改和处理器特定功能都是公平的游戏。如果有办法摆脱少数剩余的中断,以便所有测量都是“真实的”,那也可能是一个可行的解决方案。感谢您的建议!

4

2 回答 2

4

我假设你已经尽可能地屏蔽了你的基准测试线程:

  • 它拥有对其 CPU内核(不仅是超线程)的独占访问权限,请参阅此处 了解如何轻松管理它。
  • 中断亲和性已从该核心移开,请参见此处
  • 如果可能,运行nohz内核以最小化计时器滴答声。

此外,您不应从基准代码路径中跳入内核空间:返回后,您的线程可能会被调度一段时间。

但是,您根本无法摆脱 CPU 内核上的所有中断:在 Linux 上,本地 APIC 定时器中断、处理器间中断 (IPI) 和其他用于内部目的,您根本无法摆脱它们!例如,定时器中断用于确保线程最终得到调度。同样,IPI 用于对其他内核执行触发操作,例如 TLB 击落。

现在,多亏了 Linux 跟踪基础设施,可以从用户空间判断在某个时间段内是否发生了 hardirq。

一个小问题是 Linux 在跟踪方面对两类中断的处理方式不同:

  1. 首先是网络适配器、声卡等真实设备占用的“真实”外部硬件中断。
  2. 有一些中断供 Linux 内部使用。

两者都是硬中断,因为处理器按照中断描述符表 (IDT) 的规定将控制异步转移到中断服务例程 (ISR)。

通常,在 Linux 中,ISR 只是一个用汇编编写的存根,它将控制权转移到用 C 编写的高级处理程序。

有关详细信息,请参阅arch/x86/entry_entry_64.SLinux 内核源代码。对于 Linux 内部中断,每次都定义一个跟踪存根,而对于外部中断,跟踪留给高级中断处理程序。

这样每个内部中断都有一个跟踪事件:

# sudo perf list | grep irq_vectors:
  irq_vectors:call_function_entry                    [Tracepoint event]
  irq_vectors:call_function_exit                     [Tracepoint event]
  irq_vectors:call_function_single_entry             [Tracepoint event]
  irq_vectors:call_function_single_exit              [Tracepoint event]
  irq_vectors:deferred_error_apic_entry              [Tracepoint event]
  irq_vectors:deferred_error_apic_exit               [Tracepoint event]
  irq_vectors:error_apic_entry                       [Tracepoint event]
  irq_vectors:error_apic_exit                        [Tracepoint event]
  irq_vectors:irq_work_entry                         [Tracepoint event]
  irq_vectors:irq_work_exit                          [Tracepoint event]
  irq_vectors:local_timer_entry                      [Tracepoint event]
  irq_vectors:local_timer_exit                       [Tracepoint event]
  irq_vectors:reschedule_entry                       [Tracepoint event]
  irq_vectors:reschedule_exit                        [Tracepoint event]
  irq_vectors:spurious_apic_entry                    [Tracepoint event]
  irq_vectors:spurious_apic_exit                     [Tracepoint event]
  irq_vectors:thermal_apic_entry                     [Tracepoint event]
  irq_vectors:thermal_apic_exit                      [Tracepoint event]
  irq_vectors:threshold_apic_entry                   [Tracepoint event]
  irq_vectors:threshold_apic_exit                    [Tracepoint event]
  irq_vectors:x86_platform_ipi_entry                 [Tracepoint event]
  irq_vectors:x86_platform_ipi_exit                  [Tracepoint event]

虽然外部中断只有一个通用跟踪事件:

# sudo perf list | grep irq:
  irq:irq_handler_entry                              [Tracepoint event]
  irq:irq_handler_exit                               [Tracepoint event]
  irq:softirq_entry                                  [Tracepoint event]
  irq:softirq_exit                                   [Tracepoint event]
  irq:softirq_raise                                  [Tracepoint event]

因此,在您的基准代码路径期间跟踪所有这些 IRQ *_entries,您就会知道您的基准样本是否已被 IRQ 毒害。

请注意,x86 上还有第三种硬件中断样式:异常。至少,我也会检查页面错误。对于上面遗漏的 NMI(通过nmi:nmi_handler)。

为了您的方便,我整理了一小段代码,用于在您的基准代码路径中跟踪 IRQ。请参阅随附example.c的使用方法。请注意,/sys/kernel/debug需要访问才能确定跟踪点 ID。

于 2016-02-05T16:27:38.400 回答
1

我知道在 x86 上观察中断的两种“快速”方法,其中第一种是我自己使用的。

  1. 您可以在测试部分之前和之后使用用户空间直接rdpmc读取hw_interrupts.received事件,以确定是否发生任何中断。为了首先对计数器进行编程并处理读取,我在这个答案中列出了一些库。如果我现在开始一个新项目,我可能会使用pmu-tools,或者perf_event_open直接使用,因为实现起来并不难。

  2. 在您的定时区域之前设置%fs或设置%gs为非零值,然后检查该值是否在之后保持不变。如果它已被设置回零,则会发生中断,因为iret指令会重置这些寄存器。在 x86-64 上,您最好不要使用%gs,因为%fs它用于线程本地存储。这篇博文中的全部细节,这是我了解它的地方。

于 2018-12-05T04:12:43.477 回答