Rod*_*ddy 18 performance x86 assembly cpu-architecture cpu-speed
当我以前编写嵌入式系统和早期的8/16位PC(6502,68K,8086)时,我非常好地处理了每条指令执行的时间(以纳秒或微秒为单位).根据系列,一个(或四个)周期等同于一个"内存提取",并且无需担心缓存,您可以根据所涉及的内存访问次数猜测时序.
但是对于现代CPU,我很困惑.我知道它们的速度要快得多,但我也知道,如果不知道每条指令需要多少个时钟周期,标题千兆赫速度就无济于事.
那么,任何人都可以为两个示例指令提供一些时序,比如说(2)Core 2 Duo.最好和最坏的情况(假设缓存中没有任何内容/缓存中的所有内容)将是有用的.
指令#1:将一个32位寄存器添加到第二个.
指令#2:将32位值从寄存器移到存储器.
编辑:我之所以要这样做是为了尝试开发一个"经验法则",这样我就可以查看简单的代码并粗略估计所需的时间到最接近的数量级.
编辑#2:有趣点的答案很多,但没有人(还)已经记下了及时测量的数字.我很欣赏这个问题有"复杂性",但是来吧:如果我们可以估算出纽约市钢琴调音师的数量,我们应该能够估计代码运行时间......
采取以下(哑)代码:
int32 sum = frigged_value();
// start timing
for (int i = 0 ; i < 10000; i++)
{
for (int j = 0 ; j < 10000; j++)
{
sum += (i * j)
}
sum = sum / 1000;
}
// end timing
Run Code Online (Sandbox Code Playgroud)
我们如何估计运行多长时间... 1飞秒?1千兆?
Tal*_*eff 40
你提到的现代处理器如Core 2 Duo都是超标量和流水线.它们每个核心有多个执行单元,实际上每个核心一次处理多个指令; 这是超标量部分.流水线部分意味着从读取和"发出"指令到完成执行时存在延迟,并且该时间根据该指令与同时移动通过其他执行单元的其他指令之间的依赖性而变化.因此,实际上,任何给定指令的时序取决于它周围的内容和依赖的内容.这意味着给定指令具有基于许多因素的最佳情况和最差情况执行时间.由于多个执行单元,您实际上可以有多个指令完成每个核心时钟的执行,
以上所有内容都是从CPU内核本身的角度出发的.然后,您与缓存进行交互,并与其他核心争用带宽.CPU 的总线接口单元处理将指令和数据输入到内核中,并通过高速缓存将结果从内核返回到内存.
用一粒盐做的粗略的数量级经验法则:
HUA*_*UAH 14
几乎不可能以对您有用的方式提供您期望的准确计时信息.
以下概念影响指令时序; 有些可能随时变化:
如果您需要有关上述概念的任何进一步说明,请查阅有关现代计算机体系结构的书
测量代码速度的最佳方法是(惊喜!)来衡量运行相同工作负载的代码的速度,以及在"在现实世界中"时所处的相同条件.
使用主要基于英特尔奔腾架构的描述来缩短一个非常长的故事:
由于指令的时间取决于周围的指令,因此在实践中,通常最好对代表性的代码进行计时而不是尝试并担心单个指令.然而:
因此,例如,如果,例如,浮点加法和乘法指令的吞吐量均为2且延迟为5(实际上,为了将其加倍,我认为),这意味着向自身添加寄存器或将其乘以本身可能需要两个时钟周期(因为没有其他相关值),而添加它之前的乘法结果将需要或稍微少于2 + 5个时钟周期,具体取决于您开始/结束时序的位置,以及在各种其他事情上.(在某些时钟周期中,可能会发生另一个加/复运算,因此可以说你实际上有多少个周期归属于单个add/mutliply指令......)
哦,就像一个具体的例子.用于遵循Java代码
public void runTest(double[] data, double randomVal) {
for (int i = data.length-1; i >= 0; i--) {
data[i] = data[i] + randomVal;
}
}
Run Code Online (Sandbox Code Playgroud)
Hotspot 1.6.12 JIT-将内循环序列编译为以下英特尔代码,包括数组中每个位置的加载 - 存储(在这种情况下,'randomVal'保存在XMM0a中):
0b3 MOVSD XMM1a,[EBP + #16]
0b8 ADDSD XMM1a,XMM0a
0bc MOVSD [EBP + #16],XMM1a
0c1 MOVSD XMM1a,[EBP + #8]
0c6 ADDSD XMM1a,XMM0a
0ca MOVSD [EBP + #8],XMM1a
...
Run Code Online (Sandbox Code Playgroud)
每组加载 - 添加存储似乎需要5个时钟周期.
现代处理器做的事情更棘手.
乱序执行.如果可以在不影响正确行为的情况下执行此操作,则处理器可以按照与程序中列出的顺序不同的顺序执行指令.这可以隐藏长时间运行指令的延迟.
注册重命名.处理器通常在其指令集中具有比可寻址寄存器更多的物理寄存器(所谓的"架构"寄存器).这可以是为了向后兼容,或者只是为了实现有效的指令编码.当程序运行时,处理器将"重命名"它使用的架构寄存器,以便任何物理寄存器都是空闲的.这允许处理器实现比原始程序中存在的更多的并行性.
例如,如果您在EAX和ECX上有很长的操作序列,然后是将EAX和ECX重新初始化为新值并执行另一个长序列操作的指令,则处理器可以为这两个任务使用不同的物理寄存器,并执行他们并行.
英特尔P6微架构同时执行无序执行和寄存器重命名.Core 2架构是P6的最新衍生产品.
要真正回答您的问题 - 面对所有这些架构优化,您基本上无法手动确定性能.
你所要求的那种预测是没有希望的.
如果你想要一个经验法则,这里有一些经验法则:
在从2级缓存中获取单词所需的时间内,处理器可以执行至少10条指令.所以担心内存访问,而不是指令计数---寄存器中的计算几乎是免费的.
在从RAM中获取字的时间内,处理器可以执行数千条指令(根据硬件的详细信息,这个数字会变化几个数量级).确保只在冷缓存上发生这种情况; 否则没有其他问题.
如果您在x86 CPU上运行,则没有足够的寄存器.尽量不要在代码中包含超过5个实时变量.或者更好的是,转移到AMD64(x86_64)并将寄存器数量增加一倍.有16个寄存器和寄存器中传递的参数,您可以放弃担心寄存器.
曾经有一段时间我会问建筑师我应该使用哪些经验法则来预测编译器生成的代码的成本.我已经停止了,因为上次我收到一个有用的答案是在1999年.(答案是"确保你的循环适合重新排序缓冲区".所有知道重新排序缓冲区的人现在都可以举手了.如果您可以在当前使用的任何计算机上发现重新排序缓冲区的大小,请指向.)
您可以在此处下载 Intel 64 和 IA-32 手册。
他有很多附加信息,例如他的手册“指令表:Intel 和 AMD CPU 的指令延迟、吞吐量和微操作故障列表”。
或者测试时钟周期计数程序(他使用时间戳计数器)。
这只回答了你的部分问题,但是我发现维基百科的这张表在参考地点很有帮助.它使用大约2006次来描述内存层次结构的不同级别的访问速度和内存量: