Правильна ли эта реализация C ++ AtomicInt?

9

Предпосылка: я работаю со встроенной (почти голой) средой ARM, где у меня даже нет C ++ 11 (с std::atomic<int>), поэтому избегайте ответов типа « просто используйте стандартный C ++std::atomic<int> »: я не могу .

Является ли эта ARM- реализация AtomicInt правильной? (предположим, что архитектура ARM - это ARMv7-A )

Вы видите проблему с синхронизацией? Это volatileтребуется / полезно?

// 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)
}

Кроме того, я пытаюсь добиться некоторого повторного использования кода, поэтому я выделил только одну базовую функцию для реализации в специфичном для платформы коде ( add()метод внутри arm/atomic_int.cpp).

Действительно atomic_int.hли он переносим на разных платформах / архитектурах / компиляторах? Является ли этот подход представляется возможным ? (Под осуществимостью я имею в виду выполнимость для каждой платформы, чтобы гарантировать атомарность путем реализации только add()метода ).

вот соответствующая реализация ARM GCC 8.3.1 той же функции. Видимо, единственная реальная разница - это наличие dmbдо и после. Они действительно нужны в моем случае? Почему? У вас есть пример, где мой AtomicInt(без dmb) не удается?

ОБНОВЛЕНИЕ: исправлена ​​реализация, удален get()метод для решения проблем атомарности и выравнивания. Теперь add()ведет себя как стандарт fetchAndAdd().

gentooise
источник
volatileКлючевое слово в C ++ означает не оптимизировать с помощью переменной. Таким образом, get()метод выигрывает от этого. Хотя, в общем, volatile собирается дешифроваться в C ++. Если ваша система не может встроить синхронизацию 32-битных данных, то у вас нет другого выбора, кроме как использовать мьютексы - по крайней мере, спин-блокировки.
ALX23z
Какую версию архитектуры arm вы используете? armv-7?
Майк ван Дайк
1
Это не решает вопрос, но имена, которые содержат два последовательных подчеркивания ( __ATOMIC_INT_H_) и имена, начинающиеся с подчеркивания, за которым следует заглавная буква, зарезервированы для использования реализацией. Не используйте их в своем коде.
Пит Беккер
Имя участника, atomicвероятно, лучше не использовать, чтобы избежать путаницы std::atomic, хотя возникает вопрос, почему вы бы не использовали это в любом случае.
Клиффорд,
Добавлена ​​архитектура ARM, переименован __ATOMIC_INT_H_идентификатор.
gentooise

Ответы:

2

Если вы используете, gccвозможно, вы можете использовать устаревшие __syncвстроенные функции для доступа к атомарной памяти :

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

Создает :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Максим Егорушкин
источник
К сожалению, я не использую gcc, и в любом случае я не хочу связывать реализацию с каким-либо конкретным компилятором. В любом случае, спасибо за ваш совет, по крайней мере, он говорит мне, что моя ARM add()часть должна быть правильной. Какая разница между ldxrа ldrex?
Gentooise
Это ARM8 (eg64bit), а не одна из 32-битных версий.
Marko
Мне удалось получить соответствующий код, указав целевую архитектуру: ссылка . Похоже, что GCC на самом деле помещает dmbдо и после цикла ldrex/ strex.
gentooise
2
Я думаю, что это хороший подход, но чтобы сделать его независимым от компилятора, просто перейдите к типу godbolt.org/z/WB8rxw в нужной функции, используя встроенные функции gcc, и скопируйте соответствующий вывод сборки. Обязательно сопоставьте параметр -march с конкретной версией ARM.