Pokazywanie postów oznaczonych etykietą CppCon. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą CppCon. Pokaż wszystkie posty

11 lutego 2020

[C++] Atomics

Nie miałem do tej pory wiele do czynienia z atomic-ami (inny wpis) w prawdziwym życiu i traktuje je jako niskopoziomowy mechanizm, ale inni chyba lepiej potrafią wykorzystać ich możliwości. Pozwalają na pisanie kodu "lock-free", chociaż bez głębszego zrozumienia ich natury, niekoniecznie będzie to kod szybszy od tego opartego na muteksach. Ciekawy wykład na ich temat poprowadził Fedor Pikus na CppCon 2017: C++ atomics, from basic to advanced. What do they really do? Warto obejrzeć więcej niż raz.

Operacje na atomic-ach odzwierciedlają operacje sprzętowe i gwarantują, że zostaną wykonane w jednej transakcji (atomowo). CPU oferuje sporą liczbę mechanizmów, które są z nimi związane, z tego też względu standardowa biblioteka jest całkiem rozbudowana. Atomic-iem, może być każdy prymitywny typ (tylko takie obiekty mogą pojawić się w rejestrach CPU).
Przykłady:
// Dla
std::atomic<int> x{0};

// Operacje:
++x;           // atomowy pre-increment
x++;           // atomowy post-increment
x += 1;        // atomowy increment
int y = x * 2; // atomowy odczyt x
x = y + 2;     // atomowy zapis do x

// Uwaga, ta operacja jest niewspierana 
x *= 2;        // ERROR

// Atomowy odczyt x, po którym następuje atomowy zapis do x (dwie operacje)
x = x * 2;
W przykładzie poniżej, atomic posłużył do blokowania wątków, tak aby funkcje even/odd drukowały naprzemiennie tekst w momencie inkrementacji. Uwaga, nie ma gwarancji, że wartość counter wyświetlana na ekranie będzie zgodna z tym co było sprawdzane w if. Są to dwie atomowe operacje odczytu z pamięci, a wartość counter może się zmienić pomiędzy nimi.
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int> counter{0};

void odd(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 1) {
            cout << "Odd  increment: " << counter << endl;
            counter++;
        } else {
            cout << "Odd  check: " << counter << endl;   // wartość mogła się zmienić
        }

        std::this_thread::sleep_for(std::chrono::milliseconds{20});
    }
}

void even(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 0) {
            cout << "Even increment: " << counter << endl;
            counter++;
        } else {
            cout << "Even check: " << counter << endl;   // wartość mogła się zmienić
        }
        std::this_thread::sleep_for(std::chrono::milliseconds{40});
    }
}

int main() {
    constexpr size_t steps{6};
    std::thread t1{odd, steps};
    std::thread t2{even, steps};

    t1.join();
    t2.join();
}

Wynik:
Odd  check: 0
Even increment: 0
Odd  increment: 1
Even increment: 2
Odd  increment: 3
Odd  check: 4
Even increment: 4
Odd  increment: 5
Odd  check: 6
Even increment: 6
Even check: 7
Even check: 7

18 stycznia 2020

Nowości w języku C (C11/C18)

Dwa ciekawe wykłady Dana Saksa na temat nowości w standardzie języka C (np. funkcje inline jako zamienniki makr).



A także o odwrocie od C++ w środowisku embedded (na konferencji CppCon 2016).


23 lipca 2016

gdb + python

Bardzo łagodne wprowadzenie do tematyki użycia pythona w gdb i jeszcze raz na podstawie CppCon 2015: Greg Law "Give me 15 minutes & I'll change your view of GDB"
Na początku jednak kilka komend, które okazały się całkiem pomoce w procesie nauki:
  • start - ustawia się na pierwszej instrukcji naszego programu, w zależności od systemu punkt wejścia może mieć różną nazwę, gdb w takim przypadku zachowa się uniwersalnie.
  • save breakpoints mybbb - zapisanie breakpointów do pliku. Przydatne, gdy trzeba zamknąć gdb, ale nie chcemy niszczyć dorobku naszej sesji.
  • source mybbb - załadowanie breakpointów z pliku
Proste wykorzystanie pythona, do odczytania process id. Aby zakończyć interaktywny tryb należy wpisać end, podobnie jak to się dzieje w przypadku command.
(gdb) python
>import os
>print('pid %d' % os.getpid())
>end
pid 17865
Odczyt informacji o breakpointach:
Reading symbols from a.out...done.
(gdb) b 8
Breakpoint 1 at 0x400589: file main.cpp, line 8.
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400589 in main() at main.cpp:8
(gdb) r
Starting program: /home/user/cpp_gdb_test/a.out 
is i now 7

Breakpoint 1, main () at main.cpp:8
8     i++;
(gdb) python
>print(gdb.breakpoints()[0].location)
>end
/home/user/cpp_gdb_test/main.cpp:8
Ustawienie braekpointa z poziomu pythona:
(gdb) python gdb.Breakpoint('11')
Breakpoint 2 at 0x4005a1: file main.cpp, line 11.
(gdb) c
Continuing.
is i now 8

Breakpoint 2, main () at main.cpp:11
11     printf("Hello world!!!\n");

22 lipca 2016

[CppCon 2015] recording w gdb

Fajny lightning talk na zeszłorocznej konferencji CppCon 2015 traktujący o przydatnych trikach w procesie debugowania programu:



Postanowiłem przećwiczyć sobie kilka mechanizmów. Najciekawszą rzeczą dla mnie był record, który umożliwia odtworzenie wartości pamięci przy reverse debug-u. Szczególnie przydatny, gdy program pisze po stosie i nie można odtworzyć pechowej sekwencji instrukcji z core dumpa. Linki:
Przykładowy program, który w chwili, gdy zostanie wylosowana wartość 4, dla zmiennej v, zaczyna pisać po stosie.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void overflow(int v)
{
    char t[10];

    if (v == 4)
    {
        for (int n = 0; n < 10000; n++)
            t[n] = 'A';
    }
}

int main()
{
    srand(time(0));

    int i;
    do
    {
        i = rand() % 10;
        printf("i is now %d\n", i);
        int v = rand() % 100;
        overflow(v);
    } while(i != 5);

    return 0;
}

Kompilacja, konfiguracja i uruchomienie, trzeba pamiętać o ustawieniu rozmiaru ulimit dla core dump-ów, domyślnie jest to bowiem wartość 0.
ulimit -a 
ulimit -c unlimited 
ulimit -a 

gcc -g main.cpp
while ./a.out ; do echo "OK"; done
gdb a.out -c core
Wynik:
i is now 3
i is now 2
Segmentation fault (core dumped)
Stos został uszkodzony, w tym przypadku widać ślad po ostatniej instrukcji i z jaką wartością została wykonana, ale w przypadku bardziej skomplikowanych struktur danych, może być to kłopotliwe.
Core was generated by `AAAAAAAA'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  __GI_getenv (name=0x7f15a332b3c2 "BC_FATAL_STDERR_", name@entry=0x7f15a332b3c0 "LIBC_FATAL_STDERR_") at getenv.c:84
84  getenv.c: No such file or directory.
(gdb) p i
No symbol "i" in current context.
(gdb) p v
No symbol "v" in current context.
(gdb) p t
No symbol "t" in current context.
(gdb) bt
#0  __GI_getenv (name=0x7f15a332b3c2 "BC_FATAL_STDERR_", name@entry=0x7f15a332b3c0 "LIBC_FATAL_STDERR_") at getenv.c:84
#1  0x00007f15a31d7ef2 in __GI___libc_secure_getenv (name=name@entry=0x7f15a332b3c0 "LIBC_FATAL_STDERR_")
    at secure-getenv.c:29
#2  0x00007f15a321549a in __libc_message (do_abort=do_abort@entry=1, 
    fmt=fmt@entry=0x7f15a332cc7f "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:80
#3  0x00007f15a32b689c in __GI___fortify_fail (msg=<optimized out>, msg@entry=0x7f15a332cc61 "stack smashing detected")
    at fortify_fail.c:37
#4  0x00007f15a32b6840 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x00000000004006bb in overflow (v=4) at main.cpp:14
#6  0x4141414141414141 in ?? ()
#7  0x4141414141414141 in ?? ()
Próba odtworzenia problemu z wykorzystaniem record. gdb przechowa zawartość pamięci, z kolejnych kroków wykonania programu, dzięki czemu można się cofnąć, o jedną komendę (reverse-next), na sam początek nagrywania (reverse-continue), lub skorzystać z innych bliźniaczych komend (reverse-*).

Inne udogodnienia, z których skorzystałem to warunkowy breakpoint w linii 3 (b 26 if v == 4), który zachodzi, gdy zmienna v będzie miała ustawioną wartość na 4. Oraz command, który zostaje wykonany w momencie zatrzymania programu (linie 12-17). Pierwszym argumentem command jest numer porządkowy breakpoint-a, który można uzyskać z info break.
gdb a.out

(gdb) b 26 if v == 4
Breakpoint 1 at 0x400748: file main.cpp, line 26.
(gdb) b 11
Breakpoint 2 at 0x400686: file main.cpp, line 11.
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400748 in main() at main.cpp:26
 stop only if v == 4
2       breakpoint     keep y   0x0000000000400686 in overflow(int) at main.cpp:11
(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>record
>continue
>end
(gdb) r
Starting program: /home/user/cpp_gdb_test/a.out 
i is now 9
i is now 7
i is now 2
i is now 3
i is now 3
i is now 9
i is now 9

Breakpoint 1, main () at main.cpp:26
26         overflow(v);

Breakpoint 2, overflow (v=4) at main.cpp:11
11         for (int n = 0; n < 10000; n++)
(gdb) 
(gdb) bt
#0  overflow (v=4) at main.cpp:11
#1  0x0000000000400752 in main () at main.cpp:26
(gdb) reverse-next
9     if (v == 4)
(gdb) 
6 {
(gdb) 

No more reverse-execution history.
main () at main.cpp:26
26         overflow(v);
(gdb) p i
$5 = 9

13 stycznia 2016

[CppCon 2015] C++11/14/17 atomics and memory model: Before the story consumes you

Model pamięci jest największą zmianą jaką przyniósł nowy standard. A mechanizm atomic-ów na razie sprawiają mi najwięcej problemów. Być może znajdę kiedyś motywację, by bliżej się im przyjrzeć. W tej chwili wszystko co zrobiłem w tym kierunku to sucha lektura. Poniżej, bardzo fajny wykład z tegorocznego CppCon 2015 autorstwa Michaela Wonga, który rozjaśnił mi wiele kwestii.