Win10专业版下新指令集使用技巧
在我之前的博客文章“问题:软件实际上是否使用新的指令集?”中,我观察了几种不同的Linux *设置使用的指令种类,以及每个设置如何通过更改所运行的处理器的类型而受到影响。作为该职位的后续工作,现在我已经为Microsof t Windows 10做了同样的工作。在这篇文章中,我将介绍Windows 10如何在各个处理器之间运行,以及它的行为与Ubuntu * 16相比如何。回到Ubuntu来查看不同使用场景下的指令使用情况。
实验装置
就像以前一样,我在WindRiver®Simics®虚拟平台工具中使用了我们的“通用PC”平台,并使用两种不同的处理器模型运行它。一个型号是英特尔®酷睿™i7第一代处理器(代号“Nehalem”),另一个型号是英特尔酷睿i7第六代处理器(代号“Skylake”)。
使用这些不同的模型,我启动了Windows 10映像(准确地说,构建1511)。就像以前一样,我在开机的第一个60秒内跑完了,最后在一个闲置的桌面上。
下面是从我的笔记本电脑启动几个Windows 10目标收集统计数据的截图:
在启动过程中,我使用Simics工具环境来收集所看到指令类型的统计信息。 我计算每个助记符的指令使用情况,就像在上一篇文章中一样。 另外,我在下面讨论的基础上根据其他标准看了指令流。
跨越世代的指示
首先,我查看了引导过程中出现的百分之一或更多动态指令的所有指令。 结果如下图所示:
我们在Windows数据中看到的内容与我们在前一篇博文中看到的Ubuntu 16非常相似。处理器世代之间有一些小的变化,但大多数相同的代码运行,无论处理器。
对于像Ubuntu和Windows这样广泛使用的通用操作系统发行版来说,这并不是那么出乎意料。在之前的博客文章中,处理器世代间最显着的差异是Yocto * Linux版本,这是有道理的。
Yocto可以让你为自己构建一个Linux,并且在包含使用新指令集的代码方面,它的默认值可以更加激进,因为你通常不需要支持广泛的用户群。对于Ubuntu和Windows 10来说,它们拥有广泛的用户基础和共同的目标,可以为大量的用户提供可靠的工作,硬件世代之间的差异太大会使得测试和质量控制变得更加困难。不像你在自己的Linux上运行时那样,不要为了一个单一的系统而积极地进行优化。
无论如何,如果我们看看使用的指令,最常见的指令是移动,比较,跳转和基本算术。这与我们在Linux上看到的非常相似,但使用的确切指令确实有点不同。
比较Linux
当我们在这里改变操作系统的时候,编译器用来构建代码的变化(从Linux *上的gcc到Windows上的Microsoft *编译器),以及关于如何完成函数调用和操作系统调用的约定。所有这些都会影响编译器中的指令选择过程,因此在工作负载之间使用的实际指令可能会有所不同。事实上,我们甚至可以看到一些工作负载中唯一使用的指令。在我们当前的数据集中有一些这样的例子。
Windows 10从不使用LEAVE或ENTER指令。正如我以前的博客文章中所讨论的那样,旧的Linux 2.6 Busybox *安装程序确实使用了LEAVE,但是更新的Linux发行版并没有使用它。由于Windows 10是一个相当新的软件堆栈,所以它甚至不再使用LEAVE指令。
有一些Windows 10使用的说明,但没有一个Linux的设置。其中最重要的是来自英特尔®SIMD流指令扩展指令集2(SSE2)指令集的MOVNTI指令。它占Windows的三分之三以上!另外,除上述最常见的指令之外,Windows 10还使用几种独特的向量指令,而不使用任何Linux变体:PADDW,PSRLW,PMOVZXBW,PUNPCKHQDQ,PSUBW和PMADDWD。鉴于矢量指令集的丰富性,这并不令人惊讶。
Windows(而不是Linux)也使用原始8086指令集的CMC(补充进位标志)指令。这同样适用于80386的BSR(位反向扫描)。它们并不普遍(在小于0.01%的情况下测量),但仍然感兴趣的是,它们从来没有用在Linux引导中。
请注意,这只是关于操作系统启动过程;应用软件的图片可能会有所不同。事实上,我在Linux上进行了一些其他的实验,结果如下所述。
矢量和SIMD
在启动过程中,矢量指令不会被使用太多。 v1和v6处理器之间的区别对于Windows来说并不是特别大 - 在Linux上它更明显。但是,当Windows 10与Ubuntu 16相比时,它变得更有趣了:
总的来说,Windows和Ubuntu在启动过程中使用相同比例的向量指令(大约5%)。 不过,这些矢量指令分布的方式却不尽相同。 Windows使用更多的SSE2指令,而Ubuntu使用更多的MMX指令。 Windows也不会在两代之间改变使用的指令,在v6处理器上几乎没有可察觉的使用英特尔®高级矢量扩展(AVX)。
还有更多的事情
对指令助记符的这种调查实际上只是一个简单的例子,您可以在使用虚拟平台中的工具运行的软件中观察到这一点。 这是相当丰富的,但有更多的可以观察和计数。 作为一个Simics用户,你可以编程工具来收集你想要的任何东西(只要它是虚拟平台的一部分)。
例如,下面是在v6处理器上进行Windows 10启动时指令大小的分配:
每个执行的指令的平均大小约为3.73字节。 请注意,这并没有说明代码的大小。 这代表了代码放在缓存系统和处理器解码器上的压力。 英特尔®架构(IA)是一个经典的可变长度指令集,在这里清楚地看到,指令长度从1字节到14字节不等。 值得注意的是,真正长的指令也是非常罕见的。
分割指令的另一种方法是查看操作数类型以及指令操作码。 这是比上面使用的助记符更细粒度的分割。 例如,MOV指令的20个最常见的变体如下:
这远远不是所有这些...还有很多其他特定的寻址模式正在使用。这是经典的长尾分布:到目前为止,绝大多数的MOV操作是最常见的模式,而更复杂的模式经常使用仍然很重要。
请注意,我们在这里看到所有大小的移动:仅仅因为这是在64位处理器上运行的64位Windows操作系统并不意味着所有的操作实际上是64位大小。字节(8位),字(16位)和双字(32位)操作也正在使用。 32位和64位一样普遍。
在Linux桌面的向量和SIMD
当与我的一位同事讨论这些测量结果时,问题出现在一般的矢量指令和AVX指令以及它们如何非常依赖所使用的工作负载。操作系统引导不太可能将其用于多个小密码以及可能的一些高度优化的存储器复制操作。但是他在交互式地使用系统时看到了一些其他的行为。于是,又做了一个实验,我把Ubuntu的v6处理器放在那里,开机后开始运行一些交互式软件。本质上,打开一个终端,并启动一个新的Firefox进程。
从图中可以看出,桌面活动广泛使用AVX指令 - 甚至包括更新的AVX2指令和FMA3指令。向量指令实际上包含了执行的所有指令的12%以上,并记住这包括整个机器中的所有指令,而不仅仅是用户级代码或图形子系统中的代码。
结论
这是第二篇博客文章,其中有图表和数字,详细介绍了在不同处理器类型的许多不同工作负载中执行的不同类型的指令。这是一个像我这样的计算机架构书呆子的数据集。然而,最有趣的是如何收集数字 - 使用Simics及其仪器功能。 Simics几乎可以模拟任何系统,并允许非侵入式的检查和调试。收集指令统计信息,例如我在这里为处理器设计者,软件工程师,研究人员和学生提供有用的见解。
这里有一个简单的插件:Simics免费提供给大学,它是一个用于计算机体系结构,操作系统,网络,嵌入式系统,仿真,虚拟平台和低级编程等领域的多功能工具。