2016-08-03 47 views
6

Próbowałem kontynuować myśl, że używając zarówno zapór pamięci programowych, jak i sprzętowych, mogę wyłączyć optymalizację poza kolejnością dla określonej funkcji wewnątrz kodu skompilowanego z optymalizacją kompilatora i dlatego może implementować oprogramowanie semafora użyciu algorytmów jak Peterson lub Deker, że nie wymaga wykonania poza kolejnością, i testowano następujące kodu z zaporowej SW asm volatile("": : :"memory") i gcc Builtin HW barierę __sync_synchronize:Używanie barier pamięci do wymuszania wykonywania w kolejności

#include <stdio.h> 
int main(int argc, char ** argv) 
{ 
    int x=0; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=1; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=2; 
    asm volatile("": : :"memory"); 
    __sync_synchronize(); 
    x=3; 
    printf("%d",x); 
    return 0; 
} 

Ale plik wyjściowy kompilacji to:

main: 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    mfence 
    mfence 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    mfence 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

A jeśli usunąć bariery i skompilować ponownie, otrzymuję:

main 
.LFB24: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, %edx 
    movl $.LC0, %esi 
    movl $1, %edi 
    xorl %eax, %eax 
    call __printf_chk 
    xorl %eax, %eax 
    addq $8, %rsp 

zarówno skompilowany z gcc -Wall -O2 w Ubuntu 14.04.1 LTS, x86.

Spodziewanym rezultatem było to, że plik wyjściowy kodu zawierającego bariery pamięci będzie zawierał wszystkie przypisania wartości, które mam w moim kodzie źródłowym, z mfence między nimi.

Według powiązanej StackOverflow postu -

gcc memory barrier __sync_synchronize vs asm volatile("": : :"memory")

Dodając swój zespół inline na każdej iteracji, gcc nie jest dopuszczalne, aby zmienić kolejność operacji przeszłość bariery

A później:

Howe ver, kiedy procesor wykonuje ten kod, można zmienić kolejność operacji "pod maską", o ile nie powoduje to uszkodzenia modelu zamawiania pamięci. Oznacza to, że wykonanie tych operacji może być niemożliwe (jeśli procesor obsługuje to, jak większość robi obecnie). Ogrodzenie HW uniemożliwiłoby to.

Ale jak widać, jedyną różnicą między kodem z barier pamięci i kodu bez nich jest to, że były jeden zawiera mfence w taki sposób, że nie spodziewano się go zobaczyć, a nie wszystkie zadania są uwzględnione.

Dlaczego plik wyjściowy pliku z barierami pamięci nie jest zgodny z oczekiwaniem? Dlaczego zamówienie mfence zostało zmienione? Dlaczego kompilator usunął niektóre z zadań? Czy kompilator może dokonywać takich optymalizacji, nawet jeśli zastosowana jest bariera pamięci i oddziela każdą linię kodu?

Odniesienia do rodzaju bariera pamięci i użytkowania:

+1

Terminologia: ** wykonanie poza zleceniem jest oddzielone od zmiany kolejności pamięci **. Nawet procesory w kolejności są potokowe i korzystają z bufora pamięci, szczególnie w przypadku sklepów, których brakuje w L1. (https://en.wikipedia.org/wiki/MESI_protocol#Memory_Barriers. Gdy już wiadomo, że nie są spekulatywne, mogą być śledzone tylko za pomocą logiki zamawiania pamięci (w celu wymuszenia porządkowania StoreStore i LoadStore w razie potrzeby), dopóki zatwierdzić pamięć podręczną L1, po tym jak potok o nich zapomniał.) 'MFENCE' nie tworzy serialu potoku; zmienia się tylko w kolejności, w której operacje pamięci stają się globalnie widoczne. –

Odpowiedz

4

barier pamięci poinformować kompilator/CPU że instrukcja nie powinno się zmieniać kolejności przez barierę, nie oznacza to, że i tak należy czynić zapisy, które można udowodnić, że są bezcelowe.

Jeśli definiować swoje x jak volatile, kompilator nie może sprawić, że założenie, że jest to jedyny podmiot, który dba o x s wartość i musi przestrzegać zasad C abstrakcyjnej maszyny, która służy do zapisu pamięci rzeczywiście się stało.

W konkretnym przypadku można wtedy pominąć bariery, ponieważ jest już zagwarantowane, że niestabilne wejścia nie zostaną wzajemnie uporządkowane.

Jeśli masz wsparcie dla C11, lepiej jest korzystać z _Atomic s, które dodatkowo może zagwarantować, że normalne przypisania nie zostaną zmienione w stosunku do Twojego x i że dostępy są atomowe.


EDIT: GCC (jak dzyń) wydają się być niespójne w tym względzie i nie zawsze zrobi to optimizaton. I opened a GCC bug report regarding this.

+3

Napisałeś o wiele lepszą odpowiedź niż ja. – 2501

+0

Właściwa odpowiedź. Przetestowałem to teraz z 'volatile', a kod z barierą pamięci został skompilowany poprawnie, jak się spodziewałem (podczas gdy kod bez bariery pamięci był wciąż nieco zoptymalizowany). Niestety nie mogę przetestować "atomowego", ponieważ nie mam wsparcia C11. – user2162550

+0

@ 2501 Dzięki. Możesz go przedłużyć, jeśli myślisz, że coś można poprawić. :) – a3f