6

我写了一个简单的程序,它在一个循环中执行一堆 NOP 指令,令我惊讶的是它每秒执行大约 10600000000 条,或大约 10Ghz,而我的 CPU 只有 2.2GHz。

这怎么可能?CPU是把它们当作一个单一的mega-NOP,还是我刚刚发现“指令级并行”是什么意思?

每秒指令的更好度量是什么?执行添加指令仅达到 414900000/s,是我的 CPU 报告的 bogomips 的十分之一:4390.03

C代码:

#include <stdio.h>
#include <stdint.h>
#include <time.h>

#define ten(a) a a a a a a a a a a
#define hundred(a) ten(a) ten(a) ten(a) ten(a) ten(a) ten(a) ten(a) \
        ten(a) ten(a) ten(a)

#define ITER 10000000
int main(void) {
  uint64_t i=0;
  uint64_t t=time(NULL);
  while(1) {
    for(int j=0; j<ITER;j++) {
    hundred(asm volatile ("nop");)
    }
    i+=ITER*100;
    printf("%lu/%lu\n", i, time(NULL)-t);
  }
  return 0;
}

编译的程序集:

    .file   "gbloopinc.c"
    .section    .rodata
.LC0:
    .string "%lu/%lu\n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movq    $0, -16(%rbp)
    movl    $0, %edi
    call    time
    movq    %rax, -8(%rbp)
.L4:
    movl    $0, -20(%rbp)
    jmp .L2
.L3:
#APP
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
# 15 "gbloopinc.c" 1
    nop
# 0 "" 2
#NO_APP
    addl    $1, -20(%rbp)
.L2:
    cmpl    $9999999, -20(%rbp)
    jle .L3
    addq    $1000000000, -16(%rbp)
    movl    $0, %edi
    call    time
    subq    -8(%rbp), %rax
    movq    %rax, %rdx
    movq    -16(%rbp), %rax
    movq    %rax, %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    jmp .L4
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits
4

2 回答 2

5

这与多核无关。核心不是“端口”。


每个时钟 4 个 NOP 是您的超标量/无序 CPU 的问题/停用流水线宽度。NOP 甚至不需要执行单元/执行端口(ALU 或加载或存储),因此您甚至不受整数执行单元数量的限制。甚至 Core2(英特尔的第一个 4 宽 x86 CPU)每个时钟也可以运行 4 个 NOP。

如您所料,这是指令级并行的一个示例。NOPs 当然没有输入依赖。

在您的 Sandybridge CPU(每个内核有 3 个 ALU 执行单元)上,每个时钟可以运行 3 个 ADD 和一个加载或存储指令,因为它的流水线宽度是 4 微指令。请参阅Agner Fog 的 microarch pdf和 标签 wiki中的其他链接。在独立的 ADD 指令流上,例如

add  eax, eax
add  ebx, ebx
add  ecx, ecx
add  edx, edx
...

您会在 SnB 上看到大约 3 个时钟吞吐量,这是整数 ALU 执行端口的瓶颈。Haswell 可以在每个时钟 4 个 ADD 上运行它,因为它有一个可以处理非向量整数操作(和分支)的第四个 ALU 执行端口。

乱序 CPU 通常具有比执行单元数量更宽的前端和发布/退休宽度。只要有一个空闲的执行单元,就有更多的指令被解码并准备好执行,从而提高了它们的利用率。否则,如果由于串行依赖关系而导致执行停止或减慢,无序机器只能提前看到当前正在执行的内容。(例如add eax,eax/add eax,eax需要第一个 add 的输出作为第二个 add 的输入,因此每个时钟只能运行一个 insn。)

于 2016-09-23T00:53:18.627 回答
4

我将详细介绍 HansPassant 的评论。

现代处理器既是超标量的又是多核的。很容易理解什么是多核处理器——它有多个内核。另一方面,超标量需要更多关于硬件的知识。这是一个 stackexchange 问题,解释了处理器超标量意味着什么。超标量处理器在同一个内核中有许多功能单元,并且是大量流水线的。这就是为什么可以在单个内核中同时调度和执行多条指令的原因。以下是处理器中的一些功能单元:整数加法/减法、浮点乘法、浮点除法、整数乘法、整数除法。

我鼓励您在 Google 上了解有关超标量处理器的更多信息,并特别查找有关您的处理器的更多信息。

于 2016-09-22T18:18:39.660 回答