硬件功能首先,禁用所有硬件性能功能,也就是说要禁用IntelTurboBoost和HyperThreadingfromBIOS/UEFI。正如其官方网页上说的那样,TurboBoost是“一种在处理器内核运行,并可以在低于功耗、电流和温度规格限制的情况下允许它们以高于额定频率的速度运行的技术。”此外,HyperThreading是“一种可以更高效地利用处理器资源的技术,能使每个内核都能多线程运行。”这都是值得大家花钱购买的好东西。那为什么要在性能分析/基准测试中禁用它们呢?因为使用这些技术会让大家无法得到可靠的和可复现的结果。这会让运行过程发生变化。让大家看个小例子primes.py,代码故意写得很糟糕。
这段代码可在GitHub上查看:https://github.com/apatrascu/hunting-python-performance/blob/master/01.primes.py。你需要运行以下命令安装一个依赖包:
pipinstallstatistics
让大家在一个启用了TurboBoost和HyperThreading的系统中运行它:
现在禁用该系统的睿频加速(TurboBoost)和超线程(HyperThreading),然后再次运行这段代码:
看看第一个案例的标准差为15%。这是一个很大的值!假设大家的优化只能带来6%的加速,那大家怎么能将运行过程中的变化(runtorunvariation)和你的实现的差异区分开?相对而言,在第二个例子中,标准差减少到了大约0.6%,大家的新优化方案效果清晰可见。
CPU节能
禁用所有的CPU节能设置,并使用固定的CPU频率。这可以通过在Linux功率调节器(powergovernor)中将intel_pstate改成acpi_cpufreq而实现。intel_pstate驱动使用英特尔内核(SandyBridge或更新)处理器的内部调节器实现了一个缩放驱动。acpi_cpufreq使用了ACPIProcessorPerformanceStates。下面让大家先来检查一下:
可以看到这里所使用的调节器被设置成了节能模式,而CPU的频率范围在1.20GHz到3.60GHz之间。这个设置对日常应用来说是很不错的,但却会影响到基准测试的结果。那么应该给调节器设置什么值呢?如果大家浏览一下文档,大家可以看到大家可以使用以下设置:高性能(performance):以最大频率运行CPU节能(powersave):以最小频率运行CPU自定义(userspace):按用户指定的频率运行CPU按需(ondemand):根据当前负载动态调节频率。可能跳至最高频率,空闲时又会降低保守(conservative):根据当前负载动态调节频率。相比于按需模式,其频率调节更加渐进大家要使用性能调节器(performancegovernor),并将频率设置成CPU支持的最大频率。如下所示:
现在你已经使用性能调节器将频率设置成了固定的2.3GHz。这是最大的可设置的值,没有睿频加速(TurboBoost),它可以被用在XeonE5-2699v3上。要完成设置,请使用管理员权限运行以下命令:
如果你没有cpupower,可使用以下命令安装:
sudoapt-getinstalllinux-tools-commonlinux-header-`uname-r`-y
功率调节器对CPU的工作方式有很大的影响。该调节器的默认设置是自动调节频率以减少功耗。大家不想要这样的设置,所以从GRUB中禁用它。只需要编辑/boot/grub/grub.cfg(但是如果你在kernel升级上很小心,那么这将会消失)或在/etc/grub.d/40_custom中创建一个新的kernel入口。大家的boot行中必须包含这个flag:intel_pstate=disable,如下所示:
linux/boot/vmlinuz-4.4.0-78-generic.efi.signedroot=UUID=86097ec1-3fa4-4d00-97c7-3bf91787be83rointel_pstate=disablequietsplash$vt_handoff
ASLR(地址空间配置随机发生器)
这个设置是有争议的,参见VictorStinner的博客:https://haypo.github.io/journey-to-stable-benchmark-average.html。当偶首次建议在基准测试时禁用ASLR时,那是为了进一步提升对那时在CPython中存在的ProfileGuidedOptimizations的支持。偶为什么要说这个呢?因为在上面给出的特定硬件上,禁用ASLR可以将运行之间的标准差降低至0.4%。另一方面,根据在偶的个人计算机(IntelCorei74710MQ)上的测试,禁用ASLR会导致Victor所提到的同样的问题。在更小的CPU(比如IntelAtom)上的测试会带来甚至更大的运行间标准差。因为这似乎并不是普遍适用的真理,而且很大程度上依赖于硬件/软件配置,所以对于这个设置,偶在启用后测量一次,再禁用后测量一次,之后再进行比较。在偶的机器上,偶通过在/etc/sysctl.conf.中加入以下命令禁用了ASLR。使用sudosysctl-p进行应用。
kernel.randomize_va_space=0
如果你想在运行时禁用它:
sudobash-c’echo0>|/proc/sys/kernel/randomize_va_space’
如果你想重新启用:
sudobash-c’echo2>|/proc/sys/kernel/randomize_va_space’
二、内存分析
大家为什么要关心这个问题?为什么大家不仅仅就关心性能?这些问题的答案相当复杂,但偶会总结出来。PyPy是一个可选的Python解释器,其相对于CPython有一些巨大的优势:速度(通过其JustinTime编译器)、兼容性(几乎可以替代CPython)和并发性(使用stackless和greenlets)。PyPy的一个缺点是因为其JIT和垃圾一样的回收站实现,它通常会使用比CPython更多的内存。但是在某些案例中,其的内存消耗会比CPython少。下面大家来看看你可以如何测量你的应用使用了多少内存。
诊断内存使用
memory_profiler是一个可用来测量解释器运行一个负载时的内存用量的库。你可以通过pip安装它:
pipinstallmemory_profiler
这个工具的优点是它会在一个Python脚本中一行行地显示内存消耗。这可以让大家找到脚本中可以被大家重写的位置。但这种分析有一个缺点。你的代码的运行速度比一般脚本慢10到20倍。怎么使用它?你只需要在你需要测量的函数上直接加上@profile()即可。让大家看看实际怎么操作!大家将使用之前用过的素材脚本作为模型,但做了一点修改,移除了统计部分。代码也可在GitHub查看:https://github.com/apatrascu/hunting-python-performance/blob/master/02.primes-v1.py
开始测量时,使用以下PyPy命令:
pypy-mmemory_profiler02.primes-v3.py
或者直接在脚本中导入memory_profiler:
pypy-mmemory_profiler02.primes-v3.py
在执行完这行代码之后,大家可以看到PyPy得到这样的结果:
大家可以看到这个脚本使用了24.371094MiB的RAM。让大家简单分析一下。大家看到其中大多数都用在了数值数组的构建中。它排除了偶数数值,保留了所有其它数值。大家可以通过调用range函数而对其进行一点改进,其使用一个增量参数。在这个案例中,该脚本看起来像是这样:
如果大家再次测量,大家可以得到以下结果:
很好,现在大家的内存消耗下降到了22.75MiB。使用列表解析(listcomprehension),大家还可以将消耗再降低一点。
再次测量:
大家最后的脚本仅消耗22.421875MiB。相比于第一个版本,差不多下降了10%。