我的 CPU 可以在每个 CPU 周期执行多个 NOP 吗?

标签 c performance assembly no-op

我写了一个简单的程序,在循环中执行一堆 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 个 NOP 是您的超标量/乱序 CPU 的问题/报废流水线宽度。 NOP 甚至不需要执行单元/执行端口(ALU 或加载或存储),因此您甚至不受整数执行单元数量的限制。甚至 Core2(英特尔的第一个 4 宽 x86 CPU)也可以每个时钟运行 4 个 NOP。

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

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

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

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

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

关于我的 CPU 可以在每个 CPU 周期执行多个 NOP 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39643882/

相关文章:

c - 使用 C 从 LibreDWG 指针读取数据到指针结构时出现段错误

c - LED 8x8 的程序在 Arduino Uno 中不起作用。我没有使用 pinMode() 方法。我正在使用另一种方法进行规范

linux - 如何跟踪 arm 处理器上的内存访问

ios - 更少的 Storyboard序列和更多的代码是否会使应用程序更快/更小?

java - 预热高吞吐量 Java 应用程序

linux - mov byte [esi +9], bl 导致段错误

C 编译器错误 : unknown type name '__evenaccess'

assembly - x86 - 交换 2 个寄存器的内容

c++ - 堆栈/帧指针作为外部变量

java - 调试Android SDK库