//Autor: Arkadiusz Kojtek , 2023.01.15
//Dokumentacja:
//(Program działa w wersji C++20)
//Wstep:
//Na podstawie napisanego przez programiste kodu C++, kompilator tworzy kod maszynowany planujac przy tym kolejnosc
//wykonywanych przez maszyne procedur.
//
//Kompilatory ciagle sie rozwijaja, coraz lepiej radzac sobie z optymalizacja i planowaniem wykonywanego kodu by jeszcze
//bardziej przyspieszyc wykonywanie napisanych przez programiste procedur. W tej optymalizacji niekiedy jednak przeszkadza
//brak informacji kompilatora dotyczacej prawdopodobienstwa wykonania sie danej galezi.
//
//Kilka istniejacych kompilatorow zaczelo tworzyc wlasne implementacje adnotacji ktore umozliwiliby zasugerowanie
//kompilatorowi ktora sciezka jest bardziej prawdopodobna niz inne np. definicja macro
//(#define likely(x) __builtin_expect(!!(x), 1)) korzystajaca z __builtin_expect()
//
//Tak tez w wersji C++ 20 postanowiono wprowadzic wlasna implementacje atrybutow [[likely]] oraz [[unlikely]]
//ktore umozliwilyby wieksza niezaleznosc pisanego kodu, ktory nie musialby byc uzalezniony od implementacji danego
//kompilatora tworzac bardziej przenosny kod.
//
//Same atrybuty nie sa czyms jednak czesto widocznym i uzywanym w kodzie. Nie jest to magiczna rozdzka sprawiajaca ze
//niezoptymalizowany kod zacznie  lepiej dzialac, a wspolczesne kompilatory juz dostatecznie dobrze radza sobie z
//planowaniem optymalnego wykonania kodu.
//
//Zastosowanie:
//Jednak, atrybuty [[likely]] oraz [[unlikely]], mimo ze niszowe, wciaz maja swoje zastosowanie. Szczegolne zastosowanie
//dla takich atrybutow mozna znalezc np. w kodzie jadra Linuxa gdzie, dzieki swiadomosci prawdopodobienstwa danej sciezki
//programista moze dodatkowo zoptymalizowac wykonanie swojego kodu.
//
//Mimo to, jest to wciaz rozwiazanie niszowe, w wiekszosci wypadkow programista nie powinien poswiecac czytelnosci kodu
//zasmiecajac je dopisywaniem atrybutow [[likely]] oraz [[unlikely]]. W wiekszosci wypadkow przewidywanie wykonania galezi
//przez kompilator dziala dostatecznie dobrze. Same atrybuty lepiej zostawic do najglebszych petli, o krytycznym dla
//wydajnosci zastosowaniu. A nawet wtedy, zastosowanie tych petli powinno byc uprzedzone wnikliwa analiza wydajnosci
//przeprowadzona przez programiste.
//
//Niestety, tak jak juz pisalem, te atrybuty nie sprawia ze slaby kod zacznie lepiej dzialac. Zamiast tego, oprocz utraty
//czytelnosci moga one wrecz zaszkodzic wykonywaniu sie kodu, sugerujac kompilatorowi by ten zaplanowal w nieoptymalny
//sposob kolejnosc wykonywania sie procedur.
//
//Przede wszystkim jako programista powinno sie uwazac by:
//1. nie uzywac kilkukrotnie atrybutow prawdopodobienstwa w tej samej sciezce,
//2. jako prawdopodobna okresl maksymalnie tylko jedna sciezke z kilku,
//3. nie stosowac nadmiernie tych atrybutow, w znamienitej ilosci przypadkow techniki optymalizacji uzywane przez
//   kompilator poradza sobie znacznie lepiej z ocena prawdopodobienstwa niz programista
//
//Testy:
//Poniezej znajduje sie test dzialania, atrybutow [[likely]] oraz [[unilekly]] jednak jak mozna zauwazyc roznice albo sa
//minimalne albo wrecz na niekorzysc dodawania atrybutow. Podczas pisania kodu, zauwazylem ze bardzo ciezko jest znalezc
//przyklad aplikacji ktora umozliwilaby realne pokazanie roznicy miedzy uzyciem a nie tychze atrybutow. Problem polega
//na tym ze, jak juz wspomnialem, jest to rozwiazanie bardzo niszowe ktore ciezko przedstawic w prostej aplikacji. Mozna
//jednak zauwazyc sile PGO (profile-guided optimization), ktora potrafi bez tych atrybutow dostatecznie dobrze
//zoptymalizowac wykonanie kodu.
//
//Podsumowanie:
//Atrybuty [[likely]] oraz [[unlikely]] zostaly wprowadzone by ustandaryzowac optymalizacje pomiedzy kompilatorami.
//Wczesniej niestety rozwiazania byly uzaleznione od kompilatora co czynilo z nich bardziej nieprzenosne rozwiazanie.
//Dzieki nim mozemy zasugerowac kompilatorowi ktora sciezka, lub czesc kodu przez nas napisana jest bardziej prawdopodobna
//umozliwiajac jego optymalizacje, powodujac przyspieszenie wykonania naszego kodu.
//Jednak wciaz, uzywac powinnismy ich z rozwaga, poniewaz ich nieumiejetne wykorzystanie moze przyniesc odwrotne skutki
//powodujac spadki wydajnosci.


#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>


namespace with_attributes {
    constexpr double pow(double x, long long n) noexcept {
        if (n > 0) [[likely]]
            return x * pow(x, n - 1);
        else [[unlikely]]
            return 1;
    }
    constexpr long long fact(long long n) noexcept {
        if (n > 1) [[likely]]
            return n * fact(n - 1);
        else [[unlikely]]
            return 1;
    }
    constexpr double cos(double x) noexcept {
        constexpr long long precision{16LL};
        double y{};
        for (auto n{0LL}; n < precision; n += 2LL) [[likely]]
                                                           y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
        return y;
    }
}  // namespace with_attributes

namespace no_attributes {
    constexpr double pow(double x, long long n) noexcept {
        if (n > 0)
            return x * pow(x, n - 1);
        else
            return 1;
    }
    constexpr long long fact(long long n) noexcept {
        if (n > 1)
            return n * fact(n - 1);
        else
            return 1;
    }
    constexpr double cos(double x) noexcept {
        constexpr long long precision{16LL};
        double y{};
        for (auto n{0LL}; n < precision; n += 2LL)
            y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n));
        return y;
    }
}  // namespace no_attributes

double gen_random() noexcept {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_real_distribution<double> dis(-1.0, 1.0);
    return dis(gen);
}

volatile double sink{}; // ensures a side effect

int main() {
    for (const auto x : {0.125, 0.25, 0.5, 1. / (1 << 26)}) {
        std::cout
                << std::setprecision(53)
                << "x = " << x << '\n'
                << std::cos(x) << '\n'
                << with_attributes::cos(x) << '\n'
                << (std::cos(x) == with_attributes::cos(x) ? "equal" : "differ") << '\n';
    }

    auto benchmark = [](auto fun, auto rem) {
        const auto start = std::chrono::high_resolution_clock::now();
        for (auto size{1ULL}; size != 10'000'000ULL; ++size) {
            sink = fun(gen_random());
        }
        const std::chrono::duration<double> diff =
                std::chrono::high_resolution_clock::now() - start;
        std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count()
                  << " sec " << rem << std::endl;
    };

    benchmark(with_attributes::cos, "(with attributes)");
    benchmark(no_attributes::cos, "(without attributes)");
    benchmark([](double t) { return std::cos(t); }, "(std::cos)");
}

// zrodla i linki - bibliografia
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0479r0.html
// https://medium.com/software-design/likely-unlikely-directives-802c09bd5232
// https://en.cppreference.com/w/cpp/language/attributes/likely
// https://blog.aaronballman.com/2020/08/dont-use-the-likely-or-unlikely-attributes/
// https://iq.opengenus.org/cpp-likely-and-unlikely-attributes/
