Bagaimana cara menentukan pointer sebagai penyimpanan thread_local di C++?

Saat ini saya sedang berupaya mengoptimalkan sepotong kode operasi matematika, yang mengulangi penyimpanan pointer dan menyimpan hasilnya di tempatnya. Saya perhatikan bahwa pada setiap penugasan, kompiler mengeluarkan instruksi penyimpanan memori, seperti ini (yang vmovaps harus diperhatikan):

        114 [1]                         top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bf70  <+ 2624>        c4 c1 78 10 0c 02                 vmovups (%r10,%rax,1),%xmm1
0x55555558bf76  <+ 2630>        48 83 c1 01                       add    $0x1,%rcx
0x55555558bf7a  <+ 2634>        c4 c3 75 18 4c 02 10 01           vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm1,%ymm1
0x55555558bf82  <+ 2642>        c4 c2 25 a8 0c 04                 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bf88  <+ 2648>        c4 c1 7c 29 0c 04                 vmovaps %ymm1,(%r12,%rax,1)
        115 [1]                         top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bf8e  <+ 2654>        c4 c1 78 10 04 01                 vmovups (%r9,%rax,1),%xmm0
0x55555558bf94  <+ 2660>        c4 c3 7d 18 44 01 10 01           vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bf9c  <+ 2668>        c4 c2 7d b8 ca                    vfmadd231ps %ymm10,%ymm0,%ymm1
0x55555558bfa1  <+ 2673>        c4 c1 7c 29 0c 04                 vmovaps %ymm1,(%r12,%rax,1)
        116 [1]                         top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bfa7  <+ 2679>        c4 c1 78 10 04 00                 vmovups (%r8,%rax,1),%xmm0
0x55555558bfad  <+ 2685>        c4 c3 7d 18 44 00 10 01           vinsertf128 $0x1,0x10(%r8,%rax,1),%ymm0,%ymm0
0x55555558bfb5  <+ 2693>        c4 c2 75 98 c1                    vfmadd132ps %ymm9,%ymm1,%ymm0
0x55555558bfba  <+ 2698>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
        117 [1]                         top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bfc0  <+ 2704>        c5 f8 10 0c 07                    vmovups (%rdi,%rax,1),%xmm1
0x55555558bfc5  <+ 2709>        c4 e3 75 18 4c 07 10 01           vinsertf128 $0x1,0x10(%rdi,%rax,1),%ymm1,%ymm1
0x55555558bfcd  <+ 2717>        c4 c2 75 b8 c0                    vfmadd231ps %ymm8,%ymm1,%ymm0
0x55555558bfd2  <+ 2722>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bfd8  <+ 2728>        48 83 c0 20                       add    $0x20,%rax
0x55555558bfdc  <+ 2732>        48 39 4d c0                       cmp    %rcx,-0x40(%rbp)
0x55555558bfe0  <+ 2736>        77 8e                             ja     0x55555558bf70

namun, ketika saya mengubah pointer menjadi variabel "stack array" lokal, yaitu T top_data_c[1024], instruksi penyimpanan hanya muncul di akhir loop:

        114 [1]                         top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bbe0  <+ 1712>        c5 f8 10 0c 03                    vmovups (%rbx,%rax,1),%xmm1
0x55555558bbe5  <+ 1717>        48 83 c1 01                       add    $0x1,%rcx
0x55555558bbe9  <+ 1721>        c4 e3 75 18 4c 03 10 01           vinsertf128 $0x1,0x10(%rbx,%rax,1),%ymm1,%ymm1
0x55555558bbf1  <+ 1729>        c4 c2 25 a8 0c 04                 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bbf7  <+ 1735>        c5 fc 28 c1                       vmovaps %ymm1,%ymm0
        115 [1]                         top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bbfb  <+ 1739>        c4 c1 78 10 0c 03                 vmovups (%r11,%rax,1),%xmm1
0x55555558bc01  <+ 1745>        c4 c3 75 18 4c 03 10 01           vinsertf128 $0x1,0x10(%r11,%rax,1),%ymm1,%ymm1
0x55555558bc09  <+ 1753>        c4 c2 7d 98 ca                    vfmadd132ps %ymm10,%ymm0,%ymm1
        116 [1]                         top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bc0e  <+ 1758>        c4 c1 78 10 04 02                 vmovups (%r10,%rax,1),%xmm0
0x55555558bc14  <+ 1764>        c4 c3 7d 18 44 02 10 01           vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm0,%ymm0
0x55555558bc1c  <+ 1772>        c4 e2 35 b8 c8                    vfmadd231ps %ymm0,%ymm9,%ymm1
        117 [1]                         top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bc21  <+ 1777>        c4 c1 78 10 04 01                 vmovups (%r9,%rax,1),%xmm0
0x55555558bc27  <+ 1783>        c4 c3 7d 18 44 01 10 01           vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bc2f  <+ 1791>        c4 c2 75 98 c0                    vfmadd132ps %ymm8,%ymm1,%ymm0
0x55555558bc34  <+ 1796>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bc3a  <+ 1802>        48 83 c0 20                       add    $0x20,%rax
0x55555558bc3e  <+ 1806>        48 3b 8d c8 fb ff ff              cmp    -0x438(%rbp),%rcx
0x55555558bc45  <+ 1813>        72 99                             jb     0x55555558bbe0

muncul bahwa kompiler menjauhkan tindakan penyimpanan penunjuk dari pengoptimalan karena ketidakamanan threadnya.

Mendeklarasikan susunan tumpukan atau penyalinan variabel sementara dari dan ke belakang terlihat sangat kotor dalam implementasi seperti itu, adakah cara untuk membuat penyimpanan penunjuk seperti itu aman untuk pengertian kompiler? Tentu saja perhitungan seperti itu benar-benar aman untuk thread (cara kerjanya sangat mirip dengan GPU).


person Midori Yakumo    schedule 14.03.2019    source sumber


Jawaban (1)


Kecuali Anda karena alasan tertentu mendeklarasikan penunjuk top_data_c sebagai volatile, kompiler bebas berasumsi bahwa tidak ada modifikasi luar (termasuk secara bersamaan) yang terjadi pada data yang ditulisnya, sehingga kurangnya optimasi tidak bisa disalahkan pada threading (dan tidak diselesaikan oleh thread semantik lokal).

Masalah sebenarnya di sini adalah aliasing - kompiler tidak dapat berasumsi bahwa top_data_c[pc] tidak ada di dalam bottom_data_hwc (bisa jadi sama dengan bottom_data_hwc[o4 + pc], siapa tahu?), jadi ia harus melakukan penyimpanan. Apa yang meringankan masalah ini dalam kasus "array tumpukan" adalah (jika saya tidak salah) fakta bahwa top_data_c adalah sebuah array, bukan sebuah pointer (bukan karena ia ada di tumpukan atau bahwa itu adalah thread-lokal).

Perjalanan singkat ke aturan aliasing yang ketat: Kompilator dapat berasumsi bahwa pointer ke tipe yang tidak kompatibel (misalnya int dan double) tidak dapat menunjuk ke lokasi yang sama. Jika Anda berada di dalam void foo(int* x, double* y) maka, berdasarkan aturan aliasing yang ketat, menulis ke x tidak dapat mengubah apa yang Anda baca dari y (dan sebaliknya), sehingga kompiler bebas menyusun ulang atau menghilangkan penyimpanan dan membaca ke/dari x dan y dalam fungsi ini sebagai itu inginkan.

Namun di dalam void foo(double* x, double* y), jaminan ini hilang. Jika x == y (atau x == y + n) kemudian menulis dan membaca ke x dan y berpotensi berinteraksi satu sama lain, dan kompiler tidak punya pilihan selain melakukan setiap pemuatan/penyimpanan.

Anda harus melihat kata kunci terkait restrict, yang memberi sinyal kepada kompiler "akses ke x terjadi hanya melalui x (dan salinannya) dalam fungsi ini". Beberapa materi:

Apa arti kata kunci pembatas di C++?

https://cellPerformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html

http://assemblyrequired.crashworks.org/load-hit-stores-and-the-__restrict-keyword/

person Max Langhof    schedule 14.03.2019
comment
Dingin! Terima kasih Maks! Saya menambahkan membatasi dan itu berfungsi sesuai harapan saya! - person Midori Yakumo; 14.03.2019
comment
Pertanyaan lain, bagaimana cara kerja restrict pada pointer const? Kenapa memcpy (void *__restrict __dest, const void *__restrict __src) tapi bukan memcpy (void *__restrict __dest, const void *src) ? - person Midori Yakumo; 15.03.2019
comment
const void* berarti Anda tidak dapat menulis ke lokasi yang ditunjuk, jadi restrict secara efektif hanya mengecualikan aliasing baca (karena Anda tidak dapat menulis), selain itu const tidak memiliki interaksi dengan restrict... Tetapi juga tidak ada manfaat khusus dari pembatasan keduanya vs membatasi yang satu, selama keduanya adalah satu-satunya penunjuk yang direferensikan dalam fungsi: godbolt.org/z/VuSQFu. Namun menempatkan restrict pada kedua argumen memperjelas maksudnya bagi pengguna dan memungkinkan implementasi untuk melakukan deref variabel pointer global tanpa alias menyebabkan masalah kinerja. - person Max Langhof; 15.03.2019