完全系统指令集模拟器中的高效异步中断处理
汤姆·斯平克哈里·沃格斯塔夫比约恩·弗兰克
英国爱丁堡大学信息学院计算系统结构研究所t.spink@sms.ed.ac.uk, h.wagstaff@sms.ed.ac.uk,bfranke@inf.ed.ac.uk
指摘令要集模拟器(ISS)在嵌入式软件和硬件开发中具有多种用途,通常基于动态二进制翻译(DBT),其中频繁执行的客户指令区域通过即时(JIT)编译器被编译为主机指令。全系统仿真需要处理来自定时器和I/O设备等的异步中断,这使得问题更加复杂,因为控制流会不可预测地中断并从当前代码区域转移。本文提出了一种新颖的异步中断处理方案,该方案能够无缝集成到基于区域的动态二进制翻译器中。我们首先证明了该方案的正确性,即即使在包含控制流循环的代码区域中,中断处理也不会无限期推迟。我们还表明,由于最大限度减少了插入的检查次数,新的中断处理方案具有高效性。中断处理程序也被提供给JIT编译器并编译为原生代码,进一步提升了系统的性能。我们在ARM模拟器中采用基于区域的JIT编译策略对本方案进行了评估。结果表明,与传统的按块方案相比,我们的解决方案将动态中断检查次数减少了73%,中断服务延迟降低了26%,I/O密集型工作负载的吞吐量提高了7%。
分类与主题描述
D.4.8[操作系统]:性能—模拟
通用术语
设计、实验、测量、性能
关键词
完全系统仿真,动态二进制翻译,基于区域的即时编译, 异步中断处理
引言
指令集模拟器(ISS)对于软件和硬件开发人员而言都是不可或缺的工具。事实上,在嵌入式系统领域,指令集模拟器在软件开发阶段被常规用于功能和性能测试,而硬件设计者依赖其快速的周转时间来对新架构或架构扩展进行原型设计。
全系统指令集模拟器具备超越标准指令集模拟器执行用户模式指令流的扩展能力。此类全系统指令集模拟器需要支持额外的功能,包括内存管理单元、内核模式指令、中断处理和设备仿真,这些功能对于能够托管操作系统(OS)的完整系统的模拟是必需的。
中断处理效率尤为重要,因为中断需要频繁处理(例如触发进程调度器的Linux内核定时器中断频率通常设置在100‐1000Hz之间),并且要求快速响应。然而,高效的中断处理与动态二进制翻译(DBT)存在矛盾,而DBT正是构建高性能指令集模拟器(ISS)的基础技术。在基于DBT的ISS中,控制流会被分析,频繁执行的客户指令区域被识别,并通过即时编译(JIT)编译器[25]转换为主机指令。生成的原生代码执行速度远高于解释器执行客户指令的速度,从而提供更高的仿真性能。然而,中断会不可预测地干扰应用程序的“自然”控制流,使其偏离当前代码区域而跳转到其他区域。为了捕捉这种行为,需要在生成的代码中插入额外的检查,当有待处理中断请求被触发时,启动中断处理。这些额外的检查开销较大,并且可能阻碍激进的基于区域的优化,如果简单地在每条客户指令后插入此类检查,可能导致仿真性能下降一个数量级以上。
对于异步中断,即与当前执行的指令和处理器当前状态无关的外部触发中断,其处理可以延迟一小段时间,直到更“合适”的时刻。例如,操作系统可能会在临界区中屏蔽某些中断,并仅在退出该区域后处理待处理中断。我们在指令集模拟器中利用这一特性,插入更少的中断检查,从而降低其中断检查对性能的影响。
我们在这篇论文中试图回答的核心问题是:最少需要插入多少次中断检查,以及在何处插入?
我们表明,在线性执行踪迹的开始和结束处插入中断检查(例如由基于踪迹的JIT编译器生成的踪迹),在基于区域的JIT编译方案中并不适用,而后者提供的仿真速率高于基于踪迹的对应方案。
本文提出了一种基于区域的动态二进制翻译(DBT)方法,用于在指令集模拟器(ISS)中插入异步中断检查的新方案。我们动态识别控制流循环,并在每个控制流循环中插入适当的中断检查,以保持正确性。这对于依赖中断的循环尤为重要。
用于处理其终止。虽然插入最少数量的中断检查是一个NP难问题[8],,但我们利用了一种现有的近似算法,适用于对性能要求较高的JIT环境,在大多数实际情况下能够计算出接近最优解的方案。我们展示了区域分析如何与中断处理相互作用,使得嵌套中断可以被处理,并且中断处理程序本身也可以呈现给JIT编译器,从而实现进一步的性能提升。我们通过面向ARM指令集的基于区域的动态二进制翻译指令集模拟器验证了所提中断处理机制的高效性,将动态中断检查的数量减少了73%,中断服务延迟降低了26%,并将I/O密集型工作负载的吞吐量提高了7%。
1.1 动机示例
图1:在用户模式模拟中,我们基于区域的指令集模拟器明显优于 QEMU。激进的基于区域的优化显著降低了SPECCPU2006基准测试的绝对运行时间(单位:秒,越低越好)。然而,基于区域的动态二进制翻译给全系统仿真带来了挑战,因为在全系统仿真中需要执行中断检查。我们提出了一种方法,该方法(a)是正确的,并且(b)在全系统仿真环境中保持了基于区域的动态二进制翻译的性能优势。
基于基本块的动态二进制翻译器(DBT)一次将一个客户机基本块翻译成相应的主机代码(见图2a)。这些块被视为独立单元,通过跳转到缓存中已有的代码来实现控制流,如果代码尚未出现,则触发按需翻译。将基于基本块的DBT扩展为沿路径考虑多个块,就形成了基于踪迹的动态二进制翻译(trace‐basedDBT),它允许优化跨越基本块边界,因为踪迹本身被视为一个独立单元(见图2b)。尽管基于踪迹的DBT仅考虑线性控制流,但基于区域的DBT可以利用特定代码区域内(如循环)的控制流(见图2c和2d)。
应用程序的用户模式模拟仅需在主机上仿真目标二进制文件,通常不需要中断或设备仿真——一个简单的操作系统模拟层就足以执行大多数用户二进制文件。显著的例外是那些需要或依赖于异步Unix信号的应用程序,但大多数基准测试(特别是SPECCPU2006套件中的[13])并不依赖此行为,因此不适合用于测试全系统仿真的这一重要需求。图1显示,在可以忽略中断检查的用户模式模拟中,我们的模拟器平均比处于用户模式配置下的QEMU[4],快2.23x。
图1中所示的仿真性能提升是由于我们的基于区域的方法(实现了诸如优化的块结束处理[25])以及可以在代码区域内执行的激进优化所致。
然而,通过向区域中插入额外的侧出口可能会阻碍这些优化。此类额外的侧出口可能是一个中断检查,即测试一个标志以确定是否存在待处理中断,然后退出该区域,以便模拟器能够处理待处理中断。因此,希望减少这类侧出口的数量,以便在必须进行中断检查的全系统仿真中,我们激进的区域优化仍能保持有效。
在较新版本中,QEMU的中断检查方法是在每个翻译块的开头插入一次检查,从而在执行客户机基本块之前进行检查。如果发生需要QEMU退出原生代码的事件,则会设置一个标志,原生代码将退出至主执行循环,在该循环中处理该事件。我们的方法类似:我们维护一个标志以指示是否有异步操作待处理,并在块中插入检查以判断该标志是否非零。若满足此条件,我们将退出原生代码。
差异出现在我们考虑我们的指令集模拟器与QEMU在原生代码生成方面所采用的不同方法时。
1.1.1 最先进的QEMU
由于QEMU不应用任何形式的块间优化,因此不会出现额外的检查影响优化的问题——只会产生一个容易预测的分支未命中惩罚。然而,由于我们将一组基本块视为一个整体,并希望在这些基本块之间进行优化,插入中断检查可能会抑制我们执行某些本可以进行的优化,同时显著增加我们的即时编译器需要翻译的代码量。
1.1.2 正确处理中断相关行为
考虑到图3中所示的代码,可以看出它包含一个等待中断处理程序运行的循环,随后是对三个输入数组进行基于循环的计算。为了使该程序能够被正确模拟,它必须能够接收和处理初始while循环期间的异步中断。for循环中的计算及其终止不依赖于中断,但如果发生中断信号,我们必须插入中断检查以避免不可接受的延迟。此代码的控制流图如图2所示。
这种行为在全系统仿真中很常见,尤其是在操作系统内核中,它们可能会等待外部设备指示缓冲区已满并准备好进行处理。
在仅解释执行、功能级(即非周期精确)的模拟器中检查中断非常简单——我们只需在每条解释执行的指令或基本块结束时进行检查,或者在执行了若干条指令后进行检查。这些方法均能产生正确行为,但根据所采用方案的不同,其中断服务的延迟也各不相同。然而,对于基于即时编译(JIT)的动态二进制翻译(DBT),中断检查策略的选择则要困难得多。
1.2 贡献
在本文中,我们做出了以下贡献:
1.我们设计了一种新的方案,用于在基于区域的动态二进制翻译指令集模拟器(DBTISS)中优化异步中断的处理;
2.我们证明了我们用于插入中断检查的算法是高效的,适用于即时编译(JIT)处理,并且不会引入无界的中断响应时间;
3.我们展示了我们的方案在针对ARM指令集的 Linux全系统仿真中,能够提升仿真性能和I/O吞吐量。
1.3 概述
本文其余部分结构如下。在第2节中,我们简要介绍了动态二进制翻译(DBT)中存在的中断检查放置问题。接着概述了各种方案的可用性,并在第3节中描述了我们的新中断处理方案。在第4节中,我们展示了实证评估结果,然后在第5节中讨论相关工作。最后,我们在第6节中进行总结并得出结论。
2. 动态二进制翻译粒度与插入中断检查的问题
在实现动态二进制翻译系统中的中断检查时,我们有更多选择来插入中断检查。动态二进制翻译系统中最重要的细节之一(也是影响中断处理方式的最重要因素之一)是系统基于基本块、踪迹还是区域进行翻译(参见图2)。
基于基本块的动态二进制翻译器必须在每个基本块中至少检查一次中断。这在图2a中有所展示。由于每个块可以从任意前驱进入,如果我们在一个块的末尾不执行中断检查,则可能会陷入循环,等待一个永远不会被检测到的中断。由于此类动态二进制翻译器中基本块之间的控制流相对简单(通常通过虚拟地址到已翻译代码的映射查找),从中断处理程序返回也较为直接,因为下一条要执行的指令将是某个基本块的起始位置,可通过映射查找到。
基于踪迹的动态二进制翻译器具有稍高的灵活性,我们可以在每个块(在特定踪迹内)的末尾检查中断,也可以在每个踪迹的开头进行检查。这在图2b中有所说明。踪迹内的控制流是线性的,但可能存在多个退出点,因此在踪迹头部进行检查可以确保如果我们在某个踪迹中提前退出,仍可在下一个踪迹的头部检查中断。更频繁地检查可能会降低中断延迟,但会影响性能;而较少检查则可能导致与基本块情况相同的问题,即无法检测到模拟程序继续运行所必需的中断。
尽管到目前为止讨论的策略对于基于块和基于踪迹的DBT系统能有效工作,但它们并不适用于基于区域的DBT系统。基于区域的DBT系统利用动态提取的控制流信息,在基本块边界之间优化生成的代码,并对一段代码区域应用某些循环优化(图2c和2d)。在转换过程中,一个优化阶段甚至可能会拆分或合并客户程序基本块,这将产生高度优化且正确的行为,但原始基本块的表示将丢失。
与基本块或基于踪迹的DBT系统不同,生成的翻译代码能够包含循环控制流,这意味着必须特别注意以确保中断能够以及时的方式得到处理。一个简单的DBT系统可能会决定在每个已翻译基本块的末尾插入中断检查。然而,这会抵消基于区域的DBT系统的许多优势,因为每次中断检查都可能导致退出翻译代码,从而使跨越循环和基本块的优化效果大打折扣。在区域入口或出口处进行中断检查(如在踪迹式DBT系统中)也会导致错误行为,因为在这样一个区域内可能遇到依赖中断的循环,如上面的例子所示。
相反,我们应该分析该区域的控制流图,以确定必须包含中断检查的最小块集,同时仍确保正确行为。在这种情况下,我们必须确保在控制流图中的每个循环至少有一个无条件基本块中包含至少一个中断检查。如果我们未能将中断检查插入到一个长时间运行的循环中,则可能会将中断推迟过长时间(甚至可能无限期)。此外,如果我们未能将中断检查插入到其行为依赖于中断被处理的循环中,则我们的动态二进制翻译将表现出不正确的行为。
一种用于计算必须包含中断检查的最小块集的算法,可以基于计算控制流图的minimum feedback edge set[8, 15]。这能够确定控制流图(CFG)中的一组最小边,当切断这些边时,可消除控制流图中的所有循环。通过在这些边的根块(source nodes)中插入中断检查,我们可以确保执行必要的最少数量的中断检查,从而在保证正确行为的同时维持良好性能。在我们的示例中,这将得到图2c中所示的中断检查。
然而,为了确保我们的动态二进制翻译具有良好的性能,我们还必须保证有良好的“预热”时间。也就是说,我们必须在生成代码的性能与代码生成速度之间取得平衡。计算图的精确反馈弧集代价较高(该问题是NP难[8]),而计算近似算法则快得多[9],,并且与计算精确反馈弧集相比,不太可能导致生成代码的性能出现显著下降。在我们的示例上使用反馈弧集的近似算法可能会得到如图2d所示的中断检查,但当然还有许多其他可能性。
3. 基于区域的中断检查
3.1 概述
我们的指令集模拟器(ISS)是一种基于区域的动态二进制翻译(DBT)系统,利用代码热点区域中的优化机会来最大化仿真吞吐量。系统根据收集到的性能分析信息,对特定区域内的基本块进行跨基本块优化,生成高效的宿主机器代码,以精确模拟目标指令集。目标机器代码中的基本块与我们即时编译器(JIT compiler)生成的代码之间不一定存在一对一的映射关系。优化阶段可以任意合并或拆分基本块,只要保证被翻译二进制文件的行为正确,这种操作就是完全有效的。
(a)基于块的动态二进制翻译。在每个基本块之后插入中断检查,由于检查次数过多导致性能较差。(b)基于踪迹的动态二进制翻译。在每条线性踪迹的开始和结束处插入检查。控制流被扁平化,从而禁止了循环优化。(c)区域动态二进制翻译。中断检查的最优插入方式。检查数量最少,但需要在即时编译时求解一个NP难问题。(d)区域动态二进制翻译。近似算法导致检查数量较多,但接近最优且足够快速,适用于即时编译。
图2:在表示图3示例程序的控制流图中的控制流边上,由各种中断检查策略插入的以条形表示的中断检查。翻译单元(基本块、线性轨迹、区域)已突出显示。
值得称道的是,性能分析信息能够识别出哪些基本块可能属于某个区域的局部,即不会被该区域外部访问,因此可以移除任意跳转到这些块的能力。这一重要区别使得区域局部块能够在优化阶段被任意变换,从而允许在区域内对这些基本块进行跨块优化。如果我们允许通过任意基本块进入(或退出)该区域,则必须对模拟CPU状态做出某些保证(例如,修改后的寄存器值需写回状态结构),这将给生成的原生代码带来不必要的开销。
在我们的模拟器中,执行开始时使用解释器,并在执行过程中收集区域信息。这些信息包括基本块的头和长度,以及所有检测到的基本块之间的边,这些边可能是直接分支、谓词分支或间接(计算)分支。
1 void主函数()
2 {
3 //等待某些硬件信号
4 while(received_irq== 0)usleep(10);
5
6 //现在在循环中进行一些计算
7 for(inti= 0;i< 10;++i){
8 output[i]= inputa[i]*inputb[i]
9 if((i & 1)== 0){
10 output[i]+= inputc[i];
11 }否则{
12 output[i] ‐= inputc[i];
13 }
14 }
15 }
图3:需要进行中断检查的示例代码。while循环在主函数中的终止依赖于一个中断,因此我们必须在此循环内插入中断检查。而for循环的终止不依赖于中断,但如果直到循环结束后才进行中断检查,可能会导致无法接受的中断延迟。我们希望在此循环中插入尽可能少的中断检查,以满足以下两个条件:(a)每次迭代都进行检查;(b)出于性能考虑,不会进行不必要的多余检查。
一旦我们确定某个区域包含热点基本块(即该区域中的某些块被执行次数超过N次,其中N为可配置阈值),该区域将被分派到一个异步JIT编译任务池中。随后,为此区域生成LLVM[16]字节码翻译,并应用标准优化,最终为此区域生成机器代码。在分析阶段,某些块被识别为区域入口点。这些块用于从解释执行代码过渡到已翻译代码,并通过仅保留已发现的入口点来限制区域入口点的数量,从而支持在区域内进行进一步的优化。由于在翻译区域之前,我们未必已经遇到该区域内的所有代码或控制流,因此可能需要返回解释器以执行尚未翻译的代码。在这种情况下,这些未翻译的代码可能变为“热点”,从而使该区域具备重新翻译的条件。
异步中断是不利的控制流来源,可能会通过在已分析基本块之间引入虚假边而显著降低所收集的性能分析信息的准确性。为解决此问题,我们维护了一个中断栈,从而可以在当前执行的中断级别上进行控制流的性能分析。默认情况下,我们从一个特殊的无中断级别开始执行,并随着执行的推进收集性能分析信息。
当一次中断检查表明有中断挂起时,该中断级别将被压入中断栈,然后在中断处理程序中继续执行,此时性能分析器将在新的级别中收集信息。一旦中断处理程序完成(可能返回到用户代码),该中断级别便从栈中弹出,执行从中断发生处恢复,同时保留从中断点开始的性能分析信息。使用栈结构是为了支持嵌套中断。
1 define ApplyChecks(工作单元):
2 下一个索引:= 0
3 do:
4 RM计数:= 0
5
6 foreach 块in 工作单元.Blocks:
7 如果块.HasSelfLoop:
8 块.有中断检查:= True
9 否则如果not块.有中断检查:
10 call 强连接(工作单元, 块)
11 while RM计数!= 0
12
13 define 强连接(工作单元, 起始块):
14 起始块.索引:= NextIndex
15 起始块.低链接:= NextIndex
16 起始块.Seen:= True
17 NextIndex++
18
19 块栈.Push(起始块)
20 起始块.在块栈上:= True
21
22 foreach 后继者in 起始块.后继块:
23 如果后继者.有中断检查:
24 继续
25
26 如果未后继者.Seen:
27 强连接(工作单元, 后继者)
28 起始块.低链接:=
29 min(起始块.低链接 ,后继者.LowLink)
30 elseif 后继者.在块栈上:
31 起始块.低链接:=
32 min(起始块.低链接 ,后继者.Index)
33
34 如果起始块.低链接==起始块.索引:
35 计数:= 0
36 do:
37 StackedBlock:=块栈.弹出()
38 块栈.在块栈上:= False
39 计数++
40 while 块栈!=起始块
41
42 if 计数> 1:
43 起始块.有中断检查:= True
44 RM计数++
图4:基于塔扬的[26]算法,用于任意代码区域的优化中断检查放置。该算法实现了为每个节点维护一个标志的建议,以在常数时间内确定该节点是否存在于块堆栈上,并添加测试以处理循环到自身的块
block_0x00001000:
%1= load i32*%actions_pending_ptr
%2= icmp ne i32%1,0
br i1%2, label%handle_actions, label%proceed_with_block
handle_actions:
tail call void@cpuHandlePendingActions(i8*%cpu_state_val)
proceed_with_block:
…
Execution Engine
Emulation Model
离开原生代码 返回原生代码
图5:针对被确定为中断检查块的块头部生成的LLVMIR。如果CPU状态结构指示有待处理操作,则通过尾调用返回执行引擎,从而离开原生代码,由执行引擎处理该待处理操作。
3.2 中断检查放置方案
我们在指令集模拟器(ISS)中实现了三种中断检查放置方案。
尽管可以使用最精确的算法来计算区域图的反馈弧集,从而选择需要插入中断检查的基本块,但我们转而采用了一种基于塔扬强连通分量(SCC)[26]算法的近似算法,如图4所示。
使用这种近似算法可减少该分析阶段引入的延迟,从而确保我们的指令集模拟器保持快速预热时间。
在解释器执行完一个基本块后,我们总是会进行中断检查(无论使用何种方案)——我们的方案适用于即时编译器插入中断检查的方式,因为我们的兴趣在于保持生成的原生代码的性能。
我们实现的放置方案如下所述:
1.完全:在每个基本块之前插入一个中断检查。这与 QEMU执行中断检查的方式相同。
2.向后:在每个作为向后分支目标的基本块之前插入一个中断检查。
3.优化:在由图4中描述的算法选定的基本块之前插入中断检查。
在编译阶段,一个编译工作单元(包含guest基本块及其控制流信息)会经过所选的中断检查方案分析,以确定哪些块应包含中断检查。一旦识别出这些块,翻译器会在每个块被翻译时,在必要位置插入中断检查。
Tarjan的强连通分量算法需要进行轻微修改才能适用于我们的指令集模拟器。特别是,我们必须检测自环(后继者为自身的基本块),而原算法并未处理这一点。此外,我们通过为每个节点维护一个OnBlockStack标志来实现以常数时间判断节点是否在栈上的建议。除此之外,该算法保持不变。
3.3 处理中断
由于我们处理的是异步中断,任何模拟组件都可能在任意时间、任意宿主机器线程上触发中断。我们使用“待处理操作”的概念来表示存在必须中断正常执行的操作,并在CPU状态结构中使用一个位字段来指示可能有哪些类型的待处理操作。当判断是否有中断挂起时,正是查询该位字段,而我们为中断检查生成的原生代码也只是简单地测试该位字段。如果该值被确定为非零,则说明有异步操作待处理,必须退出原生代码以进行处理。
待处理操作可能是中断、Unix信号或“中止”或“转储状态”等特殊内部信号。这允许客户机程序注册信号处理器,并使宿主信号得以传播。或者,如果正在模拟一个完整的操作系统,在操作系统初始化阶段,中断向量表(IVT)将被初始化,以指定当特定中断挂起时应跳转到的位置。当模拟平台设备发出中断时,控制流将通过该IVT跳转到客户机操作系统的正确位置。
图5显示,当执行中断检查块且待处理操作位字段非零时,控制通过尾调用从原生代码返回到执行引擎。
活动区域
A ‘
B ’ C ‘
D ‘
5
1
中断栈 中断栈
2
3
4
活动区域 活动区域
A A
B
C
D
A A
图6:中断发生时区域形成的流程。(1)在执行IRQ检测循环期间(且在发现任何其他代码之前),我们检测到一个中断。(2)当前的区域状态被压入中断栈中。(3)我们执行中断处理程序,将其视为与原始区域完全不同的独立区域。(4)我们返回正常执行,并从栈中弹出先前的区域状态。(5)我们退出紧密循环并继续构建原始区域。关键在于,没有生成将“正常”区域连接到“中断”区域的多余边。
4. 实验评估
4.1 实验方法
我们在两种不同类型的工作负载上进行测量,以评估我们方法的影响。一种工作负载是中断密集型工作负载,形式为I/O基准测试,使用标准的Linux I/O基准测试工具hdparm [20],;另一种是计算密集型工作负载,使用SPEC CPU2006整数基准测试。该对比将评估我们的优化对需要低延迟中断服务的工作负载以及不依赖中断、因此对中断延迟不敏感的工作负载的影响。
4.2 实验设置
所有工作负载均在ARM Linux 3.17.0 guest操作系统(带有 Arch Linux ARM用户空间)内执行,该系统运行于我们的指令集模拟器的全系统模式下。仿真的宿主机配置如表1a所示,指令集模拟器的配置如表1b所示。
最先进的 :我们还将自己与最先进的 DBT QEMU版本2.1.50 进行比较。该比较旨在表明,即使增加了插入中断检查的复杂性,我们基于区域的方法在编译上仍能带来显著的性能提升。
4.2.1 平台配置
我们使用一个vanilla(未修改的)Linux 3.17.0 内核作为模拟器的客户机操作系统来运行实验。该内核针对ARM Versatile应用底板进行配置,并包含除了启用 VirtIO 块设备模块外,无需其他配置或修改。该内核启动在我们的 ISS 和 QEMU 上均未作修改。
ARM Versatile应用底板包含一个ARM926 CPU,以及许多外部设备,如定时器和I/O模块。我们支持其中的多个设备,但排除了与实验无关的设备(例如FPGA)或没有公开文档的设备。该平台规范中仅包含128MB内存,不足以运行 SPEC基准测试套件。因此,我们对该平台进行了修改,增加了额外的内存——这一修改在我们的ISS和QEMU中均已完成。
我们已在ISS中实现了VirtIO规范,如[23]中所述,以便为来宾Linux操作系统提供块设备实现。该块设备包含用于启动的根文件系统,并且还用作I/O基准测试的目标。
4.3 I/O密集型工作负载的主要结果
在我们的I/O基准测试中,我们并不关心测试底层存储设备,而只是希望对我们的中断系统施加压力。因此,hdparm基准测试适用于这些中断测试,因为我们的I/O设备被实现为一个 VirtIO [23]块设备,该设备使用中断将I/O完成信息传回给客户机。我们可以通过测量系统的I/O吞吐量来衡量系统的性能,因为I/O吞吐量将直接对应于我们处理中断的速率。我们无需测试不同的I/O访问模式(如顺序、随机等),因为这对中断系统没有任何影响。
在我们的指令集模拟器(ISS)中,当被模拟的设备引发中断时,会导致CPU上的IRQ线被置位,进而使一个异步操作处于待处理状态。该异步操作最终将导致被模拟的CPU执行流程跳转到中断服务例程(ISR)。我们将中断延迟定义为从被模拟设备引发中断,到CPU跳转并开始执行ISR所经历的时间。
| (a)ISS主机配置。 | |
|---|---|
| 供应商与型号 | Dell™PowerEdge™ R610 |
| 处理器架构 | x86‐64 |
| 处理器型号 | 2×Intel©Xeon™ X5660 |
| 核心数量 | 2×6 |
| 时钟/前端总线频率 | 2.80/1.33 千兆赫 |
| L1缓存 | 2×6× 32K 指令/数据 |
| L2缓存 | 2×6× 256K |
| L3缓存 | 2× 12 MB |
| 内存 | 通过6个通道的36 GB |
| 操作系统 | Linux版本2.6.32 (x86‐64) |
| (b)指令集模拟器系统配置。 | |
|---|---|
| ISS参数 | 设置 |
| 客户机架构 | ARMv5T |
| 客户机操作系统 | Linux 3.17.0 |
| 主机架构 | x86‐64 |
| 翻译/执行模型 | 异步混合模式 |
| 跟踪方案 | 基于区域 [6, 25] |
| 跟踪间隔 | 30000 块 |
| 即时编译器 | LLVM 3.4 |
| JIT 编译线程数 | 10 |
| JIT 优化 | ‐O3 & 部分评估[27] |
| JIT 阈值 | 自适应 [6] |
表1: 实验设置:主机系统参数和指令集模拟器系统的配置。
0
5
10
15
20
25
30
QEMU Full 向后 优化
I/ O T hr ou gh pu t (M B /s )
| 29.12|29.12|29.12|29.12|29.12|29.12|29.12|29.12|29.12|
| —|—|—|—|—|—|—|—|—|
| 27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|||
| 27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|27.34 27.50|||
| |||||||||
| 18|18|18|||||||
| |||||||||
| |||||||||
| |||||||||
图7: 使用hdparm基准测试测得的绝对I/O吞吐量(单位:MB/s)—— 数值越高越好。我们表明在所有情况下,我们的I/O吞吐量均高于 QEMU,并且相比我们的基线完全方案提升了7%。
图7显示,在hdparm基准测试中,I/O吞吐量相比 QEMU提升了61%,使用优化的放置方案相较于向后和完全检查方案有7%的相对提升。
4.4 CPU密集型工作负载的主要结果
由于我们正在模拟一个完整的操作系统,因此不可能移除所有的中断检查——即使在一段时间内没有产生中断也是如此——因此 CPU密集型工作负载会因偶尔的中断检查而产生较小的性能损失。
因此,我们考虑了我们的方案对CPU密集型工作负载运行时间的影响。图8显示,在采用更优放置算法时,SPEC CPU2006整数基准测试的运行时间减少了13%。这归因于我们生成的原生代码质量更高,原因是插入的中断检查较少。
SPEC CPU2006整数基准测试被广泛使用,被认为代表了广泛的应用领域。我们已将其与其reference数据集一起使用。这些基准测试使用GCC 4.6.0 C/C++交叉编译器进行编译,目标为ARM架构(无硬件浮点支持),并采用‐O2优化设置。
4.4.1 与QEMU的比较
我们的指令集模拟器的一个重要指标是,相较于最先进的QEMU,持续为CPU密集型工作负载带来性能提升。图8显示,相对于我们的基线方案(完全 放置),QEMU在全系统模式下慢3倍,证实了我们的基于区域的DBT方法即使插入了中断检查,仍能保持跨基本块边界的代码优化能力。
此外,使用VirtIO基础设施的一个优势是,我们可以配置QEMU使用完全相同的内核镜像、文件系统和块设备配置,从而能够直接将我们的I/O吞吐量与QEMU进行比较。
0
0.5
1
1.5
2
2.5
3
QEMU Full 向后 优化
图8: SPEC CPU2006整数基准测试相对于完全基线的实际运行 时间的相对减少——数值越低越好。我们表明,在所有情况下, 我们的速度都快于QEMU,并通过优化的放置方案将模拟速度提 高了13%。
4.5 进一步分析
4.5.1 静态与动态中断检查
一个静态中断检查对应于决定在某个基本块中放置一个中断检查,而一个动态中断检查则是在运行时间实际发生的中断检查。我们的目标是最小化所放置的静态中断检查的数量,从而相应地减少发生的动态检查次数。静态中断检查数量的减少具有两个目的:a. 提供给LLVM JIT编译器的中间表示数量减少,从而降低了优化器和编译器的工作量,进而改善了编译时间;b. 优化器可以自由地在整个区域上进行更激进的优化,并生成更优的原生代码。
(a)I/O密集型工作负载
0
0.2
0.4
0.6
0.8
1
Full 向后 优化
Re la tiv e Ch ec k Co un t
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查||||
| —|—|—|—|—|—|—|—|—|—|—|—|
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查||||
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查||||
| ||||||动态检查|动态检查|动态检查||||
| ||||||||||||
| ||||||||||||
| ||||||||||||
| ||||||||||||
(b) CPU 密集型工作负载
0
0.2
0.4
0.6
0.8
1
Full 向后 优化
Re la tiv e Ch ec k Co un t
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|||||
| —|—|—|—|—|—|—|—|—|—|—|—|—|—|
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|||||
| 静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|静态检查|||||
| |||||||动态检查|动态检查|动态检查|||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
| ||||||||||||||
图9: 在三种不同的中断检查方案中,I/O密集型和CPU密集型工作负载的静态和动态中断检查减少情况——数值越低越好。对于I/O密集型工作负载,我们的优化放置算法将静态检查减少了66%,动态检查减少了73%。对于CPU密集型工作负载,我们分别将静态检查和动态检查减少了63%和69%。
图9a和图9b均显示,我们放置的静态中断检查较少,因此通常执行的动态检查也较少。例外情况是在CPU密集型工作负载中使用向后分支方案时,我们观察到动态检查有所增加。这可归因于CPU密集型工作负载在热点循环控制流中花费更多时间,而在此处我们必然插入了中断检查,从而增加了动态中断检查的计数。我们的优化的放置方案比基线方案减少了66%的中断检查,并导致73%更少的动态检查发生。
4.5.2 中断延迟
我们所测量的中断延迟是指从模拟中断被触发开始,到执行引擎开始执行中断服务程序(ISR)为止的时间。降低中断延迟可以提高I/O密集型工作负载的吞吐量,因为数据请求能够更快地得到响应。我们测量了不同放置方案对中断延迟的影响,以确保不会将中断推迟过长时间。这些测量针对I/O密集型工作负载进行,因为中断延迟不会影响CPU密集型工作负载的吞吐量。
虽然看起来采用完全 放置方案时延迟应该更低(即更多的检查意味着更多的响应中断的机会),但由于该方案对生成代码质量的影响,实际上我们观察到更高的延迟(平均为 108μs,高于80微秒)。图10显示,在减少插入的静态 检查数量的方案中,我们降低了中断延迟;对于我们的优化算法,延迟降低了26%。我们生成的更高质量的原生代码使执行速度快,这解释了为何我们能更快速地响应中断。
4.5.3 中断延迟的分布
0
20
40
60
80
100
120
Full 向后 优化
In te rr u p t L at en cy ( us )
| |||||||
| —|—|—|—|—|—|—|
| 108|108|108|108|108|108|108|
| ||80 80|80 80|80 80|80 80|80 80|
| |||||||
| |||||||
| |||||||
| |||||||
图10: 运行hdparm I/O 基准测试时测得的绝对中断延迟(单位为 μs), 数值越低越好。我们展示了优化的放置方案可将延迟降低26%。
图11 显示了在 完全 和 优化 方案下处理中断的各种延迟分布情况。这两种方案产生的延迟之间的余弦相似度为 cos θ= 0.999,表明不同方案在系统产生的延迟方面没有显著差异。图11所示的累积延迟分布实际上可与在实际硬件上运行的流行Xen虚拟机监控器的ARM移植版本相媲美 [28]。
0
0.2
0.4
0.6
0.8
1
0 20 40 60 80 100 120 140
Cu m ula tiv e L at en cy D ist rib uti on
中断延迟(微秒)
Full
优化
图11: 优化的中断策略与每个基本块都进行检查相比的中断延迟累积分布
4.5.4 可扩展性
我们提出了一系列中断频率,从1赫兹到1千赫兹,以测试系统在更高频率下的扩展能力,图12显示我们的优化方案始终优于朴素全方案。此外,可以看出,我们在整个频率范围内保持了相对稳定的性能水平,仅下降了约2%。
750
760
770
780
790
800
810
820
1 Hz 10赫兹 100赫兹 1千赫兹
E xe cu ti o n T h ro ug hp ut ( in M IP S)
中断频率
| Full 优化|Full 优化||
| —|—|—|
| |||
| |||
| |||
| |||
| |||
| |||
图12: 在从1赫兹到1千赫兹的不同中断频率下,完整中断检查 与我们的中断检查策略的比较。在高中断频率时,完整中断检查会带来显著的性能开销,而我们的优化策略仍能保持良好的性能。
4.5.5 与硬件的比较
随着仿真吞吐量接近实际硬件性能,必须确保我们指令集模拟器中的中断处理与所模拟的硬件处于同等水平,即不会引入不可接受的延迟。
在实际的非模拟系统中观察到的中断响应时间是依赖于硬件的时间和某些操作系统引入的开销的总和。依赖于硬件的时间由处理器的微架构及其当前状态、系统配置以及中断类型决定。操作系统开销在最佳和最坏情况之间可能会有很大差异,通常在内核(临时)禁用中断时达到最大值。
根据制造商的规格 [2] ,在带有两级缓存的 ARM1176JZ(F)-S 上运行的 Linux 驱动程序所经历的中断延迟大约为 5000个周期。这是主要由操作系统本身的开销引起。在300兆赫兹下5000个周期为16.7微秒,而我们的指令集模拟器在配置了优化插入算法后,平均延迟为80微秒。
5. 相关工作
全系统仿真中的错误来源最近在[11]中进行了分析。中断检查的最优放置可以类比于性能分析计数器的最优插入问题,该问题可能表现出与前几节所述相似的问题。然而,更新性能分析计数器不会引入额外的控制流——因为在大多数情况下只是简单的计数更新。虽然减少计数器更新次数可以通过降低内存访问量来实现性能提升,但我们的问题是略有不同的,因为必须添加额外的控制流以执行中断检查,从而在优化器中造成额外延迟,并导致生成的代码不够最优。[3]中描述的技术解决了最优放置问题,但未解决因引入额外退出点而遇到的问题。
尽管有许多可用的全系统模拟器,无论是开源的(例如 QEMU [4] , ARM-Iss [18] 或 MARSSx86 [22])还是采用商业许可证的(例如 Simics [19]),有关指令集模拟器中中断处理的论文却很少发表 [7]。
较早版本的 QEMU 使用了零开销中断检查机制,但该机制存在严重的竞态条件问题。然而,后续版本(包括我们对比所用的版本)通过在每个基本块的开头插入检查来解决了这些问题。但由于我们采用了基于[6, 25, 27] 中所述技术的方法,我们的动态二进制翻译整体性能平均仍快 3.4x。
ARM-Iss [18] 是一种针对 ARM 架构的指令集模拟器。它基于解释执行模型,并增加了指令缓存。解释型指令集模拟器比本文讨论的动态二进制翻译指令集模拟器性能数量级更慢。ARM-Iss 在每条指令执行后都会检查待处理中断。尽管这种方式具有较高的准确性,但进一步加剧了该系统的性能损失。
MARSSx86[22]是一款用于x86处理器的完全系统模拟器。在底层,MARSSx86使用QEMU进行功能仿真,并使用 PTLsim进行周期精确建模,通过将x86指令分解为类似RISC的 μ‐操作码,并利用基本块缓冲区形成x86 μ‐操作码的轨迹。MARSSx86会延迟向CPU发出的中断,直到CPU进入在操作码提交边界定义的稳定状态为止。一旦中断被发送给CPU,MARSSx86便从详细仿真切换到功能仿真,以正确解码该中断。仿真模式会设置正确的CPU上下文来处理中断,但不会开始执行中断处理程序。在正确CPU上下文建立完成后,MARSSx86再切换回详细仿真,并开始在内核模式下仿真中断处理程序代码。由于采用了周期精确方法,MARSSx86中的中断处理是精确的,但其运行速度仅约为每秒20万次指令提交(KIPS),大约比本文提出的(指令精确)动态二进制翻译指令集模拟器慢1000倍。
已在[7]中提出了一种改进的机制,用于在周期精确模拟器中精确模拟中断。该模拟器推测性地执行被模拟处理器的指令,假设不会发生中断。在恢复点处验证此假设,如果确实发生了中断,则将处理器状态回滚到之前的恢复点。尽管这种方法在加速周期精确模拟方面是有效的,但对于高速功能级指令集模拟器而言,其开销仍然过高。
一种基于 COTSon [1] 的软件模拟器,能够以每秒数十 MIPS 的速度忠实地模拟 x86 硬件,已在 [24] 中被描述。但其中未提供有关中断处理的详细信息。同样,对于 Giano [10],、 SimFlex [12] 或 Graphite [21],也未进一步说明中断检查的策略。 Gem5[5] 由于致力于支持周期精确模拟,因此执行每条指令的中断检查。
5.1 虚拟机
在指令集模拟器中的中断检查与Java虚拟机中的异常处理有某种关联。Java异常是同步的,即它们与当前执行的指令相关,而不是由外部触发。[17]中介绍了在即时编译期间处理Java异常的两种技术,即对异常处理程序进行按需翻译和异常处理程序预测。在我们的系统中,我们实现了一种类似于按需翻译的技术,其中中断处理程序的翻译会延迟到中断真正发生时才进行。然而,这通常是动态二进制翻译的一般性需求,而非中断处理所特有的。
一个显著的例外是 JikesRVM [14] Java 虚拟机中让步点的实现,其中在方法序言、方法尾声以及回边上插入了中断检查。这些检查的插入是为了促进 Java 线程的用户空间调度。但自版本 3.1.0 起已被弃用,转而支持原生线程。JikesRVM 在方法前言和结尾处以及目标为向后的控制转移指令(例如 if)处插入让步点。然而,该技术不适用于我们的动态二进制翻译,因为我们是通过构建一个区域的控制流图来动态发现正在执行代码的结构,并且必须考虑自修改代码的可能性。
6. 总结与结论
本文提出了一种优化方案,用于在基于区域的动态二进制翻译的全系统指令集模拟器中高效地放置异步中断检查。我们的技术能够检测任意结构和嵌套层次的控制流循环,并插入接近最少数量的中断检查。该技术通过保证在任何外层循环的每次迭代中至少执行一次待处理中断的检查,从而确保正确性。平均而言,与在每个基本块末尾检查中断的方案相比,我们在ARM模拟器中将动态中断检查的数量减少了73%。尽管中断检查频率降低,但由于中断检查之间的代码优化机会增加,服务中断的延迟反而降低了26%。我们还表明,在全系统仿真中,相较于最先进的QEMU,我们在一系列基准测试中I/O吞吐量提高了1.6x,仿真性能提高了3.4x,保持了性能优势。
6.1 未来工作
我们打算研究中断检查的精确放置对生成代码质量的影响。例如,将中断检查插入循环条件块而非循环体中,是否能使优化器生成更优的代码?我们还将研究同步异常/中断的优化机会,特别是由内存指令引起的异常,这类异常经常遇到。
基于区域的异步中断优化
681

被折叠的 条评论
为什么被折叠?



