Belum lama berselang, saya berbicara lagi dengan seorang kolega tentang topik abadi: "dengan referensi, atau dengan nilai." Akibatnya, artikel ini muncul. Di dalamnya, saya ingin mempresentasikan hasil penelitian saya tentang ini dan topik terkait. Berikutnya akan dipertimbangkan:
- Register dan tujuannya saat memanggil fungsi.
- Transfer dan pengembalian tipe dan struktur sederhana.
- Bagaimana melewati dengan referensi dan nilai mempengaruhi pengoptimalan fungsi tubuh oleh kompiler.
- Bagaimana ruang digunakan dalam beberapa panggilan fungsi.
- Mekanisme panggilan virtual.
- Optimalisasi panggilan ekor dan rekursi.
- Inisialisasi struktur, susunan, dan vektor.
Perhatian Artikel ini berisi sejumlah besar C ++ dan kode assembler ( Intel ASM dengan komentar), serta banyak tabel dengan peringkat kinerja. Semuanya yang ditulis relevan untuk Sistem x86-64 ABI , yang digunakan di semua sistem operasi Unix modern, misalnya, Linux dan macOS.
Informasi diambil dari System B Application Interface untuk dokumen x86-64 . Daftar Assembler diperoleh untuk dentang 5.0.0 x86-64 dengan bendera -O3 -std=c++1z -march=sandybridge
(menggunakan situs https://godbolt.org ). Peringkat kinerja dibuat untuk Intelยฎ Xeonยฎ Processor E5-2660 2.20GHz .
Isi
Mendaftar di x86-64
Semua data disimpan dalam RAM. Untuk mempercepat pekerjaan dengannya, cache bertingkat digunakan. Tetapi untuk mengubah data, dengan satu atau lain cara, register digunakan ( diskusi dalam komentar ). Di bawah ini adalah deskripsi yang sangat singkat dari register yang paling sering digunakan dalam arsitektur x86-64.
- 16 register tujuan umum:
rax, rbx, rcx, rdx, rbp, rsi, rdi, rsp
dan juga r8-r15
. Ukuran masing-masing adalah 64 bit (8 byte). Untuk mengakses 32 bit yang lebih rendah (4 byte), awalan e
sebagai ganti r
( rax
โ eax
). Hanya operasi integer non-vektor yang didukung. rip
(penunjuk instruksi) menunjukkan instruksi yang akan dieksekusi selanjutnya. Berbagai data konstan yang terletak di bagian memori dengan instruksi dapat dibaca pada offset relatif terhadap rip
.rsp
(stack pointer) menunjuk ke item terakhir pada stack. Tumpukan tumbuh menuju alamat yang lebih rendah. Mendorong sesuatu ke tumpukan mengurangi nilai rsp
.- 16 SSE mendaftarkan 128 bit dalam ukuran:
xmm0 - xmm15
. Jika mode AVX
didukung, mereka merujuk ke 128 bit lebih rendah dari ymm0 - ymm15
masing-masing berukuran 256 bit. Untuk operasi vektor, atau non-integer, data harus terlebih dahulu dimuat ke dalam register ini.
Melewati parameter
Bagian ini memberikan deskripsi yang agak ringkas dan disederhanakan dari algoritma untuk mendistribusikan argumen di register / stack. Untuk keterangan lengkap, lihat halaman 17 "Sistem V ABI".
Kami memperkenalkan beberapa kelas objek:
- INTEGER - Jenis integral ditempatkan di register umum. Ini adalah
bool
, char
, int
dan sebagainya. - SSE adalah angka floating point yang masuk ke dalam register vektor. Ini
float
dan double
. - MEMORY - benda yang melewati tumpukan.
Untuk menyatukan deskripsi, __int128
dan tipe complex
direpresentasikan sebagai struktur dua bidang:
struct __int128 { int64 low, high; }; struct complexT { T real, imag; };
Pada awalnya, setiap argumen fungsi diklasifikasikan:
- Jika jenisnya lebih besar dari 128 bit, atau memiliki bidang yang tidak selaras, maka itu MEMORY .
- Jika ada destruktor non-sepele, copy constructor, metode virtual, kelas basis virtual, maka itu akan melewati "tautan transparan". Objek diganti dengan pointer yang bertipe INTEGER .
- Agregat, dan ini adalah struktur dan array, dianalisis dalam potongan 8 byte .
- Jika ada bidang tipe MEMORY di bagian, maka seluruh bagian adalah MEMORY .
- Jika ada bidang tipe INTEGER , maka seluruh bagian adalah INTEGER .
- Kalau tidak, seluruh bagian SSE .
- Jika ada sepotong tipe MEMORY , maka seluruh argumen adalah MEMORY .
- Jenis
long double
dan complex long double
menggunakan set x87 FPU
khusus dan merupakan tipe MEMORY . - Tipe
__m256
, __m128
dan __float128
adalah __float128
SSE .
Setelah klasifikasi, semua potongan 8 byte (dalam satu potongan dapat ada beberapa bidang struktur, atau elemen array) didistribusikan ke register:
- MEMORY dilewatkan melalui tumpukan.
- INTEGER dikirimkan melalui register gratis
rdi, rsi, rdx, rcx, r8, r9
yang berikutnya secara berurutan. - SSE ditransmisikan melalui register gratis berikutnya
xmm0 - xmm7
.
Argumen dipertimbangkan dari kiri ke kanan. Argumen-argumen yang tidak memiliki cukup register dilewatkan melalui tumpukan. Jika ada argumen yang tidak memiliki register, maka seluruh argumen dilewatkan melalui tumpukan.
Nilai kembali adalah sebagai berikut:
- Jenis MEMORY dikembalikan melalui tumpukan. Tempat itu disediakan oleh fungsi panggilan dan alamat awalnya dilewatkan melalui
rdi
seolah-olah itu adalah argumen pertama untuk fungsi. Setelah kembali, alamat ini harus dikembalikan melalui rax
. Argumen asli pertama akan disahkan, masing-masing, sebagai argumen kedua, dan seterusnya. - Potongan INTEGER dikembalikan melalui
rax, rdx
register gratis berikutnya rax, rdx
. - Potongan SSE dikembalikan melalui register gratis berikutnya
xmm0, xmm1
. Register ini digunakan untuk menerima dan mengembalikan nilai.
Tabel pivot dengan register dan tujuannya sangat berguna saat membaca assembler:
Daftar | Janji temu |
---|
rax | Registrasi sementara, kembalikan yang pertama (ret 1) hasil INTEGER . |
rbx | Milik fungsi panggilan, tidak boleh diubah pada saat kembali. |
rcx | Melewati argumen INTEGER keempat (4). |
rdx | Melewati argumen INTEGER ketiga (3), mengembalikan hasil INTEGER kedua (ret 2). |
rsp | Pointer ke tumpukan. |
rbp | Milik fungsi panggilan, tidak boleh diubah pada saat kembali. |
rsi | Melewati argumen INTEGER kedua (2). |
rdi | Melewati argumen INTEGER pertama (1). |
r8 | Melewati argumen INTEGER kelima (5). |
r9 | Melewati argumen INTEGER keenam (6). |
r10-r11 | Register sementara. |
r12-r15 | Milik fungsi panggilan, tidak boleh diubah pada saat kembali. |
xmm0-xmm1 | Lewati dan kembalikan argumen SSE pertama dan kedua. |
xmm2-xmm7 | Melewati argumen SSE ketiga hingga keenam. |
xmm8-xmm15 | Register sementara. |
Register milik fungsi panggilan sebaiknya tidak digunakan, atau nilainya harus disimpan di suatu tempat, misalnya, di tumpukan, dan kemudian dikembalikan.
Contoh sederhana
Kecuali secara eksplisit dinyatakan sebaliknya, semua fungsi yang digunakan ditandai sebagai NOINLINE
. Kami berpura-pura bahwa fungsi tubuh terletak di file cpp, dan KPP dinonaktifkan. Selain itu, semua hasil fungsi ditransfer ke fungsi NOINLINE
kosong untuk mencegah pengoptimal menghapus semua kode.
#define NOINLINE __attribute__((noinline)) #define INLINE static __attribute__((always_inline))
Pertimbangkan sesuatu yang sederhana.
double foo(int8_t a, int16_t b, int32_t c, int64_t d, float x, double y) { return a + b + c + d + x + y; } ... auto result = foo(1, 2, 3, 4, 5, 6);
Parameter dilewatkan seperti ini:
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
a | rdi | d | rcx | xmm0 |
b | rsi | x | xmm0 | |
c | rdx | y | xmm1 | |
Pertimbangkan kode yang dihasilkan secara lebih rinci.
foo(signed char, short, int, long, float, double): add edi, esi # a b. add edi, edx # c. movsxd rax, edi # rax, 64 . add rax, rcx # d. vcvtsi2ss xmm2, xmm2, rax # float xmm2. vaddss xmm0, xmm2, xmm0 # x, 's' vaddss single precision. vcvtss2sd xmm0, xmm0, xmm0 # double. vaddsd xmm0, xmm0, xmm1 # y, 'd' vaddsd double precision. ret # , , , rsp 8. .LCPI1_0: # . .long 1084227584 # float 5 .LCPI1_1: .quad 4618441417868443648 # double 6 main: # @main sub rsp, 24 # . vmovss xmm0, dword ptr [rip + .LCPI1_0] # xmm0 = mem[0],zero,zero,zero vmovsd xmm1, qword ptr [rip + .LCPI1_1] # xmm1 = mem[0],zero mov edi, 1 mov esi, 2 mov edx, 3 mov ecx, 4 call foo(signed char, short, int, long, float, double) # call , rsp 8, . vmovsd qword ptr [rsp + 16], xmm0 # .
Jika Anda meneruskan parameter tipe sederhana ke suatu fungsi, Anda harus berusaha keras agar tidak melewati register.
Pertimbangkan berbagai contoh agregat. Array dapat dianggap sebagai struktur dengan beberapa bidang.
struct St { double a, b; }; double foo(St s) { return sa + sb; } ... St s{1, 2}; auto result = foo(s);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
sa | xmm0 | sb | xmm1 | xmm0 |
Tampaknya tidak ada yang mencegah mendorong dua double
menjadi satu register xmm
sekaligus. Namun sayang, algoritma distribusi hanya beroperasi pada potongan delapan byte.
foo(St): # @foo(St) vaddsd xmm0, xmm0, xmm1 # , . ret .LCPI1_0: .quad 4607182418800017408 # double 1 .LCPI1_1: .quad 4611686018427387904 # double 2 main: # @main sub rsp, 24 # . vmovsd xmm0, qword ptr [rip + .LCPI1_0] # xmm0 = mem[0],zero vmovsd xmm1, qword ptr [rip + .LCPI1_1] # xmm1 = mem[0],zero call foo(St) vmovsd qword ptr [rsp + 16], xmm0 # double .
Jika Anda menambahkan bidang double
lain, maka seluruh struktur akan melewati tumpukan, karena ukurannya akan melebihi 128 byte.
struct St { double a, b, c; }; double foo(St s) { return sa + sb + sc; } ... St s{1, 2, 3}; auto result = foo(s);
foo(St): # @foo(St) # , 8 , . , rsp+8. vmovsd xmm0, qword ptr [rsp + 8] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, qword ptr [rsp + 16] vaddsd xmm0, xmm0, qword ptr [rsp + 24] ret .L_ZZ4mainE1s: .quad 4607182418800017408 # double 1 .quad 4611686018427387904 # double 2 .quad 4613937818241073152 # double 3 main: # @main sub rsp, 40 # 40 . # , . , mov . mov rax, qword ptr [rip + .L_ZZ4mainE1s+16] # '3'. mov qword ptr [rsp + 16], rax # '3' . vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1s] # xmm0 '1' '2'. vmovups xmmword ptr [rsp], xmm0 # '1' '2 . : 1 = *rsp , 2 = *(rsp+8), 3 = *(rsp+16). call foo(St) vmovsd qword ptr [rsp + 32], xmm0 # double .
Mari kita lihat apa yang terjadi jika kita ganti double
dengan uint64_t
.
struct St { uint64_t a, b; }; uint64_t foo(St s) { return sa + sb; } ... St s{1, 2}; auto result = foo(s);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
sa | rdi | sb | rsi | rax |
foo(St): # @foo(St) lea rax, [rdi + rsi] ret main: # @main sub rsp, 24 mov edi, 1 mov esi, 2 call foo(St) mov qword ptr [rsp + 16], rax
Hasilnya terasa lebih kompak. Informasi lebih lanjut tentang mengapa instruksi lea
digunakan daripada add
dapat dibaca, misalnya, di sini: https://stackoverflow.com/a/6328441/1418863
Jika Anda menambahkan bidang lain, maka, seperti dalam contoh dengan double
, struktur akan melewati tumpukan. By the way, kode akan hampir identik, bahkan memuat ke stack akan melalui register xmm
.
Pertimbangkan sesuatu yang lebih menarik.
struct St { float a, b, c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1.a | xmm0 | s1.b | xmm0 | xmm0, xmm1 |
s1.c | xmm1 | s1.d | xmm1 | |
s2.a | xmm2 | s2.b | xmm2 | |
s2.c | xmm3 | s2.d | xmm3 | |
Dua bidang float
didorong ke setiap register xmm
.
foo(St, St): # @foo(St, St) # vaddps float . vaddps xmm0, xmm0, xmm2 vaddps xmm1, xmm1, xmm3 ret .LCPI1_0: .long 1065353216 # float 1 .long 1073741824 # float 2 .zero 4 .zero 4 # LCPI1_1 - LCPI1_3. ... main: # @main sub rsp, 24 # , . vmovapd xmm0, xmmword ptr [rip + .LCPI1_0] # xmm0 = <1,2,u,u> vmovapd xmm1, xmmword ptr [rip + .LCPI1_1] # xmm1 = <3,4,u,u> vmovaps xmm2, xmmword ptr [rip + .LCPI1_2] # xmm2 = <5,6,u,u> vmovaps xmm3, xmmword ptr [rip + .LCPI1_3] # xmm3 = <7,8,u,u> call foo(St, St) # , . xmm 256 , 128 a b, - c d. vunpcklpd xmm0, xmm0, xmm1 # xmm0 = xmm0[0],xmm1[0] vmovupd xmmword ptr [rsp + 8], xmm0
Jika struktur tidak 4, tetapi tiga bidang, maka kode fungsi akan serupa, kecuali untuk mengganti instruksi vaddps
kedua dengan vaddss
, yang hanya menambahkan 64 bit pertama dari register.
struct St { int32_t a, b, c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1.a | rdi | s1.b | rdi | rax, rdx |
s1.c | rsi | s1.d | rsi | |
s2.a | rdx | s2.b | rdx | |
s2.c | rcx | s2.d | rcx | |
foo(St, St): # @foo(St, St) lea eax, [rdx + rdi] movabs r8, -4294967296 # 0xFFFFFFFF00000000 . and rdi, r8 add rdi, rdx and rdi, r8 or rax, rdi lea edx, [rcx + rsi] # , add. and rsi, r8 add rsi, rcx and rsi, r8 or rdx, rsi ret main: # @main sub rsp, 24 movabs rdi, 8589934593 # . movabs rsi, 17179869187 movabs rdx, 25769803781 movabs rcx, 34359738375 call foo(St, St) mov qword ptr [rsp + 8], rax # . mov qword ptr [rsp + 16], rdx
Bit ajaib terjadi di dalam fungsi, tetapi prinsipnya cukup jelas. Setiap pasangan nomor 32 bit dikemas ke dalam satu register 64 bit. Pengembalian uang dilakukan dengan cara yang sama.
Mari kita lihat apa yang terjadi jika kita mulai mencampur jenis bidang, tetapi agar dalam 8 byte potongan mereka dari kelas yang sama.
struct St { int32_t a, b; float c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1.a | rdi | s1.b | rdi | rax, xmm0 |
s1.c | xmm0 | s1.d | xmm0 | |
s2.a | rsi | s2.b | rsi | |
s2.c | xmm1 | s2.d | xmm1 | |
foo(St, St): # @foo(St, St) lea eax, [rsi + rdi] # . movabs rcx, -4294967296 and rdi, rcx add rdi, rsi and rdi, rcx or rax, rdi vaddps xmm0, xmm0, xmm1 # . ret .LCPI1_0: .long 1077936128 # float 3 .long 1082130432 # float 4 .zero 4 .zero 4 ... main: # @main sub rsp, 24 vmovaps xmm0, xmmword ptr [rip + .LCPI1_0] # xmm0 = <3,4,u,u> vmovaps xmm1, xmmword ptr [rip + .LCPI1_1] # xmm1 = <7,8,u,u> movabs rdi, 8589934593 movabs rsi, 25769803781 call foo(St, St) mov qword ptr [rsp + 8], rax # . vmovlps qword ptr [rsp + 16], xmm0
Tapi ini tidak menarik, karena jenis bidang di setiap 8 byte chunk adalah sama. Kocok bidang.
struct St { int32_t a; float b; int32_t c; float d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2);
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1.a | rdi | s1.b | rdi | rax, rdx |
s1.c | rsi | s1.d | rsi | |
s2.a | rdx | s2.b | rdx | |
s2.c | rcx | s2.d | rcx | |
Lihat paragraf 3.2. Karena chunk 8-byte berisi float
dan int
, chunk keseluruhan akan bertipe INTEGER dan akan diteruskan dalam register umum.
foo(St, St): # @foo(St, St) mov rax, rdx add edx, edi shr rdi, 32 vmovd xmm0, edi mov rdi, rcx add ecx, esi shr rsi, 32 vmovd xmm1, esi shr rax, 32 vmovd xmm2, eax vaddss xmm0, xmm0, xmm2 shr rdi, 32 vmovd xmm2, edi vaddss xmm1, xmm1, xmm2 vmovd eax, xmm0 shl rax, 32 or rdx, rax vmovd eax, xmm1 shl rax, 32 or rcx, rax mov rax, rdx mov rdx, rcx ret main: # @main sub rsp, 24 movabs rdi, 4611686018427387905 # 0x4000000000000001, 32 int, - float. movabs rsi, 4647714815446351875 movabs rdx, 4665729213955833861 movabs rcx, 4683743612465315847 call foo(St, St) mov qword ptr [rsp + 8], rax # 64 . mov qword ptr [rsp + 16], rdx
Di sini Anda dapat melihat 6 operasi shift untuk mengekstrak bidang float
dan mendorongnya ke register dengan hasilnya. Serta tidak adanya operasi vektor. Secara umum, yang terbaik adalah tidak mengganggu tipe bidang dalam potongan struktur 8 byte.
Lewat tautan
Melewati parameter melalui referensi konstan mirip dengan melewatkan pointer ke objek. Jika objek tidak muat dalam register, maka objek itu dilewatkan dan dikembalikan melalui tumpukan. Mari kita lihat bagaimana ini terjadi. Untuk realisme, pertimbangkan struktur untuk titik tiga dimensi.
struct Point3f { float x, y, z; }; struct Point3d { double x, y, z; }; Point3f scale(Point3f p) { return {px * 2, py * 2, pz * 2}; } Point3f scaleR(const Point3f& p) { return {px * 2, py * 2, pz * 2}; } Point3d scale(Point3d p) { return {px * 2, py * 2, pz * 2}; } Point3d scaleR(const Point3d& p) { return {px * 2, py * 2, pz * 2}; }
Bandingkan kode fungsi. Kebanyakan register xmm
baru akan digunakan, sehingga logikanya dapat dimengerti.
scale(Point3f): # @scale(Point3f) # , x, y xmm0, z - xmm1, . vaddps xmm0, xmm0, xmm0 vaddss xmm1, xmm1, xmm1 ret scaleR(Point3f const&): # @scaleR(Point3f const&) # rdi, . xmm0, xmm1. vmovsd xmm0, qword ptr [rdi] # xmm0 = mem[0],zero vaddps xmm0, xmm0, xmm0 vmovss xmm1, dword ptr [rdi + 8] # xmm1 = mem[0],zero,zero,zero vaddss xmm1, xmm1, xmm1 ret scale(Point3d): # @scale(Point3d) # rdi , . [rsp+8, rsp+32). [rsp, rsp+8) . vmovapd xmm0, xmmword ptr [rsp + 8] vaddpd xmm0, xmm0, xmm0 vmovupd xmmword ptr [rdi], xmm0 vmovsd xmm0, qword ptr [rsp + 24] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, xmm0 vmovsd qword ptr [rdi + 16], xmm0 mov rax, rdi # , . ret scaleR(Point3d const&): # @scaleR(Point3d const&) # , [rsp+8, rsp+32), [rsi, rsi+24). vmovupd xmm0, xmmword ptr [rsi] vaddpd xmm0, xmm0, xmm0 vmovupd xmmword ptr [rdi], xmm0 vmovsd xmm0, qword ptr [rsi + 16] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, xmm0 vmovsd qword ptr [rdi + 16], xmm0 mov rax, rdi ret
Sekarang mari kita lihat tempat di mana fungsi-fungsi ini dipanggil.
# scale(Point3f) main: # @main sub rsp, 24 # . vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = <1,2,u,u> vmovss xmm1, dword ptr [rip + .LCPI4_1] # xmm1 = <3,u,u,u> call scale(Point3f) # scaleR(const Point3f&) main: # @main sub rsp, 24 # rdi, . mov edi, .L_ZZ4mainE1p # <1,2,3,u> call scaleR(Point3f const&) # scale(Point3d) main: # @main sub rsp, 64 # . mov rax, qword ptr [rip + .L_ZZ4mainE1p+16] mov qword ptr [rsp + 16], rax vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1p] vmovups xmmword ptr [rsp], xmm0 lea rbx, [rsp + 40] mov rdi, rbx # [rsp+40, rsp+64). call scale(Point3d) .L_ZZ4mainE1p: .quad 4607182418800017408 # double 1 .quad 4611686018427387904 # double 2 .quad 4613937818241073152 # double 3 # scaleR(const Point3d&) main: # @main sub rsp, 64 # . mov rax, qword ptr [rip + .L_ZZ4mainE1p+16] mov qword ptr [rsp + 32], rax vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1p] vmovaps xmmword ptr [rsp + 16], xmm0 lea rbx, [rsp + 40] lea rsi, [rsp + 16] # [rsp+16, rsp+40). mov rdi, rbx # [rsp+40, rsp+64). call scaleR(Point3d const&)
Mari kita lihat bahwa jika kita memiliki banyak bidang, tetapi strukturnya masih sesuai dengan register. Di sini kesenangan dimulai.
struct St { char d[16]; }; St foo(St s1, St s2) {
Kode untuk fungsi yang mengambil argumen berdasarkan nilai.
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1.d [1: 8] | rdi | s1.d [8:16] | rsi | rax, rdx |
s2.d [1: 8] | rdx | s2.d [8:16] | rcx | |
foo(St, St): # @foo(St, St) mov qword ptr [rsp - 16], rdi mov qword ptr [rsp - 8], rsi mov qword ptr [rsp - 32], rdx mov qword ptr [rsp - 24], rcx mov eax, edx add al, dil mov byte ptr [rsp - 48], al mov r8, rdi shr r8, 8 mov rax, rdx shr rax, 8 add al, r8b mov byte ptr [rsp - 47], al mov r8, rdi shr r8, 16 mov rax, rdx shr rax, 16 add al, r8b mov byte ptr [rsp - 46], al mov r8, rdi shr r8, 24 mov rax, rdx shr rax, 24 add al, r8b mov byte ptr [rsp - 45], al mov r8, rdi shr r8, 32 mov rax, rdx shr rax, 32 add al, r8b mov byte ptr [rsp - 44], al mov r8, rdi shr r8, 40 mov rax, rdx shr rax, 40 add al, r8b mov byte ptr [rsp - 43], al mov r8, rdi shr r8, 48 mov rax, rdx shr rax, 48 add al, r8b mov byte ptr [rsp - 42], al shr rdi, 56 shr rdx, 56 add dl, dil mov byte ptr [rsp - 41], dl mov eax, ecx add al, sil mov byte ptr [rsp - 40], al mov rax, rsi shr rax, 8 mov rdx, rcx shr rdx, 8 add dl, al mov byte ptr [rsp - 39], dl shr rsi, 16 shr rcx, 16 add cl, sil mov byte ptr [rsp - 38], cl mov al, byte ptr [rsp - 21] mov cl, byte ptr [rsp - 20] add al, byte ptr [rsp - 5] mov byte ptr [rsp - 37], al add cl, byte ptr [rsp - 4] mov byte ptr [rsp - 36], cl mov al, byte ptr [rsp - 19] mov cl, byte ptr [rsp - 18] add al, byte ptr [rsp - 3] mov byte ptr [rsp - 35], al add cl, byte ptr [rsp - 2] mov byte ptr [rsp - 34], cl mov al, byte ptr [rsp - 17] add al, byte ptr [rsp - 1] mov byte ptr [rsp - 33], al mov rax, qword ptr [rsp - 48] mov rdx, qword ptr [rsp - 40] ret
Ya, di sini semua argumen disalin ke stack, setelah itu diekstraksi dan ditambahkan satu byte setiap kali. Seperti yang Anda lihat, ada persis 16 instruksi add
dalam fungsi. GCC, omong-omong, dalam contoh ini menghasilkan kode yang jauh lebih ringkas, tetapi masih dengan menyalin melalui tumpukan. Adakah yang bisa diperbaiki? Lewati struktur dengan referensi.
St fooR(const St& s1, const St& s2) { }
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1 | rdi | s2 | rsi | rax, rdx |
fooR(St const&, St const&): # @fooR(St const&, St const&) vmovdqu xmm0, xmmword ptr [rsi] vpaddb xmm0, xmm0, xmmword ptr [rdi] vmovdqa xmmword ptr [rsp - 24], xmm0 mov rax, qword ptr [rsp - 24] mov rdx, qword ptr [rsp - 16] ret
Oh ya! Terlihat jauh lebih baik. Kita dapat memuat 16 elemen byte tunggal sekaligus ke register vpaddb
dan memanggil vpaddb
yang akan vpaddb
semuanya dalam satu operasi. Setelah itu, hasilnya disalin ke register keluaran melalui tumpukan. Anda mungkin berpikir bahwa Anda dapat menyingkirkan operasi terakhir ini dengan mengganti argumen pertama dengan referensi yang tidak konstan.
void fooR1(St &s1, const St& s2) { for(int i{}; i < 16; ++i) s1.d[i] += s2.d[i]; }
Nama depan | Daftar | Nama depan | Daftar | Hasil |
---|
s1 | rdi | s2 | rsi | |
fooR1(St&, St const&): # @fooR1(St&, St const&) mov al, byte ptr [rsi] add byte ptr [rdi], al mov al, byte ptr [rsi + 1] add byte ptr [rdi + 1], al mov al, byte ptr [rsi + 2] add byte ptr [rdi + 2], al mov al, byte ptr [rsi + 3] add byte ptr [rdi + 3], al mov al, byte ptr [rsi + 4] add byte ptr [rdi + 4], al mov al, byte ptr [rsi + 5] add byte ptr [rdi + 5], al mov al, byte ptr [rsi + 6] add byte ptr [rdi + 6], al mov al, byte ptr [rsi + 7] add byte ptr [rdi + 7], al mov al, byte ptr [rsi + 8] add byte ptr [rdi + 8], al mov al, byte ptr [rsi + 9] add byte ptr [rdi + 9], al mov al, byte ptr [rsi + 10] add byte ptr [rdi + 10], al mov al, byte ptr [rsi + 11] add byte ptr [rdi + 11], al mov al, byte ptr [rsi + 12] add byte ptr [rdi + 12], al mov al, byte ptr [rsi + 13] add byte ptr [rdi + 13], al mov al, byte ptr [rsi + 14] add byte ptr [rdi + 14], al mov al, byte ptr [rsi + 15] add byte ptr [rdi + 15], al ret
Sepertinya ada yang salah. Ini terjadi karena, secara default, kompiler sangat berhati-hati, dan menyarankan bahwa programmer mungkin tidak dapat menulis dan menulis sesuatu seperti ini:
char buff[17]; fooR1(*reinterpret_cast<St*>(buff+1), reinterpret_cast<const St*>(buff));
Dalam hal ini, buff[i+1] += buff[i]
dihitung pada setiap iterasi, yaitu pointer aliasing tersedia. Untuk menunjukkan kepada kompiler bahwa penggunaan fungsi yang aneh seperti itu tidak diharapkan, kata kunci __restrict ada.
void fooR2(St & __restrict s1, const St& s2) { }
Yang memberikan hasil yang diinginkan.
fooR2(St&, St const&): # @fooR2(St&, St const&) vmovdqu xmm0, xmmword ptr [rdi] vpaddb xmm0, xmm0, xmmword ptr [rsi] vmovdqu xmmword ptr [rdi], xmm0 ret
void fooR3(St &__restrict s1, St s2)
, , St foo(St, St)
.
, , void foo(char* __restrict s1, const char* s2, int size)
, __restrict
.
b
a
, , foo
:
St a, b; st(a, b);
Code | Cycles per iteration |
---|
St a, b; st(a, b); | 7.6 |
4 x foo no reuse | 121.9 |
4 x foo | 117.7 |
4 x fooR no reuse | 66.3 |
4 x fooR | 64.6 |
4 x fooR1 | 84.5 |
4 x fooR2 | 20.6 |
4 x foo inline | 51.9 |
4 x fooR inline | 30.5 |
4 x fooR1 inline | 8.8 |
4 x fooR2 inline | 8.8 |
'no reuse' , . auto a2 = foo(a, b); auto a3 = foo(a2, b);
. 'inline' , INLINE , NOINLINE .
fooR1 inline / fooR2 inline
, , , , , foo inline / fooR inline
, . , , .
, , .
struct Point3f { float x, y, z; ~Point3f() {} }; Point3f scale(Point3f p) { return {px * 2, py * 2, pz * 2}; }
rdi
, . , rsi
, .
scale(Point3f): # @scale(Point3f) vmovss xmm0, dword ptr [rsi] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi], xmm0 vmovss xmm0, dword ptr [rsi + 4] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi + 4], xmm0 vmovss xmm0, dword ptr [rsi + 8] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi + 8], xmm0 mov rax, rdi ret
, , ( ) . POD . Point3f scaleR(const Point3f&)
. .
Point3f p{1, 2, 3}; auto result = scale(p); sink(&result);
main: # @main push rbx sub rsp, 48 movabs rax, 4611686019492741120 # . mov qword ptr [rsp + 16], rax mov dword ptr [rsp + 24], 1077936128 lea rbx, [rsp + 32] lea rsi, [rsp + 16] # [rsp+16, rsp+28) mov rdi, rbx # [rsp+32, rsp+44) call scale(Point3f) mov qword ptr [rsp + 8], rbx # [rsp+8, rsp+16) . lea rdi, [rsp + 8] call void sink<Point3f*>(Point3f* const&) xor eax, eax add rsp, 48 pop rbx ret # . mov rdi, rax call _Unwind_Resume
NOINLINE , .
main: # @main push r14 push rbx sub rsp, 56 movabs rax, 4611686019492741120 # [rsp, rsp+12). p. mov qword ptr [rsp], rax mov dword ptr [rsp + 8], 1077936128 # [rsp+24, rsp+36), pTmp. mov eax, dword ptr [rsp + 8] mov dword ptr [rsp + 32], eax mov rax, qword ptr [rsp] mov qword ptr [rsp + 24], rax lea r14, [rsp + 40] lea rbx, [rsp + 24] mov rdi, r14 # [rsp+40, rsp+52), result. mov rsi, rbx # - pTmp. call scale(Point3f) mov rdi, rbx # pTmp. - this. call Point3f::~Point3f() mov qword ptr [rsp + 16], r14 # [rsp+16, rsp+24) result. sink. lea rdi, [rsp + 16] call void sink<Point3f*>(Point3f* const&) lea rdi, [rsp + 40] # result. call Point3f::~Point3f() mov rdi, rsp # p. call Point3f::~Point3f() xor eax, eax add rsp, 56 pop rbx pop r14 ret # . . mov rbx, rax lea rdi, [rsp + 40] # result. call Point3f::~Point3f() mov rdi, rsp # p. call Point3f::~Point3f() mov rdi, rbx call _Unwind_Resume
p
, .
, . .
# Point3f result = scale(scale(Point3f{1, 2, 3})); sub rsp, 24 vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = <1,2,u,u> vmovss xmm1, dword ptr [rip + .LCPI4_1] # xmm1 = mem[0],zero,zero,zero # xmm0, xmm1 , , ! call scale(Point3f) call scale(Point3f) vmovlps qword ptr [rsp + 8], xmm0 vmovss dword ptr [rsp + 16], xmm1 # Point3f result = scaleR(scaleR(Point3f{1, 2, 3})); sub rsp, 56 # [rsp+24, rsp+36). movabs rax, 4611686019492741120 # 0x400000003F800000 = [2.0f, 1.0f] mov qword ptr [rsp + 24], rax mov dword ptr [rsp + 32], 1077936128 # 0x40400000 = 3.0f lea rdi, [rsp + 24] # . call scaleR(Point3f const&) # [rsp+8, rsp+20). vmovlps qword ptr [rsp + 8], xmm0 vmovss dword ptr [rsp + 16], xmm1 lea rdi, [rsp + 8] # . call scaleR(Point3f const&) vmovlps qword ptr [rsp + 40], xmm0 vmovss dword ptr [rsp + 48], xmm1
, , . , .
# Point3d result = scale(scale(Point3d{1, 2, 3})); sub rsp, 112 # [rsp, rsp+24). vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = [1.000000e+00,2.000000e+00] vmovaps xmmword ptr [rsp + 32], xmm0 movabs rax, 4613937818241073152 # 0x4008000000000000 = 3.0 mov qword ptr [rsp + 48], rax mov rax, qword ptr [rsp + 48] mov qword ptr [rsp + 16], rax vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rsp], xmm0 # [rsp+64, rsp+88). lea rdi, [rsp + 64] # rdi . call scale(Point3d) # [rsp, rsp+24). mov rax, qword ptr [rsp + 80] # z z . mov qword ptr [rsp + 16], rax vmovups xmm0, xmmword ptr [rsp + 64] # [x, y] [x, y] . vmovups xmmword ptr [rsp], xmm0 # [rsp+88, rsp+112). lea rbx, [rsp + 88] mov rdi, rbx call scale(Point3d) # Point3d result = scaleR(scaleR(Point3d{1, 2, 3})); sub rsp, 72 # [rsp, rsp+24), . vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] vmovaps xmmword ptr [rsp], xmm0 movabs rax, 4613937818241073152 mov qword ptr [rsp + 16], rax lea r14, [rsp + 24] mov rsi, rsp # - [rsp, rsp+24). mov rdi, r14 # - [rsp+24, rsp+48). call scaleR(Point3d const&) lea rbx, [rsp + 48] mov rdi, rbx # [rsp+48, rsp+72). mov rsi, r14 # [rsp+24, rsp+48). call scaleR(Point3d const&)
, , , , . โ . . , , .
, . , . , , Point3f
, Point3d
โ .
Code | Cycles per iteration |
---|
auto r = pf(); | 6.7 |
auto r = scale(pf()); | 11.1 |
auto r = scaleR(pf()); | 12.6 |
auto r = scale(scale(pf())); | 18.2 |
auto r = scaleR(scaleR(pf())); | 18.3 |
auto r = scale(scale(scale(pf()))); | 16.8 |
auto r = scaleR(scaleR(scaleR(pf()))); | 20.2 |
auto r = pd(); | 7.3 |
auto r = scale(pd()); | 11.7 |
auto r = scaleR(pd()); | 11.0 |
auto r = scale(scale(pd())); | 16.9 |
auto r = scaleR(scaleR(pd())); | 14.1 |
auto r = scale(scale(scale(pd()))); | 21.2 |
auto r = scaleR(scaleR(scaleR(pd()))); | 17.2 |
INLINE | 8.1 โ 8.9 |
Point3f
struct Point3i { int32_t x, y, z; };
Point3d
struct Point3ll { int64_t x, y, z; };
, . , , , 64 int, . , Point3f
struct Point2ll { int64_t x, y; };
Point3d
struct Point4ll { int64_t x, y, z, a; };
, -.
:
- . .
- , , .
- inline , . , , , . .
optional
std::optional
, boost::optional
, , "x86-64 clang (experimental concepts)" , , MSVC ,
struct Point { float x, y; }; using OptPoint1 = optional<Point>;
struct OptPoint2 { float x, y; union { char _; bool d; };
, OptPoint1
, OptPoint2
โ .
OptPoint1 foo(OptPoint1 s) { return Point{s->x + 1, s->y + 1}; } OptPoint2 foo(OptPoint2 s) { return {sx + 1, sy + 1, true}; } ... OptPoint1 s1{Point{1, 2}}; OptPoint2 s2{3, 4, true}; auto result1 = foo(s1); auto result2 = foo(s2);
.LCPI0_0: .long 1065353216 # float 1 foo(std::optional<Point>): # @foo(std::optional<Point>) vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero # rsi . vaddss xmm1, xmm0, dword ptr [rsi] # x , 1. vaddss xmm0, xmm0, dword ptr [rsi + 4] # y , 1. vmovss dword ptr [rdi], xmm1 # x , rdi , . vmovss dword ptr [rdi + 4], xmm0 # y . mov byte ptr [rdi + 8], 1 # optional::has_value(). mov rax, rdi # . ret .LCPI1_0: .long 1065353216 # float 1 .long 1065353216 # float 1 .zero 4 .zero 4 foo(OptPoint2): # @foo(OptPoint2) vaddps xmm0, xmm0, xmmword ptr [rip + .LCPI1_0] # [x, y] c [1, 1]. xmm0 . mov al, 1 # d, al - 8 rax. ret .LCPI2_0: .long 1077936128 # float 3 .long 1082130432 # float 4 .zero 4 .zero 4 main: # @main push rbx sub rsp, 64 movabs rax, 4611686019492741120 # 0x400000003F800000 x y. mov qword ptr [rsp + 32], rax # x, y . mov byte ptr [rsp + 40], 1 # optional::has_value(). lea rbx, [rsp + 48] lea rsi, [rsp + 32] # . mov rdi, rbx # . call foo(std::optional<Point>) # . vmovaps xmm0, xmmword ptr [rip + .LCPI2_0] # xmm0 = <3,4,u,u> mov edi, 1 # bool d. call foo(OptPoint2) vmovlps qword ptr [rsp + 16], xmm0 # . mov byte ptr [rsp + 24], al # al rax.
, foo
inline
, .
# OptPoint1 foo(OptPoint1) OptPoint1 foo(const OptPoint1&) vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero vaddss xmm1, xmm0, dword ptr [rsp + 32] vaddss xmm0, xmm0, dword ptr [rsp + 36] vmovss dword ptr [rsp + 8], xmm1 vmovss dword ptr [rsp + 12], xmm0 mov byte ptr [rsp + 16], 1 # OptPoint2 foo(OptPoint2) OptPoint2 foo(const OptPoint2&) vmovsd xmm0, qword ptr [rsp + 48] # xmm0 = mem[0],zero vaddps xmm0, xmm0, xmmword ptr [rip + .LCPI0_1] vmovlps qword ptr [rsp + 8], xmm0 mov byte ptr [rsp + 16], 1
, , , .
: inline
, std::optional
.
, , . . , . , - , , , . .
struct Fn { virtual ~Fn() noexcept = default; virtual int call(int x) const = 0; }; struct Add final : Fn { Add(int a) : a(a) {} int call(int x) const override { return a + x; } int a; }; NOINLINE bool isFixedPoint(const Fn& fn, int x) { return fn.call(x) == x; } int main() { Add add{32}; bool result = isFixedPoint(add, 10); }
- .
Add::call(int) const: # @Add::call(int) const # rdi this , [rdi, rdi+8) , . add esi, dword ptr [rdi + 8] mov eax, esi # rax, eax 32 . ret # Add. 16, RTTI . vtable for Add: .quad 0 .quad typeinfo for Add # RTTI. -8. .quad Fn::~Fn() # , 0 . .quad Add::~Add() .quad Add::call(int) const # , 16 . isFixedPoint(Fn const&, int): # @isFixedPoint(Fn const&, int) push rbx # rbx, , . mov ebx, esi # 32 . mov rax, qword ptr [rdi] # rdi - Fn, this . call qword ptr [rax + 16] # Add::call. cmp eax, ebx # call rax, ebx, . sete al # 8 eax. pop rbx # rbx. ret main: # @main sub rsp, 40 mov qword ptr [rsp + 24], vtable for Add+16 # Add, 16 , , , . RTTI . mov dword ptr [rsp + 32], 32 # add.a . lea rdi, [rsp + 24] # . mov esi, 10 # . call isFixedPoint(Fn const&, int) mov byte ptr [rsp + 15], al # . ... mov rdi, rax # . call _Unwind_Resume mov rdi, rax call _Unwind_Resume
protected
, ( 34-37). NOINLINE , ( false
). NOINLINE , . .
struct Add final { Add(int a) : a(a) {} NOINLINE int call(int x) const { return a + x; } int a; }; template<typename T> NOINLINE bool isFixedPoint(const T& fn, int x) { return fn.call(x) == x; }
Add::call(int) const: # @Add::call(int) const add esi, dword ptr [rdi] # , this rdi , , . mov eax, esi ret bool isFixedPoint<Add>(Add const&, int): # @bool isFixedPoint<Add>(Add const&, int) push rbx mov ebx, esi # Add::call , isFixedPoint. call Add::call(int) const cmp eax, ebx sete al pop rbx ret main: # @main sub rsp, 24 mov dword ptr [rsp + 8], 32 # add.a. lea rdi, [rsp + 8] # , rdi add.a. mov esi, 10 call bool isFixedPoint<Add>(Add const&, int) mov byte ptr [rsp + 7], al ... ret
, NOINLINE .
1000 Add
isFixedPoint
.
Code | Cycles per iteration |
---|
call , isFixedPoint call | 5267 |
call , NOINLINE isFixedPoint | 10721 |
call , INLINE isFixedPoint | 8291 |
call , NOINLINE isFixedPoint | 10571 |
, NOINLINE call , NOINLINE isFixedPoint | 10536 |
, isFixedPoint call | 4505 |
, INLINE call , INLINE isFixedPoint | 4531 |
:
- .
- , .
- , , , inline. .
inline
. inline
- cpp , NOINLINE .inline
.
call
, jmp
.
. clang . , :
double exp_by_squaring(double x, int n, double y = 1) { if (n < 0) return exp_by_squaring(1.0 / x, -n, y); if (n == 0) return y; if (n == 1) return x * y; if (n % 2 == 0) return exp_by_squaring(x * x, n / 2, y); return exp_by_squaring(x * x, (n - 1) / 2, x * y); }
Kami mendapatkan:
.LCPI0_0: .quad 4607182418800017408 # double 1 exp_by_squaring(double, int, double): # @exp_by_squaring(double, int, double) vmovsd xmm2, qword ptr [rip + .LCPI0_0] # xmm2 = mem[0],zero vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 jmp .LBB0_3 .LBB0_9: # in Loop: Header=BB0_4 Depth=1 shr edi vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 .LBB0_3: # =>This Inner Loop Header: Depth=1 vdivsd xmm3, xmm2, xmm3 neg edi test edi, edi js .LBB0_3 .LBB0_4: # =>This Inner Loop Header: Depth=1 je .LBB0_7 cmp edi, 1 je .LBB0_6 vmulsd xmm0, xmm3, xmm3 test dil, 1 je .LBB0_9 lea eax, [rdi - 1] shr eax, 31 lea edi, [rdi + rax] add edi, -1 sar edi vmulsd xmm1, xmm3, xmm1 vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 jmp .LBB0_3 .LBB0_6: vmulsd xmm1, xmm3, xmm1 .LBB0_7: vmovapd xmm0, xmm1 ret
, , . , , . ~10% .
. .
int64_t sum(int64_t x, int64_t y) { return x + y; } int64_t add1(int64_t x) { return sum(x, 1); } int64_t add2(int64_t x) { return sum(1, x); } int64_t add3(int64_t x) { return sum(-1, x) + 2; }
sum(long, long): # @sum(long, long) lea rax, [rdi + rsi] ret add1(long): # @add1(long) mov esi, 1 # . jmp sum(long, long) # TAILCALL add2(long): # @add2(long) mov rax, rdi # . mov edi, 1 mov rsi, rax jmp sum(long, long) # TAILCALL add3(long): # @add3(long) push rax # rax . mov rax, rdi # , add2, . mov rdi, -1 mov rsi, rax call sum(long, long) add rax, 2 # 2 . pop rcx ret
, , call
, jmp
. , , sum
, , add
.
, 10%.
:
. 2D :
struct Point { double x, y; }; struct ZeroPoint { double x{}, y{}; }; struct NanPoint { double x{quietNaN}, y{quietNaN}; };
Point
. ZeroPoint
. IEEE 754-1985:
The number zero is represented specially: sign = 0 for positive zero, 1 for negative zero; biased exponent = 0; fraction = 0;
memset
. NanPoint
numeric_limits<double>::quiet_NaN();
, .
Point data;
sub rsp, 24
- .
ZeroPoint data; Point data{};
.
sub rsp, 40 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp + 16], xmm0
. xmm0
. XOR
vxorps
. .
NanPoint data;
sub rsp, 40 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0]
, .
:
static constexpr size_t smallSize = 8; static constexpr size_t bigSize = 321; extern size_t smallUnknownSize;
.
array<Point, smallSize> data;
โ .
sub rsp, 136
, .
array<ZeroPoint, smallSize> data; array<ZeroPoint, smallSize> data{}; array<Point, smallSize> data{};
.
sub rsp, 192 vxorps ymm0, ymm0, ymm0 vmovaps ymmword ptr [rsp + 128], ymm0 vmovaps ymmword ptr [rsp + 96], ymm0 vmovaps ymmword ptr [rsp + 64], ymm0 vmovaps ymmword ptr [rsp + 32], ymm0
256 , 2 .
array<NanPoint, smallSize> data; array<NanPoint, smallSize> data{};
sub rsp, 136 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0]
.
array<Point, bigSize> data;
sub rsp, 5144
.
array<ZeroPoint, bigSize> data; array<ZeroPoint, bigSize> data{}; array<Point, bigSize> data{};
sub rsp, 5152 lea rbx, [rsp + 16] xor esi, esi mov edx, 5136 mov rdi, rbx call memset # memset(rsp+16, 0, 5136).
, memset
. , , rdi, esi, edx
. e
d
32 64- .
array<NanPoint, bigSize> data;
sub rsp, 5144 lea rax, [rsp + 8] lea rcx, [rsp + 5144] vmovaps xmm0, xmmword ptr [rip + .LCPI0_0]
. , 321 3 . rax, rcx
.
array<NanPoint, bigSize> data{};
sub rsp, 5152 lea rbx, [rsp + 16] xor esi, esi mov edx, 5136 mov rdi, rbx call memset # memset(rsp+16, 0, 5136). lea rax, [rsp + 5152] vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] # . .LBB0_1: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rbx], xmm0 vmovups xmmword ptr [rbx + 16], xmm0 vmovups xmmword ptr [rbx + 32], xmm0 add rbx, 48 cmp rbx, rax jne .LBB0_1
. , memset
. , , . .
vector<Point> data(smallSize);
sub rsp, 40 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 128 call operator new(unsigned long) # new(128). mov qword ptr [rsp], rax mov rcx, rax sub rcx, -128 mov qword ptr [rsp + 16], rcx vxorps xmm0, xmm0, xmm0 # . vmovups xmmword ptr [rax + 16], xmm0 vmovups xmmword ptr [rax], xmm0 vmovups xmmword ptr [rax + 32], xmm0 vmovups xmmword ptr [rax + 48], xmm0 vmovups xmmword ptr [rax + 64], xmm0 vmovups xmmword ptr [rax + 80], xmm0 vmovups xmmword ptr [rax + 96], xmm0 vmovups xmmword ptr [rax + 112], xmm0
new
. , T{}
, , . ZeroPoint
NanPoint
. :
vector<Point> data(bigSize);
main: # @main push rbx sub rsp, 48 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) # new(5136). mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov rcx, rax add rcx, 5136 mov qword ptr [rsp + 16], rcx vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp + 32], xmm0 xor edx, edx # . .LBB0_2: # =>This Inner Loop Header: Depth=1 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx], xmm0 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx + 16], xmm0 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx + 32], xmm0 add rdx, 48 cmp rdx, 5136 jne .LBB0_2 mov qword ptr [rsp + 8], rcx mov rax, rsp
, . NanPoint
.
vector<NanPoint> data(bigSize);
main: # @main push rbx sub rsp, 32 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) # new(5136). mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov rcx, rax add rcx, 5136 mov qword ptr [rsp + 16], rcx xor edx, edx vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] # NAN. .LBB0_2: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax + rdx], xmm0 vmovups xmmword ptr [rax + rdx + 16], xmm0 vmovups xmmword ptr [rax + rdx + 32], xmm0 add rdx, 48 cmp rdx, 5136 jne .LBB0_2 mov qword ptr [rsp + 8], rcx mov rax, rsp
A ZeroPoint
.
vector<ZeroPoint> data(bigSize);
sub rsp, 32 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) # new(5136). mov qword ptr [rsp], rax mov rbx, rax add rbx, 5136 mov qword ptr [rsp + 16], rbx xor esi, esi mov edx, 5136 mov rdi, rax call memset # memset(&data, 0, 5136).
memset
. , , memset
Point
. , , bigUnknownSize
.
vector<NanPoint> data(bigUnknownSize);
sub rsp, 32 mov rbx, qword ptr [rip + bigUnknownSize] vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 test rbx, rbx # bigUnknownSize == 0, new. je .LBB0_1 mov rax, rbx shr rax, 60 jne .LBB0_3 mov rdi, rbx shl rdi, 4 # 16 4. call operator new(unsigned long) # new(bigUnknownSize*16). jmp .LBB0_6 .LBB0_1: xor eax, eax .LBB0_6: mov rcx, rbx shl rcx, 4 add rcx, rax mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov qword ptr [rsp + 16], rcx test rbx, rbx # bigUnknownSize == 0, . je .LBB0_14 lea rdx, [rbx - 1] mov rsi, rbx and rsi, 7 je .LBB0_10 neg rsi vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] # 0-7 . .LBB0_9: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax], xmm0 dec rbx add rax, 16 inc rsi jne .LBB0_9 .LBB0_10: cmp rdx, 7 jb .LBB0_13 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] # 8. .LBB0_12: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax], xmm0 vmovups xmmword ptr [rax + 16], xmm0 vmovups xmmword ptr [rax + 32], xmm0 vmovups xmmword ptr [rax + 48], xmm0 vmovups xmmword ptr [rax + 64], xmm0 vmovups xmmword ptr [rax + 80], xmm0 vmovups xmmword ptr [rax + 96], xmm0 vmovups xmmword ptr [rax + 112], xmm0 sub rax, -128 add rbx, -8 jne .LBB0_12 .LBB0_13: mov rax, rcx .LBB0_14: mov qword ptr [rsp + 8], rax mov rax, rsp
. , LBB0_9
, 8. 8 .
, Point
, ZeroPoint
memset
:
vector<ZeroPoint> data(bigUnknownSize);
sub rsp, 40 mov rbx, qword ptr [rip + bigUnknownSize] vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 test rbx, rbx # bigUnknownSize == 0, new. je .LBB0_1 mov rax, rbx shr rax, 60 jne .LBB0_3 mov rdi, rbx shl rdi, 4 call operator new(unsigned long) # new(bigUnknownSize*16). jmp .LBB0_6 .LBB0_1: xor eax, eax .LBB0_6: mov rdx, rbx shl rdx, 4 lea r14, [rax + rdx] mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov qword ptr [rsp + 16], r14 test rbx, rbx # bigUnknownSize == 0, memset. je .LBB0_8 xor esi, esi mov rdi, rax call memset # memset(&data, 0, bigUnknownSize*16). mov rax, r14 .LBB0_8: mov qword ptr [rsp + 8], rax mov rax, rsp
,
vector<NanPoint> data; data.resize(bigUnknownSize);
250 , . operator new
, .
.
Code | Cycles per iteration |
---|
Point p; | 4.5 |
ZeroPoint p; | 5.2 |
NanPoint p; | 4.5 |
array<Point, smallSize> p; | 4.5 |
array<ZeroPoint, smallSize> p; | 6.7 |
array<NanPoint, smallSize> p; | 6.7 |
array<Point, bigSize> p; | 4.5 |
array<ZeroPoint, bigSize> p; | 296.0 |
array<NanPoint, bigSize> p; | 391.0 |
array<Point, bigSize> p{}; | 292.0 |
array<NanPoint, bigSize> p{}; | 657.0 |
vector<Point> p(smallSize); | 32.3 |
vector<ZeroPoint> p(smallSize); | 33.8 |
vector<NanPoint> p(smallSize); | 33.8 |
vector<Point> p(bigSize); | 323.0 |
vector<ZeroPoint> p(bigSize); | 308.0 |
vector<NanPoint> p(bigSize); | 281.0 |
vector<ZeroPoint> p(smallUnknownSize); | 44.1 |
vector<NanPoint> p(smallUnknownSize); | 37.6 |
vector<Point> p(bigUnknownSize); | 311.0 |
vector<ZeroPoint> p(bigUnknownSize); | 315.0 |
vector<NanPoint> p(bigUnknownSize); | 290.0 |
vector<NanPoint> p; p.resize(bigUnknownSize); | 315.0 |
:
- .
- .
memset
, .- .
- , . .
- . .
- , . .