虚拟化环境下 Windows IO 性能调优

阅读数:74 2019 年 11 月 13 日 17:48

虚拟化环境下Windows IO性能调优

随着云计算技术与服务的发展和进步,越来越多的客户选择将业务部署到云端。但由于引入了虚拟化层,在业务部署过程中经常会遇到 IO 问题,通常也不易调试。本文主要介绍利用 perf、systemtap 等工具,帮助一位托管云客户调试 IO 性能问题,来分析虚拟环境下 Windows IO 的性能。

问题出现

有一次,托管云客户自己搭建了虚拟化环境,在同一台宿主机上创建 Windows 2008 R2 和 Centos6.5 虚拟机,用 fio 分别测试其随机读性能,Windows 2008 R2 的 IOPS 大约在 18K,而 Linux 的 IOPS 却可以达到 100K 左右。

客户测试用的 fio 配置

复制代码
[global]
ioengine=windowsaio
direct=1
iodepth=64
thread=1
size=20g
numjobs=1
[4k]
bs=4k
filename=d:test.img
rw=randread

测试结果

虚拟化环境下Windows IO性能调优

复制代码
win_fio1

云主机 IO 栈

虚拟化环境下Windows IO性能调优

复制代码
io stack

云主机环境下,整个 IO 栈相对较长,涉及到 Guest OS 中的应用层 / 文件系统 /Block 层以及驱动层、虚拟化层、宿主机 OS 文件系统 /Block 层以及驱动层。因为涉及面多,所以其中任何一个环节出现问题都会造成性能下降,也为做 IO 的 Tracing 增加了难度。

从这次得到的信息来看,首先排除了宿主机文件系统和 Block 层以及驱动层的问题,因为同样情况的配置,Linux 系统并没有问题。

所以目前主要集中于两点。

1.Guest OS(Windows 系统)
2.fio 程序
3. 文件系统 /Block layer
4.VirtIO Block 驱动 虚拟机为 Guest OS 提供的是 Virtio Block 设备
5.QEMU

如何排除 QEMU 的嫌疑?

对于 IOPS 的性能问题,很容易想到两种可能性:

1.IO 延时过高
2. 设备支持 IO 队列太短

在队列的问题方面,Linux 和 Windows 虚拟机对应的 Virtio Block 设备都是一样的,那么就需要确认延时问题。

QEMU 完成 Block IO 花了多长时间?

幸运的是,Stefan Hajnoczi 已经为 QEMU 添加了 Tracing 的特性,因此可以很方便地统计出 QEMU 从接收到一个 IO 请求到完成所用的具体时长。

虚拟化环境下Windows IO性能调优

从上述统计来看,平均 IO 完成时间在 130 us,由此暂时排除 QEMU 层造成太高延时的影响。另外,如果关注这种动态 Tracing 的 overhead,从测试观察上大致接近 20%。

排除队列和延时问题,可能造成影响的也只有 Guest OS 了。

1.VirtIO Block 驱动的问题?
至少更新到最新稳定版本的 Virtio-Win 驱动,仍然存在同样的问题。

2.Windows 文件系统 /Block 层的问题?
原生 Windows 系统在确认后并没有做任何配置上的修改。

fio 测试程序的问题

为什么 Linux 上 fio 没有问题呢?

两种可能性

在性能排查过程中,总是很容易陷入死局,经常会问到底是哪儿出了问题?因此一切可能影响的因素似乎都没有做任何变动。从经验来看,大部分性能问题都可以分成两种可能:

1.on CPU
2.off CPU

重新来看这个问题 ,在基本排除 IO 延时问题后,对应的问题还有两种可能性:

1.CPU 极其忙碌,但是大部分时间并不是在做 IO 处理;
2.CPU 经常处于空闲状态,那相应的也没有主要在处理 IO。

注:之所以说到目前为止并不能排除 IO 延时的影响,是因为只排除了 QEMU Block 层可能的影响,但是还有 Guest OS(这次暂时忽略 Guest OS)。

先看测试过程中,虚拟机的 CPU 消耗情况。

复制代码
top -H -p 36256

虚拟化环境下Windows IO性能调优

复制代码
win_fio1

从上图来看,QEMU 主线程的 CPU 负载已经达到 90%以上,似乎符合 on CPU 类问题。通常来说,解决这类问题最好的办法就是用 perf 进程采样,然后生成火焰图,因为首先查看 CPU 具体消耗在什么地方是一个不错的选择。

复制代码
perf record -a -g -p 36256 sleep 20

生成火焰图:

虚拟化环境下Windows IO性能调优

复制代码
win2008-bad

可以清楚的看到,CPU 大部分消耗都是 KVM 的操作,其中最主要的消耗是 vmx_handle_exit。(真实的火焰图是一个矢量图,用浏览器查看很容易确认)。这里引起 vmx_handle_exit 主要有两点:

1. 访问 IO Port(handle_pio)
2. 访问 MMIO(handle_apic_access)

既然 KVM 模块占了大部分,那就更希望了解测试时 KVM 的真实行为,通过另一个工具 (kvm_stat) 可以达到。

虚拟化环境下Windows IO性能调优

复制代码
kvm_pio

除 VM Entry 和 VM Exit 事件外,最高的就是 kvm_pio 和 kvm_mmio,说明 Windows 确实有大量 IO Port 和 MMIO 操作,这也验证了在火焰图上所得出的结论。

在虚拟化里,IO Port 或者 MMIO 都可能引起 VM Exit,甚至是 Heavy Exit。如果需要改善性能,一般都会尽量避免这种情况,至少避免 Heavy Exit。

具体访问哪些 IO Port 和 MMIO 导致的 VM Exit?

对于这个问题,KVM 模块已经加了很多 trace event,上面的 kvm_stat 也是利用这些 trace event,只是并没有把具体 trace event 信息打印出来。为了获取 trace-event 的信息,有很多前端工具,如 trace-cmd、perf,都是不错的选择。

查看所有 kvm 模块的 trace event。

复制代码
[xs3c@devhost1 ]# trace-cmd list -e | grep kvm
kvmmmu:kvm_mmu_pagetable_walk
kvmmmu:kvm_mmu_paging_element
kvmmmu:kvm_mmu_set_accessed_bit
kvmmmu:kvm_mmu_set_dirty_bit
kvmmmu:kvm_mmu_walker_error
kvmmmu:kvm_mmu_get_page
kvmmmu:kvm_mmu_sync_page
kvmmmu:kvm_mmu_unsync_page
kvmmmu:kvm_mmu_zap_page
kvm:kvm_entry
kvm:kvm_hypercall
kvm:kvm_pio
kvm:kvm_cpuid
kvm:kvm_apic
kvm:kvm_exit
kvm:kvm_inj_virq
kvm:kvm_inj_exception
kvm:kvm_page_fault
kvm:kvm_msr
kvm:kvm_cr
kvm:kvm_pic_set_irq
kvm:kvm_apic_ipi
kvm:kvm_apic_accept_irq
kvm:kvm_eoi
kvm:kvm_pv_eoi
kvm:kvm_write_tsc_offset
kvm:kvm_ple_window
kvm:kvm_vcpu_wakeup
kvm:kvm_set_irq
kvm:kvm_ioapic_set_irq
kvm:kvm_ioapic_delayed_eoi_inj
kvm:kvm_msi_set_irq
kvm:kvm_ack_irq
kvm:kvm_mmio

KVM 模块添加了许多 trace event 的点,这里只抓起其中两个:kvm:kvm_pio 和 kvm:kvm_mmio。

虚拟化环境下Windows IO性能调优

复制代码
trace-cmd-pio-mmio

通过统计发现主要访问的:

1.IO Port 是 0x608 和 0xc050
2.MMIO 是 0xFEE003xx

经由 qemu info mtree 命令,可以查看 IO Port 608、c050 以及 FEE003xx 分别对应的具体设备。

IO Port

复制代码
0000000000000608-000000000000060b (prio 0, RW): acpi-tmr
000000000000c040-000000000000c07f (prio 1, RW): virtio-pci

MMIO

复制代码
00000000fee00000-00000000feefffff (prio 4096, RW): icc-apic-container

c050 可以忽略,这个被 Virtio Block 来做 VM Exit。

到目前为止,可以判断出 windows 大量读取 ACPI Power Manager Timer 以及访问 APIC 寄存器,进而导致过多 vm exit 产生,消耗大量 CPU 资源,因此就可以具体讨论两个问题:

1. 如何减少读取 ACPI PM Timer 寄存器而引起的 VM Exit;
2. 如何减少访问 APIC MMIO 导致的 VM Exit。

如何减少读取 ACPI PM Timer 而引起的 VM Exit?

从虚拟化层优化的思路来说,减少 IO Port 引发的 VM Exit 通常会考虑是否可以利用 Paravirtulization 替换 Full-virtualization 以达到目的,来看 Windows 在这方面是如何做的。

从 Windows 7 开始,微软为了使 Windows 操作系统能够在 HyperV 得到更好性能,特意为 Windows 系统做了很多虚拟化方面的增强工作,其中就包括这里可以利用到的 HyperV Timer,这个特性类似于 Linux 中的 kvmclock。

从当前的支持情况来看:

1.Windows 7
2.Windows 7 SP1
3.Windows Server 2008 R2
4.Windows Server 2008 R2 SP1/SP2
5.Windows 8/8.1/10
6.Windows Server 2012
7.Windows Server 2012 R2

这些 Windows 系统都包含虚拟化增强功能,更多的信息在微软官方网站。

2014 年,RedHat 工程师 Vadim Rozenfeld 和 Peter Krempa 分别为 qemu 和 libvirt 添加了 HyperV Timer 的支持,所以可以直接通过 libvirt 使能 HyperV Timer。

复制代码
<clock ...>
<timer name='hypervclock' present='yes'/>
</clock>

另外,KVM 里很早也支持了 HyperV Timer,只是客户的宿主机内核版本并不支持该功能,所以需要为客户升级 UCloud 自己维护的内核版本。

如何减少 APIC ACCESS 而引起 VM Exit?

Intel CPU 也已经支持 apic-v,同样升级到 UCloud 自己维护的内核版本来解决。

最终效果

虚拟化环境下Windows IO性能调优

复制代码
win-fio-good

虚拟化环境下Windows IO性能调优

复制代码
win-good

总结

从这个案例可以看出,跟物理环境相比,在虚拟化环境下,Windows IO 性能较差时,并不一定真正是 IO 路径出现问题,可能是一些虚拟化性能的问题对 IO 性能造成了很大影响。

本文转载自公众号 UCloud 技术(ID:ucloud_tech)。

原文链接:

https://mp.weixin.qq.com/s/nkfSoeV2zbgiw5wInwFPmw

评论

发布