Как да посочите указател като 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

обаче, когато промених указателя в локална променлива "stack array", т.е. 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