我正在开发low level routines for binary search in C和x64程序集,并尝试测量搜索未缓存数组(RAM中的数据)的确切执行时间。在相同的数组中搜索不同的目标需要大量不同的时间,具体取决于分支预测的“幸运”程度。我可以准确地测量最小和中位执行时间,但我发现很难测量最大执行时间。
问题在于,分支预测的最坏情况在时间上与平均情况加上处理器中断相当。最糟糕的情况和中断都是罕见的,但我没有想出一个很好的方法来区分一个罕见的事件与另一个。标准方法只是过滤掉所有“异常”高测量值,但只有在两者之间有明确的线条时才有效。
所以问题就变成了,“我怎样才能区分被中断的测量和合法用了比其他测量长得多的测量?”
或者更一般地说,“如何在不预先确定硬性最大值的情况下衡量执行时间的完整分布?”
内核是否存储了我可以查询是否发生中断的任何信息?我可以在测量之前和之后查询的东西会告诉我测量是否被中断?理想情况下,它会告诉我中断所需的周期有多长,但只要知道测量受到影响就是一个很好的开始。
也许除了(或代替)RDTSC之外,我可以使用RDPMC来读取一个计数器,该计数器测量在Ring 0(内核)而不是Ring 3(用户)中花费的周期数?是否可能已设置一个计数器来执行此操作,或者我是否需要设置自己的计数器?我是否需要创建自己的内核模块才能执行此操作,还是可以使用现有的ioctl?
一些背景知识:
我主要在Intel Skylake i7-6700上运行Ubuntu 14.03 Linux 4.2.0,但我也在Intel Sandy Bridge和Haswell上进行测试。我已经尽力减少系统上的抖动。我用CONFIG_NOHZ_FULL重新编译了一个无空洞内核,没有强制抢占,透明的大页面支持,以及100 Hz的定时器频率。
我已经停止了大多数不必要的进程,并删除了大多数不必要的内核模块。我正在使用cpuset / cset shield为单个进程保留一个NoHZ内核,并使用内核/调试/跟踪来验证我收到的中断非常少。但我仍然得到足够的精确测量很难。也许更重要的是,我可以设想未来的长尾情况(一个很少需要调整大小的哈希表),能够区分有效和无效的测量将是非常有帮助的
我正在使用Intel suggests in their whitepaper的技术测量RDTSC / RDTSCP的执行时间,并且通常可以获得我期望的准确度。我的测试涉及搜索16位值,并且我在不同长度的随机数组上重复并单独计时65536次搜索中的每一次。为了防止处理器学习正确的分支预测,每次以不同的顺序重复搜索。每次使用“CLFLUSH”搜索后,搜索到的数组将从缓存中删除。
这是一个研究项目,我的目标是了解这些问题。因此,我愿意接受可能被视为愚蠢和极端的方法。自定义内核模块,受保护模式x64程序集,未经测试的内核修改和处理器特定功能都是公平的游戏。如果有办法摆脱少数剩余的中断,以便所有测量都是“真实的”,那么这也许是一个可行的解决方案。谢谢你的建议!
答案 0 :(得分:2)
我假设你已经屏蔽了你的基准测试线程 可能的:
此外,你不应该从你的内部空间进入内核空间 基准测试代码路径:返回后,您的线程可能会被安排 离开一段时间。
但是,你根本无法摆脱CPU内核上的所有中断:on Linux,本地APIC定时器中断,处理器间中断 (IPI)和其他用于内部目的,你根本不能 摆脱他们!例如,定时器中断用于确保, 线程最终被安排。同样,IPI也是如此 触发其他核心的动作,如TLB击落。
现在,由于Linux跟踪基础设施,可以从用户空间判断是否有 hardirq在一段时间内发生过。
一个小的复杂问题是Linux处理两类中断 在跟踪方面有所不同:
两者都是处理器异步的意义上的硬件 将控制转移到中断服务程序(ISR),如下所示 中断描述符表(IDT)。
通常,在Linux中,ISR只是一个用汇编编写的存根 将控制转移到用C语言编写的高级处理程序。
有关详细信息,请参阅Linux内核源代码中的arch/x86/entry_entry_64.S
。
对于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
)。
为了您的方便,我为little piece of code整理了一个
在您的基准测试代码路径中跟踪IRQ。请参阅附带的example.c
用法。请注意,为了获得/sys/kernel/debug
,需要访问ú
确定跟踪点ID。
答案 1 :(得分:1)
我知道在x86上观察中断的两种“快速”方法,第一种是我自己使用的。
您可以在测试部分之前和之后使用用户空间rdpmc
直接读取hw_interrupts.received
事件,以确定是否发生了任何中断。为了首先对计数器进行编程并处理读取,我列出了一些库in this answer。如果我现在开始一个新项目,我可能会使用pmu-tools
,或者直接使用perf_event_open
,因为这并不是很难实现。
在您的定时区域之前将%fs
或%gs
设置为非零值,然后在此之后检查该值是否保持不变。如果将其重新设置为零,则会发生中断,因为iret
指令会复位这些寄存器。在x86-64上,最好使用%gs
,因为%fs
用于线程本地存储。详细信息in this blog post,这是我了解的地方。