Šta je novo?

Implementacija std::atomic u C++98

MasterChief2

Slavan
Učlanjen(a)
01.03.2015
Poruke
927
Poena
215
Moja oprema  
CPU & Cooler
AMD Ryzen 7 5700X3D i Cooler Master Hyper 212 Evo
Matična ploča
MSI B350 Tomahawk
RAM
G.SKILL 32GB Aegis DDR4 3200MHz CL16 KIT F4-3200C16D-32GIS
GPU
SAPPHIRE NITRO+ Radeon RX 580 8GB GDDR5
Storage
2 x 2TB HDD, 1 x WD_BLACK 2TB SN750, 1 x Samsung SSD 870 QVO 1TB
Zvuk
Integrisana zvucna + Kenwood KA-1500 + Pioneer CS-999
PSU
Be Quiet! Straightpower BQT E6-700W
Kućište
Spawn Gerovit LD01
Monitor
Tesla 27GM620BF 27"/FullHD/165Hz
Miš & tastatura
A4Tech Bloody V7 i Dell SK-8175
Laptop
MacBook Pro (13-inch, M1, 2020)
Mobilni telefon
LeEco Le Max 2 6/128GB
Steam
antonic901
Pristup internetu
  1. Optički internet
Ukratko:
Portujem XBMC/Kodi na Original Xbox i limitiran sam C++98 standardom. Skoro sam u potpunosti uspeo da backportujem GUILIB sa Kodi Krypton v17, ali kako se polako priblizavam novijim verzijama Kodija sve vise imam problema sa funkcijama koje nedostaju u C++98. Jednja od njih koja mi je sada preko potrebna da bi resio neki deadlock jeste std::atomic. Napisao sam dve implementacije za atomicne operacije nad bool vrednoscu, ali nisam siguran da li je i koja ispravna (ako je i jedna). Pa bih molio za pomoc malo iskusnijih C/C++ programera. Ovo je su implementacije:

1. Upotrebom Win native threading funkcija:
C++:
class AtomicBool
{
public:
  AtomicBool() : value(0) {}

  AtomicBool(bool newValue)
  {
    set(newValue);
  }

  AtomicBool& operator=(bool newValue)
  {
    set(newValue);
    return *this;
  }

  operator bool() const
  {
    return get();
  }

private:
  bool get() const
  {
    return InterlockedCompareExchange(const_cast<LONG*>(&value), 1, 1) == 1;
  }

  void set(bool newValue)
  {
    InterlockedExchange(const_cast<LONG*>(&value), newValue ? 1 : 0);
  }

  LONG value;
};

2. Upotrebom AtomicIncrement/AtomicDecrement koje sam iskopa u starijim verzijama Kodija:
Kod:
class AtomicBool
{
public:
  AtomicBool() : value(0) {}

  AtomicBool(bool newValue)
  {
    set(newValue);
  }

  AtomicBool& operator=(bool newValue)
  {
    set(newValue);
    return *this;
  }

  operator bool() const
  {
    return get();
  }

private:
  bool get() const
  {
    return value == 1;
  }

  void set(bool newValue)
  {
    newValue ? AtomicIncrement(&value) : AtomicDecrement(&value);
  }

  volatile LONG value;
};

U prilogu sam zakacio Atomics.cpp u kom se nalaze Kodijeve metode. Da li je i jedna od ovih implementacija validna i ako nije da li neko ima ideju kako simulirati std::atomic u C++98?
 

Prilozi

  • Atomics.txt
    13 KB · Pregleda: 0
Nisam godinama napisao liniju C++ koda ali mogu da te usmerim na jedno alternativno rešenje. C++11 standard je nastao tako što su uzeli gomilu stvari iz Boost biblioteke:


i ozvaničili ih kao standard. Naravno preimenovali su sve namespace-ove. Umesto da se mučiš da portuješ kod za C++98, možda jednostavno možeš da iskompajliraš Boost biblioteku i iskoristiš nju. Portovanje Kodi-a bi onda trebalo u većini slučajeva da se svodi na preslikavanje 1:1 iz C++11 (ili koji već) na Boost.

 
Nazalost boost::atomic nije dostupan, barem ne u poslednjoj verziji (1.47.0) koja je kompatibilna sa MSVC7.1 (Visual Studio .NET 2003). Ali da, koristim dosta stvari iz boosta koje su zavrsile u C++11 standardu (shared_ptr, ref, static_cast itd.)

U sustini treba mi neko resenje koje ce mi garantovati konzistentost za obicnu bool promenljivu kojoj pristupa vise addona istovremeno.

I to resenje je privremeno, jer planiram sve da prebacim na Visual Studio 2022 i C++23, ali to jos uvek ne mogu zvanicno da uradim jer samo nekolicina nas sa Xbox-Scene ima pristup pecovanom XDK-u sa C++23 kom jos uvek ne radi debugging.

Ovo je jos jedno resenje koje mi je jedan covek sa Xbox-Scene predlozio:
C++:
template<typename T1>
class atomic
{
public:
  atomic()
  {
    InitializeCriticalSection(&m_Mutex);
  }

  atomic(T1 value)
  {
    InitializeCriticalSection(&m_Mutex);
    set(value);
  }

  ~atomic()
  {
    DeleteCriticalSection(&m_Mutex);
  }

  atomic& operator=(T1 value)
  {
    set(value);
    return *this;
  }

  operator bool()
  {
    return read();
  }

private:
  void set(T1 value)
  {
    EnterCriticalSection(&m_Mutex);
    m_value = value;
    LeaveCriticalSection(&m_Mutex);
  }

  T1 read()
  {
    EnterCriticalSection(&m_Mutex);
    T1 result = m_value;
    LeaveCriticalSection(&m_Mutex);
    return result;
  }

  CRITICAL_SECTION m_Mutex;
  T1 m_value;
}
 
Critical section za bool/int je ubistvo performansi.
Koji instruction set koristi taj Xbox? Za arm/x86 su Read i Write Bool-a uvek atomic.
E sad ako Set treba da vrati staro stanje onda mora InterlockedExchange. U sustini interlocked ti sve mogu odraditi, ne znam koliko ti treba da reimplementiras std:atomic

Koje sve f-je ti trebaju?
 
U Xbox-u je Pentium 3, dakle x86. Ne znam da li ti znaci mnogo, ali ovaj PR pokusavam da bekportujem:

Znaci po tebi ovaj m_cancelled bio mogao biti obicna bool varijabla, nije mi potreba std::atomic na x86?

Cisto iz radoznalosti, zasto je Critical section los za perfomanse?
 
Alternativni pristup koji vidim da nije pomenut - mogao bi koristiti nativne atomične instrukcije samog x86 CPU kroz malo inline assemblera.

Kod koji mi je ChatGPT dao kada sam mu ovo zahtevao, verovatno će trebati nešto modifikacija, ali ilustruje pristup:

C++:
class AtomicBool {
public:
    AtomicBool() : value(0) {}

    explicit AtomicBool(bool initial) : value(initial ? 1 : 0) {}

    bool load() const {
        int result;
        __asm__ __volatile__ (
            "lock; xchgl %0, %1"
            : "=r" (result), "+m" (value)
            : "0" (value)
            : "memory"
        );
        return result;
    }

    void store(bool desired) {
        __asm__ __volatile__ (
            "lock; xchgl %0, %1"
            : "+m" (value), "+r" (desired)
            :
            : "memory"
        );
    }

    bool compare_exchange_strong(bool &expected, bool desired) {
        int original;
        __asm__ __volatile__ (
            "lock; cmpxchgl %2, %1"
            : "=a" (original), "+m" (value)
            : "r" (desired), "0" (expected)
            : "memory"
        );
        bool success = (original == expected);
        if (!success) expected = original;
        return success;
    }

private:
    mutable volatile int value;
};
 
C98 bi trebao da ima InterlockedExchange, InterlockedCompareExchange itd., nema potrebe za asembler?

@MasterChief2, za ovaj m_cancelled iz linka atomic ne znaci nista. Jedino je pametno da se mozda napravi da bude "volatile", da kasnije ne bi pravio bug. Ali atomic je nepotreban u bas ovom slucaju.
 
C98 bi trebao da ima InterlockedExchange, InterlockedCompareExchange itd., nema potrebe za asembler?
Zar to onda nije ova implementacija koju sam gore postavio?
C++:
public:
  AtomicBool() : value(0) {}

  AtomicBool(bool newValue)
  {
    set(newValue);
  }

  AtomicBool& operator=(bool newValue)
  {
    set(newValue);
    return *this;
  }

  operator bool() const
  {
    return get();
  }

private:
  bool get() const
  {
    return InterlockedCompareExchange(const_cast<LONG*>(&value), 1, 1) == 1;
  }

  void set(bool newValue)
  {
    InterlockedExchange(const_cast<LONG*>(&value), newValue ? 1 : 0);
  }

  LONG value;
};
@MasterChief2, za ovaj m_cancelled iz linka atomic ne znaci nista. Jedino je pametno da se mozda napravi da bude "volatile", da kasnije ne bi pravio bug. Ali atomic je nepotreban u bas ovom slucaju.
Onda cu ostaviti da bude obican bool, pa ako VFS addoni budu pravili probleme, dodacu da bude atomicno.

Alternativni pristup koji vidim da nije pomenut - mogao bi koristiti nativne atomične instrukcije samog x86 CPU kroz malo inline assemblera.
To je upravo kako je Kodi radio pre nego sto su kompletno presli na C++11. To je ova implementacija gde koristim metode iz Atomics.cpp:
C++:
class AtomicBool
{
public:
  AtomicBool() : value(0) {}

  AtomicBool(bool newValue)
  {
    set(newValue);
  }

  AtomicBool& operator=(bool newValue)
  {
    set(newValue);
    return *this;
  }

  operator bool() const
  {
    return get();
  }

private:
  bool get() const
  {
    return value == 1;
  }

  void set(bool newValue)
  {
    newValue ? AtomicIncrement(&value) : AtomicDecrement(&value);
  }

  volatile LONG value;
};
 
Za slucaj da hoces svoj std atomic:
PItanje je sta = operator treba da uradi.
Ako je stari value, onda ne moze kroz Set, vec mora da koristi Interlocked direktno da bi dobio stari value.
Ako treba novi value onda ovo nije atomic, treba da vrati newValue a ne *this.
 
Za sad cu staviti da bude obicna bool promenljiva, posto ni meni sada nije jasno kako dva VFS addon-a dele taj resurs. Ako bude bilo kakvih problema onda cu videti kako ih resiti. Hvala!

Inace izgleda da su me lose informisali da XDK podrzava samo C++98. Cisto iz radoznalosti sam pokusao da kompajliram XBMC sa boost 1.57.0 i proslo je bez ikakvih problema. Onda samo malo vise istrazio i nasao sam na netu da MSVC7.1 (koji se koristi kao kompajler u XDK-u) podrzava C++99, negde sam naisao i na podatke da je C++03 (ili kako god mu je pravo ime) takodje podrzan u nekim aspektima. Probao sam boost::atomic, boost::move i jos neke stvari koje su mi trebale i kod se kompajlira. E sad da li radi to sve kako treba, veliko je pitanje..
 
Nazad
Vrh Dno