Як вказати вказівник як сховище thread_local у C++?

Наразі я працюю над оптимізацією фрагмента коду математичних операцій, де він виконує цикл над сховищем вказівників і зберігає результат на місці. Я помітив, що при кожному призначенні компілятор видає інструкцію збереження пам’яті, ось таку (vmovaps, яку слід помітити):

        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

однак, коли я змінив вказівник на локальну змінну «стекового масиву», тобто T top_data_c[1024], інструкція збереження з’являється лише в кінці циклу:

        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

виявилося, що компілятор утримує дію зберігання покажчиків від оптимізації через небезпеку потоку.

Оголошення масиву стека або копіювання тимчасової змінної з і назад виглядає таким брудним у такій реалізації, чи є спосіб зробити таке зберігання вказівників потокобезпечним для компілятора? Звичайно, таке обчислення є абсолютно потокобезпечним (воно працює схоже на GPU).


person Midori Yakumo    schedule 14.03.2019    source джерело


Відповіді (1)


Якщо ви з якоїсь причини не оголосили вказівник top_data_c як volatile, компілятор може вільно припускати, що з даними, які він записує, не відбуваються зовнішні (включаючи одночасні) модифікації, тому відсутність оптимізації не є причиною потоків (і не вирішується потоком). локальна семантика).

Справжньою проблемою тут є накладання псевдонімів — компілятор не може припустити, що top_data_c[pc] не знаходиться десь усередині bottom_data_hwc (це може бути те саме, що bottom_data_hwc[o4 + pc], хто знає?), тому він повинен робити сховища. Що полегшує цю проблему у випадку "стекового масиву", так це (якщо я не помиляюся) той факт, що top_data_c є масивом, а не вказівником (а не те, що він знаходиться в стеку або що він локальний для потоку).

Короткий екскурс у суворі правила псевдонімів: компілятор може припустити, що вказівники на несумісні типи (наприклад, int і double) не можуть вказувати на те саме розташування. Якщо ви перебуваєте всередині void foo(int* x, double* y), тоді, відповідно до строгих правил псевдонімів, запис до x не може змінити те, що ви читаєте з y (і навпаки), тому компілятор може змінювати порядок або вилучати сховища та читати до/з x та y у цій функції як воно хоче.

Але всередині void foo(double* x, double* y) ця гарантія зникає. Якщо x == y (або x == y + n), то запис і читання в x і y потенційно можуть взаємодіяти один з одним, і компілятор не має іншого вибору, крім як виконувати кожне завантаження/зберігання.

Ви повинні переглянути ключові слова, пов’язані з restrict, які сигналізують компілятору про те, що «доступ до x відбувається тільки через x (та його копії) у цій функції». Деякі матеріали:

Що означає ключове слово restrict у 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
Круто! Дякую Макс! Я додав restrict, і це працює відповідно до моїх очікувань! - person Midori Yakumo; 14.03.2019
comment
Ще одне запитання, як restrict працює з вказівником const? Чому це memcpy (void *__restrict __dest, const void *__restrict __src), а не memcpy (void *__restrict __dest, const void *src)? - person Midori Yakumo; 15.03.2019
comment
const void* означає, що ви не можете писати у вказане місце, тому restrict фактично виключає лише псевдонім читання (оскільки ви не можете писати), окрім того, що const не взаємодіє з restrict... Але також немає особливої ​​користі від обмеження обидва проти обмеження одного, якщо ці два є єдиними покажчиками, розіменованими у функції: godbolt.org/z/VuSQFu. Але розміщення restrict в обох аргументах робить суть зрозумілою для користувача та дозволяє реалізаціям дерефікувати глобальні змінні покажчика без псевдонімів, що спричиняє проблеми з продуктивністю. - person Max Langhof; 15.03.2019