logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[Rozwiązano] std::swap a typy atomowe w C++23 – czy można użyć, alternatywy dla std::atomic

27 Kwi 2025 11:52 324 9
REKLAMA
  • #1 21532179
    Konto nie istnieje
    Poziom 1  
  • REKLAMA
  • #2 21532181
    ElektrodaBot
    AI od OpenAI ChatGPT4
    Posty: 8867
    Pomógł: 364
    Ocena: 954
    khoam napisał:
    @ElektrodaBot
    Czy std::swap można użyć dla typów atomowych w C++23? Jeżeli nie, to zaproponuj inną metodę.
    Bezpośrednia odpowiedź
    Nie – w C++23 `std::swap` nie jest zdefiniowany dla `std::atomic<T>`.
    Powód: `std::atomic<T>` nie jest ani kopiowalny, ani przenoszalny, a standardowa implementacja `std::swap` wymaga co najmniej przenoszenia. Kompilator zgłosi błąd już na etapie SFINAE:

    Kod: Text
    Zaloguj się, aby zobaczyć kod


    Aby zamienić zawartość dwóch atomików, użyj jednej z poniższych metod.

    ---

    Szczegółowa analiza problemu

    1. Dlaczego `std::swap` nie działa z `std::atomic`
    1. `[atomics.types.general]` – obiekty `std::atomic<T>` „are neither CopyConstructible nor CopyAssignable”.
    2. `[utility.swap]` – domyślna wersja `std::swap` wymaga MoveConstructible lub CopyConstructible.
    3. W C++23 nie dodano żadnej wyspecjalizowanej przeciążonej wersji swap dla atomików (propozycja P1135 została odrzucona).

    W efekcie wywołanie `std::swap(a,b)` z `a,b` typu `std::atomic<T>` jest nie-formalne (ill-formed, no diagnostic required).

    2. Co naprawdę oznacza „zamiana” dwóch atomików
    W większości przypadków chodzi o wymianę wartości *A* ↔ *B* z poszanowaniem wymagań:
    • każda pojedyncza operacja na zmiennej jest atomowa;
    • lecz brakuje gwarancji, że inny wątek nie zobaczy stanu przejściowego (bo modyfikujemy dwa adresy).

    Jednopunktowa, dwunadresowa wymiana (ang. double-word swap) nie jest przenośnie dostępna w bibliotece standardowej; trzeba posiłkować się pętlą CAS lub zewnętrzną blokadą.

    3. Praktyczne sposoby

    3.1. Prosty „prawie swap” – dwa `exchange`
    Dla kodu, w którym nie przeszkadza krótkie okno z niespójnym stanem (najczęściej wystarcza):
    Kod: Text
    Zaloguj się, aby zobaczyć kod

    • każda operacja jest atomowa na pojedynczej zmiennej;
    • „swap” nie jest transakcyjny – chwilowo `a==b_old` i `b==b_old`.

    3.2. Pętla CAS – brak stanu przejściowego
    Jeśli musi być „wszystko-albo-nic”, potrzebny jest algorytm wykorzystujący `compare_exchange`:

    Kod: Text
    Zaloguj się, aby zobaczyć kod

    • brak okna niespójności;
    • może odnotowywać *livelock* pod bardzo dużą konkurencją – praktycznie rzadko problem.

    3.3. Zewnętrzna blokada
    Najprościej (i często najwydajniej przy dużym ruchu):

    Kod: Text
    Zaloguj się, aby zobaczyć kod

    • brak ryzyka zakleszczeń CAS;
    • sekcja krytyczna jest krótka, ale wymaga mutexu.

    3.4. `std::atomic_ref`
    Gdy obiekty są przechowywane „luzem”, można utworzyć referencje atomowe i użyć *zwykłego* `std::swap`:

    Kod: Text
    Zaloguj się, aby zobaczyć kod

    Nadaje się głównie do kodu jednowątkowego, gdzie czasowo potrzebujemy bezpieczeństwa pojedynczych zapisów.

    ---

    4. Wybór metody – praktyczne kryteria
    ScenariuszZalecane rozwiązanie
    Zamiana w kodzie jednowątkowym`atomic_swap_relaxed()` (lub zwykłe zmienne)
    Zamiana w kodzie wielowątkowym, niski konflikt`atomic_swap_relaxed()`
    Wymagana brak-przejściowość, niski konfliktPętla CAS (`atomic_swap_cas()`)
    Silna konkurencja na danychMuteks / spin-lock
    Wbudowany sprzętowy `xchg2`/`cmpxchg16b`Własne asemblerowe CSR lub intrinsics


    ---

    5. Standardy i bezpieczeństwo
    • Biblioteka standardowa nie gwarantuje wieloadresowych atomowych prymitywów.
    • Jeśli projekt jest wrażliwy na wydajność/latencję, rozważ instrukcje dwuwyrazowe (Intel `CMPXCHG16B`, ARM `LDXP/STXP`) – ale to już kod zależny od architektury.
    • Zachowaj spójne porządki pamięci (`memory_order`) dla wszystkich trzech operacji (`load`, `exchange`, `store`), aby uniknąć nieoczekiwanych reorderów.

    ---

    6. Co dalej?
    1. Jeżeli swap ma być częstą operacją, zmierz realny koszt CAS-ów vs mutexu na docelowej platformie.
    2. Sprawdź, czy Twój kompilator nie udostępnia rozszerzeń (GCC/Clang `__atomic_exchange`/`__atomic_compare_exchange`) obejmujących dwuwyrazowe CAS.
    3. Jeśli temat Cię interesuje, zapoznaj się z propozycją P1135R0 „Atomic swap for `std::atomic<T>`”, która wyjaśnia, dlaczego nie trafiła do standardu.

    ---

    Podsumowanie
    `std::swap` nie obsługuje `std::atomic<T>` w C++23.
    Do zamiany wartości zastosuj:
    • najprościej dwie operacje `exchange` + `store`,
    • dla „prawdziwej” atomowości – pętlę z `compare_exchange` lub blokadę.

    W razie dalszych pytań o szczegóły implementacyjne albo testy wydajności – pisz śmiało.
    Możesz mnie wywołać w każdym temacie pisząc @ElektrodaBot. Zastrzeżenie: Niektóre odpowiedzi udzielone przez ElektrodaBota mogą być niedokładne. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi ElektrodaBota powinny być zweryfikowane przez użytkownika. OpenAI może monitorować i zapisywać informacje wprowadzane po wywołaniu ElektrodaBota.
  • #3 21532183
    Konto nie istnieje
    Poziom 1  
  • REKLAMA
  • Pomocny post
    #4 21532434
    Damian_Max
    Poziom 21  
    Posty: 397
    Pomógł: 40
    Ocena: 96
    Chyba go zacytowałeś, bez 'wywołania' ;-)
  • #5 21532438
    Konto nie istnieje
    Poziom 1  
  • #6 21532443
    ElektrodaBot
    AI od OpenAI ChatGPT4
    Posty: 8867
    Pomógł: 364
    Ocena: 954
    khoam napisał:
    @ElektrodaBot

    Ta wersja wygląda najlepiej. Zweryfikuj jej użycie pod kątem pracy w środowisku ESP-IDF/FreeRTOS, w tym ISR.
    Bezpośrednia odpowiedź
    Funkcja `atomic_swap_relaxed()` (zamiana za pomocą `exchange + store`) jest:
    • poprawna i wystarczająco szybka w zwykłych wątkach FreeRTOS na ESP32 (dla typów ≤ 32 bit),
    niewskazana do uruchamiania bezpośrednio w ISR, o ile swap dwóch zmiennych musi być traktowany jako jedna „nierozrywalna” operacja lub ISR ma być ultrakrótkie.

    W przerwaniu zaleca się:
    1. albo wykonać prostą zamianę w sekcji krytycznej ISR (stały, krótki czas),
    2. albo tylko ustawić flagę/queue i odłożyć właściwy swap do zadania FreeRTOS.

    ---

    1. `std::atomic` na ESP32 (ESP-IDF 5.x)

    CechaStan na ESP32-LX6/ESP32-S3 (GCC 12+, `-std=c++23`)
    Rozmiar natywnie atomowy8, 16, 32 bit (instrukcje `S32C1I` / `WSR.SCOMPARE1`)
    ≥ 64 bit (podstawowe rdzenie)realizowane w bibliotece przy użyciu spin-locka (portENTER\_CRITICAL) – nie w ISR
    `std::atomic<T>::exchange()`1 instrukcja dla 32 bit; dla większych lock
    `compare_exchange_*`pętla HW‐CAS (32 bit) lub lock
    Wielordzeniowość (SMP)FreeRTOS używa globalnego spin-locka, `std::atomic` już to respektuje


    Wniosek: dla 32-bitowej zmiennej `atomic_swap_relaxed()` kompiluje się do trzech pojedynczych instrukcji (load, store, store) bez odwołań do RTOS, więc *może* działać w ISR, ale:

    • operacja nie jest jednopunktowa (dwa adresy),
    • pętla CAS ma czas nieograniczony – w ISR to ryzyko.

    ---

    2. Ocena trzech wariantów w kontekście FreeRTOS/ISR

    WariantKod (skrót)Czy dopuszczalny w zadaniu?Czy dopuszczalny w ISR?PlusyMinusy
    A. `atomic_swap_relaxed` (2× `exchange`)`tmp = a.exchange(b); b.store(tmp);`TAK (dla ≤ 32 bit)TYLKO jeśli niespójny stan jest akceptowalny i typ ≤ 32 bitprosty, stały czaskrótkie „okno” niespójności; nie transakcyjny
    B. pętla CAS (`compare_exchange`)CAS-loopTAKNIE – nieograniczona długość, ryzyko żywego zakleszczenia w ISRbrak stanu przejściowegonie-deterministyczny czas
    C. Sekcja krytyczna w ISR (`portENTER_CRITICAL_ISR`)maskowanie przerwań + zwykłe load/storeTAKTAK (zalecane gdy swap musi być jednopunktowy)gwarancja spójności, stały czasblokuje przerwania o niższym/equal priorytecie


    ---

    3. Przykłady kodu

    3.1. Zalecane podejście „flagowe” (zero logiki w ISR)

    Kod: Text
    Zaloguj się, aby zobaczyć kod


    • ISR trwa kilka cykli,
    • swap wykonywany w zadaniu – dowolnie złożony algorytm dozwolony.

    3.2. Swap „na siłę” w ISR z sekcją krytyczną

    Kod: Text
    Zaloguj się, aby zobaczyć kod


    Warunki:
    • kod trzyma się < ~12–20 µs (zależnie od projektu),
    • nie wolno wywoływać funkcji, które mogą wchodzić do scheduler-locka ani korzystać z `std::atomic` > 32 bit.

    ---

    4. Detaile pamięciowe

    1. ISR na Xtensa automatycznie „widzi” najświeższe dane (brak L1-cache).
    2. Dla komunikacji ISR–zadanie zwykle wystarcza `memory_order_relaxed` wewnątrz sekcji krytycznej; poza nią – `acquire/release` albo `seq_cst`.
    3. FreeRTOS SMP: `portENTER_CRITICAL[_ISR]` wyłącza przerwania na bieżącym rdzeniu i chwyta globalny spin-lock – chroni przed równoległym dostępem z drugiego rdzenia.

    ---

    5. Rekomendacja praktyczna

    1. Jeżeli tylko można, nie rób złożonych operacji w ISR. Sygnalizuj przez flagę lub kolejkę.
    2. Gdy swap musi zostać wykonany w ISR:
    • jeśli wystarcza niespójny stan – użyj `atomic_swap_relaxed()` (32 bit),
    • jeśli niespójność jest niedopuszczalna – zrób zwykły swap wewnątrz `portENTER_CRITICAL_ISR`.
    3. Nie używaj pętli CAS w ISR – brak gwarancji czasu.
    4. Typy > 32 bit wymagają locka nawet poza ISR – w przerwaniu są niedozwolone.

    ---

    6. Co sprawdzić przed wdrożeniem

    • Czy obie zmienne mogą być modyfikowane z innych ISR o wyższym priorytecie – jeśli tak, sama sekcja krytyczna nie wystarczy.
    • Czy aplikacja korzysta z trybu PRO CPU + APP CPU; jeśli tak, testy swapu wykonuj przy pełnym obciążeniu dwóch rdzeni.
    • Zmierz czasy: `esp_timer_get_time()` przed/po ISR, zwłaszcza przy łączu Wi-Fi/BLE, gdzie przerwy > 100 µs bywają krytyczne.

    ---

    7. Podsumowanie

    • `std::swap` nadal nie jest specjalizowany dla `std::atomic` → trzeba użyć własnych rozwiązań.
    • `atomic_swap_relaxed()` działa w zadaniach, a w ISR tylko gdy akceptujesz krótkie „okno” niespójności i typ ≤ 32 bit.
    • Dla pełnej jednopunktowości w ISR użyj krótkiej sekcji krytycznej (`portENTER_CRITICAL_ISR`).
    • Najczystsze i najbezpieczniejsze rozwiązanie: przenieść swap do zadania, a w ISR jedynie ustawić flagę.

    Jeśli podasz więcej danych (rozmiar typu, wymagania czasowe ISR, liczba rdzeni), wskażę dokładny wzorzec kodu lub intrinsics dla ESP32-S3.
    Możesz mnie wywołać w każdym temacie pisząc @ElektrodaBot. Zastrzeżenie: Niektóre odpowiedzi udzielone przez ElektrodaBota mogą być niedokładne. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi ElektrodaBota powinny być zweryfikowane przez użytkownika. OpenAI może monitorować i zapisywać informacje wprowadzane po wywołaniu ElektrodaBota.
  • REKLAMA
  • #7 21532467
    Konto nie istnieje
    Poziom 1  
  • REKLAMA
  • #8 21532477
    ElektrodaBot
    AI od OpenAI ChatGPT4
    Posty: 8867
    Pomógł: 364
    Ocena: 954
    khoam napisał:
    @ElektrodaBot Czy w zwykłych wątkach (poza ISR) można użyć vPortEnterCriticalSafe() oraz vPortExitCriticalSafe() zamiast stosowania operacji atomowych?
    Bezpośrednia odpowiedź
    W zadaniach (wątkach) FreeRTOS działających pod ESP-IDF można wywoływać
    `vPortEnterCriticalSafe()` / `vPortExitCriticalSafe()` zamiast używać `std::atomic`,
    ale należy traktować to wyłącznie jako cięższą alternatywę.
    Operacje atomowe są szybsze, nie wyłączają przerwań i nadają się do prostych,
    jednosłowowych modyfikacji.
    Sekcję krytyczną wybierz tylko wtedy, gdy:

    • potrzebujesz nierozrywalnego bloku obejmującego więcej niż jedną zmienną lub krok,
    • sprzętowe atomiki dla danego typu (np. 64-bit na ESP32-D0WD) nie są obsługiwane,
    • dopuszczasz krótkie wyłączenie przerwań na bieżącym rdzeniu.

    ---

    Szczegółowa analiza problemu

    1. Co dokładnie robią funkcje „Safe”
    Kod: text
    Zaloguj się, aby zobaczyć kod

    1. Sprawdzają kontekst wywołania (task / ISR).
    2. Chwytają spin-locka `mux`.
    3. Podnoszą maskę przerwań do `configMAX_SYSCALL_INTERRUPT_PRIORITY`
    (na Xtensie = poziom 3), czyli blokują wszystkie przerwania o niższym lub równym priorytecie.
    4. W `Exit…` przywracają poprzedni stan i zwalniają lock.

    Dzięki temu jeden interfejs działa zarówno w zadaniu, jak i w ISR,
    a wywołanie w złym kontekście kończy się `abort()` – stąd przydomek *Safe*.

    2. Porównanie z `std::atomic`

    WłaściwośćSekcja krytyczna (`vPortEnterCriticalSafe`)`std::atomic` / intrinsics
    Blokuje przerwaniaTAK (na rdzeniu)NIE
    Blokuje przełączanie zadańTAK (scheduler wstrzymany)NIE
    Koszt czasowy (ESP32)~70-120 cykli + zależy od I-cache1–3 cykle (load/store)
    Chroni wiele zmiennych narazTAKNIE
    Ryzyko dead-lockTAK (jeśli w sekcji zawołasz inne primitywy)brak
    Kontrola porządkowania (MO)brak jawnej kontroli; pełny barrierprecyzyjne `memory_order`
    Przyjazność dla SMPumiarkowana (spin-lock globalny)dobra (lock-free)
    Przenośność do innych RTOSsłababardzo dobra


    3. Kiedy sekcja krytyczna ma sens
    1. Operacje wielokrokowe: np. zamiana dwóch zmiennych, modyfikacja struktury,
    odczyt-modyfikacja-zapis kilku pól.
    2. Typy > 32 bit na rdzeniach, które nie mają natywnego CAS (np. 64-bit na starym
    ESP32-LX6).
    3. Krótkie, rzadko wykonywane fragmenty – np. inicjalizacja singletonu.
    4. Kod, dla którego brak responsywności <= 10 µs nie jest krytyczny.

    4. Kiedy pozostać przy atomikach
    1. Liczniki zdarzeń, flagi, proste stany – wykonywane często.
    2. Komunikacja między zadaniem a ISR, gdzie wariant `std::memory_order_relaxed /
    release / acquire` pozwala uniknąć barier pełnych.
    3. Platformy SMP (ESP32-S3, ESP32-C6), w których częste „globalne maskowanie
    przerwań” degraduje równoległość.

    5. Przykłady kodu

    5.1. Sekcja krytyczna w zadaniu
    Kod: Text
    Zaloguj się, aby zobaczyć kod

    Trwa ~0,5 µs (XT-240 MHz), wyłącza Wi-Fi/BLE ISR-y w tym oknie.

    5.2. Ta sama operacja atomikami
    Kod: Text
    Zaloguj się, aby zobaczyć kod

    Zero blokowania przerwań; latencja << 100 ns.

    6. Wytyczne czasowe ESP-IDF
    Espressif zaleca, aby czas blokowania przerwań nie przekraczał 10 µs
    (7 µs dla łącza Wi-Fi, 4 µs dla BLE LL).
    `vPortEnterCriticalSafe` spełni to, jeśli sekcja zawiera < ~100 instrukcji.

    7. Dobre praktyki
    1. Trzymaj sekcje krytyczne jak najkrótsze; bez alokacji, logu, printf.
    2. Nie wywołuj z nich prymitywów blokujących (mutex, queue, `vTaskDelay`).
    3. Jeśli potrzebna jest dłuższa ochrona – użyj mutexu FreeRTOS lub
    `std::mutex` (przekompiluje się do tego samego), zamiast globalnie gasić
    przerwania.
    4. Przy pracy na dwóch rdzeniach testuj z aktywnym Wi-Fi/BLE, bo to generuje
    najwięcej ISR-ów (priorytet 1-3).
    5. Dla 64-bitowych zmiennych na ESP32-S3/C6 warto użyć
    `std::atomic<uint64_t>` – kompilator wygeneruje dopasowany CAS-loop z wewnętrznym
    spin-lockiem, więc ręczna sekcja krytyczna zwykle niczego nie poprawi.

    ---

    Podsumowanie
    • Tak, `vPortEnterCriticalSafe()` / `vPortExitCriticalSafe()` mogą zastąpić
    `std::atomic` w kodzie zadań, ale kosztem blokowania przerwań i gorszej
    skalowalności.
    • Dla pojedynczych słów i krótkich operacji preferuj `std::atomic`;
    dla bloków wielokrokowych lub typów nieobsługiwanych sprzętowo – użyj sekcji krytycznej.
    • Pamiętaj o limitach czasowych RTOS-a i zawsze mierz realne opóźnienia na docelowej platformie.
    Możesz mnie wywołać w każdym temacie pisząc @ElektrodaBot. Zastrzeżenie: Niektóre odpowiedzi udzielone przez ElektrodaBota mogą być niedokładne. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi ElektrodaBota powinny być zweryfikowane przez użytkownika. OpenAI może monitorować i zapisywać informacje wprowadzane po wywołaniu ElektrodaBota.
  • #9 21532484
    Konto nie istnieje
    Poziom 1  
  • Pomocny post
    #10 21532492
    gulson
    Administrator Systemowy
    Posty: 29463
    Pomógł: 150
    Ocena: 6097
    Ja Ci napiszę lepszy numer, zapytasz dnia następnego i będą jeszcze inne odpowiedzi ;)

Podsumowanie tematu

✨ W C++23 nie można użyć `std::swap` dla typów `std::atomic`, ponieważ obiekty atomowe nie są kopiowalne ani przenoszalne, a standardowa implementacja `std::swap` wymaga co najmniej przenoszenia. Propozycja dodania specjalizacji swap dla atomików (P1135) została odrzucona. Alternatywą jest implementacja własnej funkcji zamiany, np. wykorzystującej operacje `exchange` i `store`, co jest efektywne i poprawne dla typów atomowych do 32 bit na platformie ESP32 z FreeRTOS, ale niezalecane do użycia bezpośrednio w ISR, gdzie wymagana jest nierozrywalność operacji. W ISR zaleca się stosowanie sekcji krytycznych lub odłożenie operacji do zadania FreeRTOS. W zadaniach FreeRTOS można używać funkcji `vPortEnterCriticalSafe()` i `vPortExitCriticalSafe()` jako alternatywy dla atomików, jednak jest to cięższe rozwiązanie, blokujące przerwania i mniej skalowalne. Operacje atomowe są szybsze i nie wyłączają przerwań, nadają się do prostych, jednosłowowych modyfikacji. Decyzja o wyborze metody zależy od wymagań dotyczących nierozrywalności, rozmiaru typu atomowego oraz środowiska wykonawczego (np. ESP-IDF/FreeRTOS na ESP32).
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA