コンテンツにスキップ

第1章 CPUは如何にしてソフトウェアを高速に実行するのか

序文

ソフトウェアのめざましい進化が十分に進歩した技術の中核となっている。そしてそのソフトウェアは計算機(ハードウェア)の上で動いている。 CPU(Central Processing Unit)には50年以上の歴史があり、たくさんの研究者や技術者の創意工夫によって作られてきた。 その構造や動作は非常に複雑であるためか、ソフトウェアプログラマは詳細を知らないことも多い。

本書は、ソフトウェアを高速化するために CPU について知りたいプログラマに向けて書かれた。 そのアプローチは、「命令の流れ」や「クロックサイクル粒度のミクロな振る舞い」に着目して各要因ごとに分解して提示することで CPU の性能についての原理を説明した上で、各種の CPU に共通する高速化のための知見を獲得する、というものである。

Note

Any sufficiently advanced technology is indistinguishable from magic.(十分に発達した技術は魔法と区別がつかない)


by アーサー・C・クラーク(SF 作家)

余談

J. L. Hennessy and D. A. Pattersonによる、『コンピュータアーキテクチャ 第 6 版 定量的アプローチ』(エスアイビー・アクセス, 2019)という本がある。 この本は、CPUやGPUのマイクロアーキテクチャに関するもので、非常に有名である。 が、難しいのとかなり分量がある。 みやじまも学生のころに輪講して、非常に難しかった思い出がある。いつか輪講できるようになるといいな。


第1章

CPUの性能とは?

本書では、以下の式で表されるCPU時間が短いことをCPUの性能が良い、と定義する。 ここでは、CPU時間は処理にかかる時間と同義。

各値は、それぞれ以下のようなもの

  • 実行命令数:アセンブリコードの行数と同義で、プログラムの書き方やコンパイラの善し悪し、CPUの命令セットアーキテクチャに左右される。
  • プログラム:プログラムサイズやプログラムの行数と同義で、プログラマの能力やプログラムの書き方に左右される。行数が少ないことが常に良いとは限らないが、CPU時間の観点では少ないことが良い。
  • クロックサイクル数:動作周波数と同義で、CPUで3GHz程度、GPUで1.5GHz程度。消費電力の都合でここ10年くらいはあまり動作周波数は上がっていない。ほぼ固定値
  • 秒数:CPUを地球で動かしているかぎり普遍なので、一般人にはどうしようもない

各項は、それぞれ以下のように説明できる。

  • 第1項目:CPU時間におけるプログラムサイズと実行命令数の影響を示した項。コンパイラやソフトウェアの書き方、命令セットアーキテクチャに左右される。どちらかと言えばソフトウェア的な部分。
  • 第2項目:CPU時間における実行命令数の影響を示した項。CPUのマイクロアーキテクチャ(micro-architecture)と、それをどう使うかに左右される。本書が対象としている部分。
  • 第3項目:CPU時間における動作周波数の影響を示した項。どちらもほぼ固定値なので、結構どうしようもない。半導体プロセスや回路実装技術によって左右される。物理設計の部分。

1.1 CPUはソフトウェアを「命令流」として見ている

人から見たソフトウェアとCPUから見えるソフトウェアは異なる。

  • 人から見たソフトウェア:C、Ruby、Python、JavaScript、Java、Goなどの人間が記述することを前提としたさまざまなプログラミング言語
  • CPUから見えるソフトウェア:アセンブリコードやマイクロコードなどの命令列や、それを時系列に処理する命令流。CPUは、命令を順番に並べたものに基づいてデータの読み書きと加工を行う機械だと言える。

具体的例:二乗するC++プログラムと、そのアセンブリコード。Compiler Explorerで、x86-64向けGCC 14.2でコンパイルした結果。

  • 人から見たソフトウェア

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    #include <iostream>
    
    int square(int num) {
        return num * num;
    }
    
    int main(void){
        int squared_value = square(2);
        std::cout << (squared_value);
    }
    

  • CPUから見えるソフトウェア。上から順番に処理するので、時系列に処理する命令流。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    square(int):
      push  rbp                    ; 現在のベースポインタをスタックに保存
      mov   rbp, rsp               ; スタックポインタをベースポインタにコピー
      mov   DWORD PTR [rbp-4], edi ; 関数の引数(整数)をスタック上のローカル変数に保存
      mov   eax, DWORD PTR [rbp-4] ; ローカル変数の値をeaxレジスタにロード
      imul  eax, eax               ; eaxレジスタの値を自分自身と掛け算(平方計算)
      pop   rbp                    ; ベースポインタを復元
      ret                          ; 関数から戻る
    
    main:
      push  rbp                    ; 現在のベースポインタをスタックに保存
      mov   rbp, rsp               ; スタックポインタをベースポインタにコピー
      sub   rsp, 16                ; スタックに16バイトの領域を確保
      mov   edi, 2                 ; ediレジスタに整数2をセット(square関数の引数)
      call  square(int)            ; square関数を呼び出し
      mov   DWORD PTR [rbp-4], eax ; 関数の戻り値をスタック上のローカル変数に保存
      mov   eax, DWORD PTR [rbp-4] ; ローカル変数の値をeaxレジスタにロード
      mov   esi, eax               ; eaxレジスタの値をesiレジスタにコピー(出力用)
      mov   edi, OFFSET FLAT:std::cout ; std::coutのアドレスをediレジスタにセット
      call  std::ostream::operator<<(int) ; std::coutに整数を出力する関数を呼び出し
      mov   eax, 0                 ; プログラムの戻り値を0にセット
      leave                          ; ベースポインタを復元し、スタックポインタを調整
      ret                            ; main関数から戻る
    

ここから言えることは、

Note

この命令流を高速に"処理"すれば、それだけCPUでソフトウェアを高速に実行できる。具体的には、結果が破綻しないように、命令流や命令列の順番を変更したり、並列に実行したりすることでCPU時間を短縮していく。各命令列は依存関係があったり、それぞれ実行時間が異なるため、これらを考慮して順番を変える必要がある。

1.2 CPUごとにどのような命令があるか

命令セットアーキテクチャ(ISA: Instruction Set Architecture)とは、CPUから見えるソフトウェア、つまり、命令の集合を定義したもの。 ISAはCPUとプログラムの間の境界であり、以下のような要素が含まれる。また、CPU時間や互換性に大きな影響を与える。 ISAはマイクロアーキテクチャとは分離されており、IBM社のSystem/360でこの考え方が導入され、互換性の向上に寄与している。

  1. 命令形式: 各命令がどのようなビットパターンで表現されるかを定義する。オペコード(操作コード)やオペランド(操作対象)が含まれる。
  2. 命令の種類: 算術演算、論理演算、データ転送、制御フローなど、プロセッサが実行できる基本的な操作を定義する。
  3. レジスタセット: プロセッサが持つレジスタの数と種類を定義する。
  4. アドレッシングモード: メモリ内のデータにアクセスする方法を定義する。直接アドレッシング、間接アドレッシング、インデックスアドレッシングなどがある。
  5. データ型: プロセッサが扱えるデータの種類(整数、浮動小数点数、文字列など)を定義する。

現在の代表的な命令セットアーキテクチャは以下の3つ。 CPU ごとに詳細な命令セットアーキテクチャの仕様は異なっているものの、いずれも同程度の粒度の命令を持つに至っている。

  1. Intel/AMD x86:Intel社の「Intel 64 and IA-32 Architecture仕様」、および、AMD社の「AMD64 Architecture 仕様」
  2. Arm v8 AArch64:Arm 社の「Arm Architecture A-profile 仕様」のうち、64 ビット CPU 向けのAArch64 仕様で、中でも現在広く普及している v8 以降のバージョン
  3. RISC-V RV64I:RISC-V International が定義する「RISC-V 仕様」のうち、64 ビット CPU 向けである RV64I 仕様
命令 Arm v8 AArch64 RISC-V RV64I Intel/AMD x86
転送 mov x5, x6 mv x5, x6 mov rax, rbx
加算 add x5, x6, x7 add x5, x6, x7 add rax, rbx
比較 cmp x6, x7 slt x5, x6, x7 cmp rax, rbx
無条件分岐 b target j target jmp target
メモリ読み出し(ロード) ldr x5, [x6] ld x5, (x6) mov rax, [rbx]
メモリ書き込み(ストア) str x5, [x6] sd x5, (x6) mov [rbx], rax

二乗するC++プログラムのARM64向けアセンブリコード。Compiler Explorerで、ARM64向けGCC 14.2でコンパイルした結果。 x86向けのアセンブリコードとちょっと違う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
square(int):
  sub   sp, sp, #16
  str   w0, [sp, 12]
  ldr   w0, [sp, 12]
  mul   w0, w0, w0
  add   sp, sp, 16
  ret
main:
  stp   x29, x30, [sp, -32]!
  mov   x29, sp
  mov   w0, 2
  bl    square(int)
  str   w0, [sp, 28]
  ldr   w1, [sp, 28]
  adrp  x0, _ZSt4cout
  add   x0, x0, :lo12:_ZSt4cout
  bl    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov   w0, 0
  ldp   x29, x30, [sp], 32
  ret

1.3 CPUで人から見えるソフトウェアをそのまま扱っていない理由

現代のCPUは、命令列を「1. 小さな命令の並び」を「2. 時間軸に沿って順番に実行する」という原理で動作するものが主流である。 これは、そのほうが汎用性が高く、高速化や小型化にも向いているからである。 もちろん、FPGAやCGRAなどCPUとは異なる動作原理を持った計算機も存在するが、主流とは言えない。

  1. 小さな命令の並び

現代のCPUは単純で小さな命令を組み合わせるアプローチで、ハードウェア(電子回路)の制御構造や規模を抑えながら、さまざまなプログラミング言語の機能を実現している。 例えば、ループ文は、CPUがループ文命令を持っているわけではなく、ループの終了を判定するための「比較命令」や、ループの先頭の命令列に処理を戻すための「分岐命令」などを組み合わせて実現されている。 ループ構造を電子回路で実現しようとすると結構たいへん。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>
int square(int num) {
    return num * num;
}

int main(void){
    for (int i = 0; i < 10; i++) {
        int squared_value = square(2);
        std::cout << (squared_value);
    }
}
for文はjmp命令で実現される
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
square(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, eax
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 0
        jmp     .L4
.L5:
        mov     edi, 2
        call    square(int)
        mov     DWORD PTR [rbp-8], eax
        mov     eax, DWORD PTR [rbp-8]
        mov     esi, eax
        mov     edi, OFFSET FLAT:std::cout
        call    std::ostream::operator<<(int)
        add     DWORD PTR [rbp-4], 1
.L4:
        cmp     DWORD PTR [rbp-4], 9
        jle     .L5
        mov     eax, 0
        leave
        ret
  1. 時間軸に沿って順番に実行する

コンパイラは処理したい順番に命令を並べた命令列を生成し、CPUはそれを順に実行する。 ループやジャンプ、関数呼び出しなど、並んだ順番以外の命令を実行したい(命令の流れを変更したい)場合には、変更先の命令列の先頭を明示的に指定する命令を作成・実行する。 この単純な方式は、特別な指示がない限り記述順序で後続の命令を次に実行すればよいことから、命令流を高速に処理するうえで著しく有利である。

1.4 プログラムはどのようなCPUの命令列に変換されるか

Compiler Explorerを使わなくても、コンパイル系の言語では命令列を出力できる。

  • GCCの-Sオプションでアセンブリ(.sファイル)を生成して見てみる

Stop after the stage of compilation proper; do not assemble. The output is in the form of an assembler code file for each non-assembler input file specified.
By default, the assembler file name for a source file is made by replacing the suffix .c, .i, etc., with .s.
Input files that don't require compilation are ignored.

gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0でやってみると

1
g++ -S -masm=intel squared.cc
二乗するプログラムのアセンブリコード
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
        .file   "square.cc"
        .intel_syntax noprefix
        .text
#APP
        .globl _ZSt21ios_base_library_initv
#NO_APP
        .globl  _Z6squarei
        .type   _Z6squarei, @function
_Z6squarei:
.LFB1988:
        .cfi_startproc
        endbr64
        push    rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        mov     rbp, rsp
        .cfi_def_cfa_register 6
        mov     DWORD PTR -4[rbp], edi
        mov     eax, DWORD PTR -4[rbp]
        imul    eax, eax
        pop     rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1988:
        .size   _Z6squarei, .-_Z6squarei
        .globl  main
        .type   main, @function
main:
.LFB1989:
        .cfi_startproc
        endbr64
        push    rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        mov     rbp, rsp
        .cfi_def_cfa_register 6
        sub     rsp, 16
        mov     edi, 2
        call    _Z6squarei
        mov     DWORD PTR -4[rbp], eax
        mov     eax, DWORD PTR -4[rbp]
        mov     esi, eax
        lea     rax, _ZSt4cout[rip]
        mov     rdi, rax
        call    _ZNSolsEi@PLT
        mov     eax, 0
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1989:
        .size   main, .-main
        .section        .rodata
        .type   _ZNSt8__detail30__integer_to_chars_is_unsignedIjEE, @object
        .size   _ZNSt8__detail30__integer_to_chars_is_unsignedIjEE, 1
_ZNSt8__detail30__integer_to_chars_is_unsignedIjEE:
        .byte   1
        .type   _ZNSt8__detail30__integer_to_chars_is_unsignedImEE, @object
        .size   _ZNSt8__detail30__integer_to_chars_is_unsignedImEE, 1
_ZNSt8__detail30__integer_to_chars_is_unsignedImEE:
        .byte   1
        .type   _ZNSt8__detail30__integer_to_chars_is_unsignedIyEE, @object
        .size   _ZNSt8__detail30__integer_to_chars_is_unsignedIyEE, 1
_ZNSt8__detail30__integer_to_chars_is_unsignedIyEE:
        .byte   1
        .ident  "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
        .section        .note.GNU-stack,"",@progbits
        .section        .note.gnu.property,"a"
        .align 8
        .long   1f - 0f
        .long   4f - 1f
        .long   5
0:
        .string "GNU"
1:
        .align 8
        .long   0xc0000002
        .long   3f - 2f
2:
        .long   0x3
3:
        .align 8
4:
  • 実行ファイルを逆アセンブル(機械語やバイナリを、アセンブリに変換)する。

objdump displays information about one or more object files. The options control what particular information to display. This information is mostly usefl to programmers who are working on the compilation tools, as opposed to programmers who just want their program to compile and work.

gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0でやってみると

1
2
g++ squared.cc -o ./squared.o
objdump -Mintel -d squared.o
二乗するプログラムの逆アセンブル結果
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
squared.o:     file format elf64-x86-64
Disassembly of section .init:

0000000000001000 <_init>:
    1000:   f3 0f 1e fa             endbr64
    1004:   48 83 ec 08             sub    rsp,0x8
    1008:   48 8b 05 e1 2f 00 00    mov    rax,QWORD PTR [rip+0x2fe1]        # 3ff0 <__gmon_start__@Base>
    100f:   48 85 c0                test   rax,rax
    1012:   74 02                   je     1016 <_init+0x16>
    1014:   ff d0                   call   rax
    1016:   48 83 c4 08             add    rsp,0x8
    101a:   c3                      ret

Disassembly of section .plt:

0000000000001020 <.plt>:
    1020:   ff 35 9a 2f 00 00       push   QWORD PTR [rip+0x2f9a]        # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:   ff 25 9c 2f 00 00       jmp    QWORD PTR [rip+0x2f9c]        # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
    102c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]
    1030:   f3 0f 1e fa             endbr64
    1034:   68 00 00 00 00          push   0x0
    1039:   e9 e2 ff ff ff          jmp    1020 <_init+0x20>
    103e:   66 90                   xchg   ax,ax

Disassembly of section .plt.got:

0000000000001040 <__cxa_finalize@plt>:
    1040:   f3 0f 1e fa             endbr64
    1044:   ff 25 8e 2f 00 00       jmp    QWORD PTR [rip+0x2f8e]        # 3fd8 <__cxa_finalize@GLIBC_2.2.5>
    104a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]

Disassembly of section .plt.sec:

0000000000001050 <_ZNSolsEi@plt>:
    1050:   f3 0f 1e fa             endbr64
    1054:   ff 25 76 2f 00 00       jmp    QWORD PTR [rip+0x2f76]        # 3fd0 <_ZNSolsEi@GLIBCXX_3.4>
    105a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]

Disassembly of section .text:

0000000000001060 <_start>:
    1060:   f3 0f 1e fa             endbr64
    1064:   31 ed                   xor    ebp,ebp
    1066:   49 89 d1                mov    r9,rdx
    1069:   5e                      pop    rsi
    106a:   48 89 e2                mov    rdx,rsp
    106d:   48 83 e4 f0             and    rsp,0xfffffffffffffff0
    1071:   50                      push   rax
    1072:   54                      push   rsp
    1073:   45 31 c0                xor    r8d,r8d
    1076:   31 c9                   xor    ecx,ecx
    1078:   48 8d 3d dd 00 00 00    lea    rdi,[rip+0xdd]        # 115c <main>
    107f:   ff 15 5b 2f 00 00       call   QWORD PTR [rip+0x2f5b]        # 3fe0 <__libc_start_main@GLIBC_2.34>
    1085:   f4                      hlt
    1086:   66 2e 0f 1f 84 00 00    cs nop WORD PTR [rax+rax*1+0x0]
    108d:   00 00 00 

0000000000001090 <deregister_tm_clones>:
    1090:   48 8d 3d 79 2f 00 00    lea    rdi,[rip+0x2f79]        # 4010 <__TMC_END__>
    1097:   48 8d 05 72 2f 00 00    lea    rax,[rip+0x2f72]        # 4010 <__TMC_END__>
    109e:   48 39 f8                cmp    rax,rdi
    10a1:   74 15                   je     10b8 <deregister_tm_clones+0x28>
    10a3:   48 8b 05 3e 2f 00 00    mov    rax,QWORD PTR [rip+0x2f3e]        # 3fe8 <_ITM_deregisterTMCloneTable@Base>
    10aa:   48 85 c0                test   rax,rax
    10ad:   74 09                   je     10b8 <deregister_tm_clones+0x28>
    10af:   ff e0                   jmp    rax
    10b1:   0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
    10b8:   c3                      ret
    10b9:   0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

00000000000010c0 <register_tm_clones>:
    10c0:   48 8d 3d 49 2f 00 00    lea    rdi,[rip+0x2f49]        # 4010 <__TMC_END__>
    10c7:   48 8d 35 42 2f 00 00    lea    rsi,[rip+0x2f42]        # 4010 <__TMC_END__>
    10ce:   48 29 fe                sub    rsi,rdi
    10d1:   48 89 f0                mov    rax,rsi
    10d4:   48 c1 ee 3f             shr    rsi,0x3f
    10d8:   48 c1 f8 03             sar    rax,0x3
    10dc:   48 01 c6                add    rsi,rax
    10df:   48 d1 fe                sar    rsi,1
    10e2:   74 14                   je     10f8 <register_tm_clones+0x38>
    10e4:   48 8b 05 0d 2f 00 00    mov    rax,QWORD PTR [rip+0x2f0d]        # 3ff8 <_ITM_registerTMCloneTable@Base>
    10eb:   48 85 c0                test   rax,rax
    10ee:   74 08                   je     10f8 <register_tm_clones+0x38>
    10f0:   ff e0                   jmp    rax
    10f2:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
    10f8:   c3                      ret
    10f9:   0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

0000000000001100 <__do_global_dtors_aux>:
    1100:   f3 0f 1e fa             endbr64
    1104:   80 3d 45 30 00 00 00    cmp    BYTE PTR [rip+0x3045],0x0        # 4150 <completed.0>
    110b:   75 2b                   jne    1138 <__do_global_dtors_aux+0x38>
    110d:   55                      push   rbp
    110e:   48 83 3d c2 2e 00 00    cmp    QWORD PTR [rip+0x2ec2],0x0        # 3fd8 <__cxa_finalize@GLIBC_2.2.5>
    1115:   00 
    1116:   48 89 e5                mov    rbp,rsp
    1119:   74 0c                   je     1127 <__do_global_dtors_aux+0x27>
    111b:   48 8b 3d e6 2e 00 00    mov    rdi,QWORD PTR [rip+0x2ee6]        # 4008 <__dso_handle>
    1122:   e8 19 ff ff ff          call   1040 <__cxa_finalize@plt>
    1127:   e8 64 ff ff ff          call   1090 <deregister_tm_clones>
    112c:   c6 05 1d 30 00 00 01    mov    BYTE PTR [rip+0x301d],0x1        # 4150 <completed.0>
    1133:   5d                      pop    rbp
    1134:   c3                      ret
    1135:   0f 1f 00                nop    DWORD PTR [rax]
    1138:   c3                      ret
    1139:   0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

0000000000001140 <frame_dummy>:
    1140:   f3 0f 1e fa             endbr64
    1144:   e9 77 ff ff ff          jmp    10c0 <register_tm_clones>

0000000000001149 <_Z6squarei>:
    1149:   f3 0f 1e fa             endbr64
    114d:   55                      push   rbp
    114e:   48 89 e5                mov    rbp,rsp
    1151:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
    1154:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    1157:   0f af c0                imul   eax,eax
    115a:   5d                      pop    rbp
    115b:   c3                      ret

000000000000115c <main>:
    115c:   f3 0f 1e fa             endbr64
    1160:   55                      push   rbp
    1161:   48 89 e5                mov    rbp,rsp
    1164:   48 83 ec 10             sub    rsp,0x10
    1168:   bf 02 00 00 00          mov    edi,0x2
    116d:   e8 d7 ff ff ff          call   1149 <_Z6squarei>
    1172:   89 45 fc                mov    DWORD PTR [rbp-0x4],eax
    1175:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    1178:   89 c6                   mov    esi,eax
    117a:   48 8d 05 bf 2e 00 00    lea    rax,[rip+0x2ebf]        # 4040 <_ZSt4cout@GLIBCXX_3.4>
    1181:   48 89 c7                mov    rdi,rax
    1184:   e8 c7 fe ff ff          call   1050 <_ZNSolsEi@plt>
    1189:   b8 00 00 00 00          mov    eax,0x0
    118e:   c9                      leave
    118f:   c3                      ret

Disassembly of section .fini:

0000000000001190 <_fini>:
    1190:   f3 0f 1e fa             endbr64
    1194:   48 83 ec 08             sub    rsp,0x8
    1198:   48 83 c4 08             add    rsp,0x8
    119c:   c3                      ret

アセンブリコードの出力は、インタープリタ方式や JIT コンパイル方式、バイトコードへのコンパイル方式などを採用している処理系では通常は行えません。