如何测量物理机上VMFUNC指令运行所需的CPU周期数
x86物理机Ubuntu 18.04下测量VMFUNC指令CPU周期数的准确方法
前置说明:VMFUNC是VMX虚拟化扩展下的非根模式专属指令,直接在普通裸机环境的ring0/ring3下执行会触发#UD(未定义指令)异常,必须先将CPU切入VMX非根运行模式、提前在VMCS中开启对应VMFUNC子功能的硬件支持,才能正常执行该指令,不存在无虚拟化环境直接跑通的可能。
- 基础计时方案选择
直接用lfence+rdtscp指令组合做周期计数,不要用普通rdtsc——rdtsc不是序列化指令,会被CPU乱序执行重排位置,导致计数结果偏差极大。rdtscp本身具备部分序列化能力,搭配前后的lfence可以完全保证指令执行顺序,把计数点牢牢卡在VMFUNC指令的前后,避免乱序干扰。 - 测量前的环境校准(这步不做结果基本不准)
- 用
sched_setaffinity系统调用把测试进程绑定到单个物理CPU核心,关闭该核心的睿频、动态频率调节、节能模式,将CPU主频锁定在固定值;同时关闭超线程,避免同物理核的其他逻辑核抢占执行资源带来波动。 - 测试时优先在内核态执行测量代码,用
local_irq_disable关闭本地核心中断,完全避免中断打断测量流程;如果一定要在用户态测,就把进程设为SCHED_FIFO最高实时优先级,缩短单次测量窗口,后续过滤异常样本。 - 先测基准开销:写和正式测量完全一致的代码流,仅把中间的
vmfunc指令去掉,跑1000次以上取中位数,得到计数、序列化、寄存器压栈出栈的固定开销,最终VMFUNC的实际周期数 = 总测量值 - 基准开销。 - 正式测量前先空跑100次以上VMFUNC做预热,把TLB、缓存相关的冷启动开销消掉。
- 用
- 核心测量代码编写要点
直接写C内联汇编实现核心测量逻辑,不要套任何第三方性能库,代码段越短越好:- 先执行
lfence做指令序列化,调用rdtscp拿到第一次的64位周期计数,存入通用寄存器暂存; - 提前给EAX寄存器赋值要调用的VMFUNC子功能号(最常用的子功能0是EPTP切换,ECX必须置0,否则会触发异常),执行
vmfunc指令; - 再次执行
lfence做序列化,调用rdtscp拿到第二次的64位周期计数,两次计数的差值就是单次测量的总周期数。
编译时开O2优化,同时给核心汇编段加volatile标记,避免编译器优化掉指令、或者重排指令顺序。
- 先执行
- 数据处理与避坑
- 连续测至少10000次样本,把数值明显偏离集群的异常样本(一般是被中断、调度、微码干扰产生的超大值)全部剔除,最终取样本的中位数作为结果,不要取平均值——平均值很容易被少量异常样本带偏。
- 不要在未修改的通用KVM/Xen虚拟机里测VMFUNC开销:大部分通用hypervisor默认会拦截VMFUNC做软件模拟,模拟开销比硬件原生执行高几十到上百倍,结果完全没有参考价值。要测硬件原生执行的开销,要么自己写极简VMX宿主壳,只配置最基础的VMCS、开启VMFUNC硬件直通不设拦截,切入非根模式跑测量代码;要么修改KVM配置开启VMFUNC的硬件透传,保证VMFUNC执行时不会陷出到hypervisor。
内容的提问来源于stack exchange,提问作者hsyhhh
相关产品推荐
相关产品推荐

