Czy ta implementacja C ++ AtomicInt jest poprawna?

9

Przesłanka: Pracuję w środowisku ARM (prawie bez systemu), w którym nawet nie mam C ++ 11 (z std::atomic<int>) dostępnym, więc proszę unikać odpowiedzi typu „ po prostu użyj standardowego C ++std::atomic<int> ”: nie mogę .

Czy ta implementacja ARM AtomicInt jest poprawna? (załóżmy, że architektura ARM to ARMv7-A )

Czy widzisz jakiś problem z synchronizacją? Czy jest to volatilewymagane / przydatne?

// File: atomic_int.h

#ifndef ATOMIC_INT_H_
#define ATOMIC_INT_H_

#include <stdint.h>

class AtomicInt
{
public:
    AtomicInt(int32_t init = 0) : atom(init) { }
    ~AtomicInt() {}

    int32_t add(int32_t value); // Implement 'add' method in platform-specific file

    int32_t sub(int32_t value) { return add(-value); }
    int32_t inc(void)          { return add(1);      }
    int32_t dec(void)          { return add(-1);     }

private:
    volatile int32_t atom;
};

#endif
// File: arm/atomic_int.cpp

#include "atomic_int.h"

int32_t AtomicInt::add(int32_t value)
{
    int32_t res, prev, tmp;

    asm volatile(

    "try:    ldrex   %1, [%3]\n"     // prev = atom;
    "        add     %0, %1, %4\n"   // res = prev + value;
    "        strex   %2, %0, [%3]\n" // tmp = outcome(atom = res); // may fail
    "        teq     %2, #0\n"       // if (tmp)
    "        bne     try"            //     goto try; /* add failed: someone else modified atom -> retry */

    : "=&r" (res), "=&r" (prev), "=&r" (tmp), "+mo" (atom)  // output (atom is both in-out)
    : "r" (value)                                           // input
    : "cc");                                                // clobbers (condition code register [CPSR] changed)

    return prev; // safe return (local variable cannot be changed by other execution contexts)
}

Staram się również ponownie wykorzystać kod, dlatego wyizolowałem tylko jedną podstawową funkcję do zaimplementowania w kodzie specyficznym dla platformy ( add()metoda wewnątrz arm/atomic_int.cpp).

Czy atomic_int.hnaprawdę jest przenośny, ponieważ znajduje się na różnych platformach / architekturach / kompilatorach? Czy to podejście jest wykonalne ? (Z wykonalnym mam na myśli wykonalność dla każdej platformy, aby zagwarantować atomowość poprzez wdrożenie tylko tej add()metody ).

tutaj jest odpowiednia implementacja ARM GCC 8.3.1 tej samej funkcji. Najwyraźniej jedyną prawdziwą różnicą jest obecność dmbprzed i po. Czy są naprawdę potrzebne w moim przypadku? Dlaczego? Czy masz przykład, w którym moje AtomicInt(bez dmb) zawodzi?

AKTUALIZACJA: poprawiono implementację, usunięto get()metodę rozwiązywania problemów z atomicznością i wyrównaniem. Teraz add()zachowuje się jak standard fetchAndAdd().

gentooise
źródło
volatilesłowo kluczowe w C ++ oznacza, że ​​nie optymalizuj poprzez zmienną. Więc get()metoda korzyści z niego. Chociaż, ogólnie, lotność ma wkrótce deprycates w C ++. Jeśli Twój system nie może wbudować synchronizacji danych 32-bitowych, nie masz innego wyboru, jak korzystać z muteksów - przynajmniej spinlocka.
ALX23z,
Jakiej wersji architektury ramienia używasz? armv-7?
Mike van Dyke,
1
Nie dotyczy to pytania, ale nazwy zawierające dwa kolejne znaki podkreślenia ( __ATOMIC_INT_H_) i nazwy rozpoczynające się znakiem podkreślenia, po którym następuje wielka litera, są zarezerwowane do użycia przez implementację. Nie używaj ich w swoim kodzie.
Pete Becker,
atomicPrawdopodobnie najlepiej nie używać nazwy członka, aby uniknąć pomyłek std::atomic, choć rodzi to pytanie, dlaczego w ogóle nie używałbyś tego.
Clifford,
Dodano architekturę ARM, zmieniono nazwę __ATOMIC_INT_H_identyfikatora.
gentooise,

Odpowiedzi:

2

Jeśli używasz gcc, możesz użyć starszych __syncwbudowanych funkcji dostępu do pamięci atomowej :

void add(int volatile& a, int value) {
    __sync_fetch_and_add(&a, value);
}

Generuje :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
źródło
Niestety nie korzystam gcc, aw każdym razie nie chcę wiązać implementacji z żadnym konkretnym kompilatorem. W każdym razie dziękuję za podpowiedź, przynajmniej mówi mi, że moja add()część ARM powinna być poprawna. Jaka jest różnica między ldxri ldrex?
gentooise
Jest to ARM8 (np. 64-bitowy), a nie jedna z 32-bitowych wersji.
marko
Udało mi się uzyskać odpowiedni kod, określając docelową architekturę: link . Wygląda na to, że GCC wstawia dmbprzed i po pętli ldrex/ strex.
gentooise
2
Myślę, że jest to dobre podejście, ale aby uczynić go niezależnym od kompilatora, po prostu przejdź do godbolt.org/z/WB8rxw w funkcji, której chcesz używać za pomocą wbudowanych gcc i skopiuj odpowiednie dane wyjściowe zestawu. Pamiętaj, aby dopasować parametr -march do konkretnej wersji ARM.