Re[21]: асинхронная сериализация
От: andrew.f  
Дата: 15.01.14 11:43
Оценка: -1 :)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, Lazin, Вы писали:


EP>>>Но на C++, по крайней мере, есть выбор. Я могу принимать итераторы через шаблоны, а могу через type-erasure, а-ля any_iterator. И оптимизировать под то, что важнее.

L>>Это С++, если ф-я инлайнится, то ее тело все равно должно быть в результирующем коде, так как ее может использовать другой модуль.

EP>Что ты имеешь ввиду? Функция вида:

EP>
EP>void foo(any_iterator x);
EP>
компилируется один раз, это не шаблон. Её даже можно вынести в динамическую библиотеку.


EP>>>Часто скорость важнее. Я даже не помню когда последний раз видел -Os — везде -O2/-O3.

L>>Иногда -Os (и -Oa, за компанию) бывает даже быстрее чем -O3, у меня так было в одном из проектов

EP>Иногда такое бывает из-за того, что march/mtune выставлено на GCD (например вместо native), а не из-за размера кода.


L>>>>К тому же, польза от инлайна на x64 и на x86 с соглашением о вызове __fastcall будет не такой уж и большой, параметры в ф-ю будут переданы через регистры, так как их всего два.

EP>>>Кого два? Инлайнятся функции с разным количеством параметров.
L>>В случае предиката сортировки, передается всего два параметра.

EP>Так мы же про общий случай, сортировка — это просто иллюстрация.


qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед", и есть масса других вариантов библиотек сортировки.
В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).

На C можно тоже исходники чистыми хедерниками писать.
И втюхивать в начало каждой функции, как inline, так и static inline, ну и (непотребный конечно вариант, но все таки хоть какая то замена шаблонам из C++) #define.
На выходе получишь те же самые свернутые в одну функцию полезные действия.

И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.

L>>Они легко могут передаваться через регистры процессора, не затрагивая память.


EP>Через память, точнее кэш, будет передаваться RSP. Но проблема даже не в этом.

EP>А в том, что теряется возможность сделать оптимизацию заинлайненного кода (зачастую многоуровневую). И плюс дополнительные инструкции.
EP>Более того, если попытаться сделать аналог std::sort для C, тот там будет ещё пара косвенных вызовов из-за:
EP>
  • произвольного типа последовательности
    EP>
  • произвольного типа элемента (а не только POD, который можно swap-byte-ить)

    EP>>>Конечно же правильный выбор алгоритмов это отличительная особенность C программистов. "Ну и что, что язык медленный — мы вас меньшей сложностью заборим!"

    L>>Я не троллю, просто типичный обитатель С++ тредов как правило любит рассказывать о том как все круто инлайнится и от этого все быстро работает, а в этом вашем Си quicksort указатель на функцию вообоще дергает, это common sense.

    EP>Так действительно всё круто инлайнится, сам неоднократно убеждался И сортировка это лишь конкретный пример.


    L>>Но при этом, наш гипотетический гуру программирования ни разу в жизни не пытался оптимизировать сортировку, выбором алгоритма под конкретные данные


    EP>Я понятия не имею о каком "гипотетическом гуру" ты говоришь.

    EP>Это выглядит как: "вот я знаю сиплюсплюскинка — так он вообще сортировки выбирать не умеет. а вот сишники — другое дело, их медленный qsort буквально подталкивает к правильному выбору алгоритма"

    L>>Стандартную библиотеку С++ писали лучшие умы человечества, поэтому мы будем использовать ее и только ее везде, чтобы было быстро


    EP>Не скажу за всю стандартную библиотеку, но STL действительно писали лучшие умы. По крайней мере это самая лучшая стандартная алгоритмическая библиотека, с самым лучшим интерфейсом и набором алгоритмов.

    EP>Тех же сортировок там три штуки: интроспективная (которая сочетает в себе три типа сортировки), пирамидальная, сортировка слиянием (причём адаптивная, а не в лоб).
    EP>Плюс алгоритмы связанные с сортировкой: разбиение множества (три штуки — разбиение Энтони Хоара, Нико Ломуто и адаптивное стабильное), слияние (две штуки: out-of-place и адаптивное in-place), поворот (out-of-place и in-place(привет Евклиду)), выбор k-го элемента (причём с полезной перестановкой остальных элементов), бинарный поиск (пять штук) и т.д.

    EP>>>Happy-path намного более вероятный — и оптимизировать нужно именно его. Кстати, с кодами ошибок будет засорение всего happy-path runtime проверками.

    L>>Если в коде куча конструкций вроде if (status != SUCCESS) ... и при этом вероятность happy path выше в разы, то branch predictor будет очень точно угадывать ветвления и обработка ошибок практически не будет оказывать влияния на время выполнения.

    EP>Естественно он будет угадывать, особенно после первого прогона.

    EP>Фишка в том, что в случае исключений ему даже угадывать ничего не придётся — cmp+je/whatever не будут разбросаны по всему happy path.

    Какая то подтасовка фактов тут описана.
    Проверка на неудачно совершенную операцию в случае с C++ exception все равно есть.
    И если операция совершилась неудачно, только в этом случае происходит throw.
    Видимо имеется ввиду, когда произошла куча вызовов функций в глубину, и на каждом возврате из вызова функции происходит проверка результата?
    Но ведь это замечательно рефакторится (и без всяких goto) разбиением общей функции с ветвлениями на две:
    — первая функция последовательные действия с проверкой результата от одиночного действия и в случае неудачи возврат во вторую функцию.
    — вторая функция проверяет результат первой и делает то что надо.


    
    static inline
    int level1_get_some(int h)
    {
        int rc;
    
        rc = get_u(h);
        if (0 != rc) return rc;
    
        rc  = get_v(h);
        if (0 != rc) return rc;
    
        rc = get_i(h);
        if (0 != rc) return rc;
    
        ...
        return 0;
    }
    
    int level2_get_some(int h)
    {
        switch(level1_get_some(h))
        {
            case 0: return 0;
    
            case ....: some_fail_action(...); break; 
        }
        return ...;
    }


    Заверни rc = action(...); if (0 != rc) в какой нибудь макрос #define do_action(action) ... — получишь компактность. Прикрути static inline и свернется вся эта портянка в одиночный вызов без хитрых лишних переходов cmp+je через каждый call.
    В общем все это делается, было бы желание...
    Раньше были спецы, про которых говорили: "пишут не на C++, а на C с классами".
    Теперь пора пускать в обиход выражение: "пишут на C, как на C++ без классов"
  • Re[22]: асинхронная сериализация
    От: smeeld  
    Дата: 15.01.14 12:57
    Оценка:
    Здравствуйте, andrew.f, Вы писали:

    AF>
    
    AF>static inline
    AF>int level1_get_some(int h)
    AF>{
    AF>    int rc;
    
    AF>    rc = get_u(h);
    AF>    if (0 != rc) return rc;
    
    AF>    rc  = get_v(h);
    AF>    if (0 != rc) return rc;
    
    AF>    rc = get_i(h);
    AF>    if (0 != rc) return rc;
    
    AF>    ...
    AF>    return 0;
    AF>}
    
    AF>int level2_get_some(int h)
    AF>{
    AF>    switch(level1_get_some(h))
    AF>    {
    AF>        case 0: return 0;
    
    AF>        case ....: some_fail_action(...); break; 
    AF>    }
    AF>    return ...;
    AF>}
    
    AF>


    И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.
    В случае же использования С++ try/catch сплошные call XXXXXXX
    в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.

    AF>Раньше были спецы, про которых говорили: "пишут не на C++, а на C с классами".

    AF>Теперь пора пускать в обиход выражение: "пишут на C, как на C++ без классов"

    Вся прога на С++, с критичными участками на C++ без его классов и ништяков, которые
    не бесплатны, и забирают память и процессор.
    Re[22]: асинхронная сериализация
    От: Evgeny.Panasyuk Россия  
    Дата: 15.01.14 14:32
    Оценка:
    Здравствуйте, andrew.f, Вы писали:

    EP>>Так мы же про общий случай, сортировка — это просто иллюстрация.

    AF>qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед",

    Да какая разница? Вот например GLib:
    gint                (*GCompareFunc)                     (gconstpointer a,
                                                             gconstpointer b);
    void                g_array_sort                        (GArray *array,
                                                             GCompareFunc compare_func);


    AF>и есть масса других вариантов библиотек сортировки.


    Покажи хотя бы пример интерфейса сортировки на C, которая будет принимать любой предикат, любой тип последовательности, с любыми элементами.

    AF>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).


    Пример не равноценный — std::cout медленный потому что много чего делает, и имеет customization points.
    А qsort медленный — потому что интерфейс такой

    AF>На C можно тоже исходники чистыми хедерниками писать.

    AF>И втюхивать в начало каждой функции, как inline, так и static inline, ну и (непотребный конечно вариант, но все таки хоть какая то замена шаблонам из C++) #define.
    AF>На выходе получишь те же самые свернутые в одну функцию полезные действия.

    Покажи интерфейс сортировки, или например rbtree.

    AF>И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.


    Есть тесты?
    Re[22]: асинхронная сериализация
    От: Evgeny.Panasyuk Россия  
    Дата: 15.01.14 14:42
    Оценка:
    Здравствуйте, andrew.f, Вы писали:

    EP>>Фишка в том, что в случае исключений ему даже угадывать ничего не придётся — cmp+je/whatever не будут разбросаны по всему happy path.

    AF>Какая то подтасовка фактов тут описана.

    Где?

    AF>Проверка на неудачно совершенную операцию в случае с C++ exception все равно есть.


    Очевидно, что если такая проверка только в самом низу call-tree, то в случае исключений будет один if. В то время как на C error_code придётся протаскивать наверх через всю call-tree-branch, по всему объёму дерева

    AF>И если операция совершилась неудачно, только в этом случае происходит throw.

    AF>Видимо имеется ввиду, когда произошла куча вызовов функций в глубину, и на каждом возврате из вызова функции происходит проверка результата?

    Да.

    AF>Но ведь это замечательно рефакторится (и без всяких goto) разбиением общей функции с ветвлениями на две:

    AF>- первая функция последовательные действия с проверкой результата от одиночного действия и в случае неудачи возврат во вторую функцию.
    AF>- вторая функция проверяет результат первой и делает то что надо.

    Во-первых у тебя остались те же jump'ы — switch/case, причём даже менее эффективные чем goto.
    Во-вторых от того что ты спрятал runtime проверки в отдельную функцию — они быстрее не стали. Они всё также остались разбросанными по всему happy-path.
    Вот смотри, в случае исключений будет:
    if(error)
       throw something();
    в самом низу call-tree, у тебя же будут проверки (runtime ветвления) по всему call-tree
    Re[23]: асинхронная сериализация
    От: Evgeny.Panasyuk Россия  
    Дата: 15.01.14 14:46
    Оценка: +1
    Здравствуйте, smeeld, Вы писали:

    S>И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.


    Этот вариант даже медленнее чем вариант с goto.

    S>В случае же использования С++ try/catch сплошные call XXXXXXX

    S> в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.

    Подробнее.
    Re[23]: асинхронная сериализация
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 15.01.14 15:02
    Оценка: 7 (1)
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    AF>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).

    что за бред?!
    с каких таких пор эта сишная всезаглатывающая кишка стала быстрее плюсовых потоков?!
    
    #include <cstdio>
    #include <iostream>
    #include <chrono>
    
    enum io_type { test_printf, test_stdio };
    
    template<io_type>
    struct test;
    
    template<>
    struct test<test_printf> {
        template<typename T>
        static std::chrono::milliseconds
        run(const T *arr, std::size_t size) {
            auto start = std::chrono::system_clock::now();
            for ( ; size; --size ) {
                printf("%d", *arr++);
            }
            return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
        }
    };
    
    template<>
    struct test<test_stdio> {
        template<typename T>
        static std::chrono::milliseconds
        run(const T *arr, std::size_t size) {
            auto start = std::chrono::system_clock::now();
            for ( ; size; --size ) {
                std::cout << *arr++;
            }
            return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
        }
    };
    
    int main() {
        enum { size = 1024*1024*4 };
        int *ia = new int[size];
        auto t1 = test<test_printf>::run(ia, size);
        std::cerr << "printf time =" << t1.count() << std::endl;
        auto t2 = test<test_stdio >::run(ia, size);
        std::cerr << "stdio  time =" << t2.count() << std::endl;
    }

    запускаем:

    ./iospeed 1>/dev/null

    получаем:

    printf time =323
    stdio time =183


    да и как вообще может быть такое, чтоб printf() который вообще ничего не знает о типе, выполнялся быстрее типизированного кода?
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[23]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 15.01.14 16:16
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    EP>Во-первых у тебя остались те же jump'ы — switch/case, причём даже менее эффективные чем goto.

    EP>Во-вторых от того что ты спрятал runtime проверки в отдельную функцию — они быстрее не стали. Они всё также остались разбросанными по всему happy-path.
    EP>Вот смотри, в случае исключений будет:
    EP>
    EP>if(error)
    EP>   throw something();
    EP>
    в самом низу call-tree, у тебя же будут проверки (runtime ветвления) по всему call-tree


    if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода
    Re[24]: асинхронная сериализация
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 15.01.14 17:00
    Оценка: +1
    L>if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода

    т.е. ты хочешь сказать, что обработка исключительных ситуаций это штатное выполнение программы?
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[24]: асинхронная сериализация
    От: Evgeny.Panasyuk Россия  
    Дата: 15.01.14 18:14
    Оценка:
    Здравствуйте, Lazin, Вы писали:

    L>if-ы и switch для редко выполняющихся условий, это практически zero overhead.


    Этот "практически zero overhead" будет разбросан по всему коду (который к тому же забивает prediction table).

    L>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch.


    switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности

    L>Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода


    Возможно выбор действия при ошибке будет и быстрее, но по всему happy-path размазывается penalty в виде if'ов.
    Re[25]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 15.01.14 21:39
    Оценка:
    Здравствуйте, niXman, Вы писали:

    L>>if-ы и switch для редко выполняющихся условий, это практически zero overhead. Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch. Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода


    X>т.е. ты хочешь сказать, что обработка исключительных ситуаций это штатное выполнение программы?


    Я ничего такого не хотел сказать. Посмотри на что я отвечал и что я написал в ответ. Мой поинт в том, что runtime проверки кодов ошибок могут вообще не сказываться на времени работы кода, все. Зачем обобщать?
    Re[25]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 15.01.14 21:48
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    L>>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch.

    EP>switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности
    Я не предлагаю заменять goto на switch. Просто сравниваю два способа обработки ошибок — throw catch и код ошибки + switch для последующей обработки.

    L>>Т.е. код, выбирающий обработчик для кода ошибки у компилятора получится более быстрым, нежели код для try catch.. catch.. catch, причем намного. Я уже молчу про локальность этого кода

    EP>Возможно выбор действия при ошибке будет и быстрее, но по всему happy-path размазывается penalty в виде if'ов.
    Несомненно размазывается. Я не утверждаю, что коды ошибок всегда быстрее, я лишь утверждаю что можно написать так, что никакого penalty не будет, я даже небольшой пример написал

    #include <iostream>
    #include <boost/timer.hpp>
    
    using namespace std;
    
    int main()
    {
        const int num_samples = 200000000;
        float* A = new float[num_samples];
        float* B = new float[num_samples];
        float* C = new float[num_samples];
        boost::timer timer;
        for (int i = 0; i < num_samples; i++) {
            A[i] = 0.f;
            B[i] = 1.f;
            C[i] = 0;
        }
        double result = timer.elapsed();
        std::cout << "rewrite of " << (3*num_samples*sizeof(float)/(1024*1024)) << " Mb takes " << result << std::endl;
    
        timer.restart();
        for (int i = 0; i < num_samples; i++) {
            C[i] += A[i] + B[i];
        }
        result = timer.elapsed();
        std::cout << "without branch: " << result << std::endl;
    
        timer.restart();
        for (int i = 0; i < num_samples; i++) {
            if (i == -1)
                break;
            if (i == -2)
                break;
            if (i == -3)
                break;
            if (i == -4)
                break;
            if (i == -5)
                break;
            if (i == -6)
                break;
            if (i == -7)
                break;
            if (i == -8)
                break;
            if (i == -9)
                break;
            C[i] += A[i] + B[i];
        }
        result = timer.elapsed();
        std::cout << "with branch: " << result  << std::endl;
    }


    На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.
    Re[26]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 15.01.14 21:50
    Оценка:
    Здравствуйте, Lazin, Вы писали:

    L>На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.


    Хотя возможно, причина в том, что оптимизатор сводит это все к одному branch-у, я не смотрел disassembly.
    Re[26]: асинхронная сериализация
    От: Evgeny.Panasyuk Россия  
    Дата: 15.01.14 23:03
    Оценка:
    Здравствуйте, Lazin, Вы писали:

    L>>>Мало того, switch для кодов ошибок (когда коды ошибок выбраны правильно), компилится в switch table, который работает намного быстрее чем throw catch.

    EP>>switch table будет медленнее оригинальных goto. Я вообще не вижу смысл в переводе безусловного goto на switch в контексте обсуждения производительности
    L>Я не предлагаю заменять goto на switch. Просто сравниваю два способа обработки ошибок — throw catch и код ошибки + switch для последующей обработки.

    В реальности есть не один код ошибки, а много — и вместо switch нужен goto.

    L>На моей машине, оба цикла отрабатывают за одно и то же время, по причине того, что branch predictor успешно предсказывает все ветвления в коде, по причине того, что i никогда не принимает отрицательные значения.


    Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat".
    Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор.
    И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
    Re[24]: асинхронная сериализация
    От: andrew.f  
    Дата: 16.01.14 01:39
    Оценка:
    Здравствуйте, niXman, Вы писали:

    X>Здравствуйте, Evgeny.Panasyuk, Вы писали:


    AF>>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).

    X>что за бред?!
    X>с каких таких пор эта сишная всезаглатывающая кишка стала быстрее плюсовых потоков?!
    X>
    
    X>#include <cstdio>
    X>#include <iostream>
    X>#include <chrono>
    
    X>enum io_type { test_printf, test_stdio };
    
    X>template<io_type>
    X>struct test;
    
    X>template<>
    X>struct test<test_printf> {
    X>    template<typename T>
    X>    static std::chrono::milliseconds
    X>    run(const T *arr, std::size_t size) {
    X>        auto start = std::chrono::system_clock::now();
    X>        for ( ; size; --size ) {
    X>            printf("%d", *arr++);
    X>        }
    X>        return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
    X>    }
    X>};
    
    X>template<>
    X>struct test<test_stdio> {
    X>    template<typename T>
    X>    static std::chrono::milliseconds
    X>    run(const T *arr, std::size_t size) {
    X>        auto start = std::chrono::system_clock::now();
    X>        for ( ; size; --size ) {
    X>            std::cout << *arr++;
    X>        }
    X>        return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()-start);
    X>    }
    X>};
    
    X>int main() {
    X>    enum { size = 1024*1024*4 };
    X>    int *ia = new int[size];
    X>    auto t1 = test<test_printf>::run(ia, size);
    X>    std::cerr << "printf time =" << t1.count() << std::endl;
    X>    auto t2 = test<test_stdio >::run(ia, size);
    X>    std::cerr << "stdio  time =" << t2.count() << std::endl;
    X>}
    
    X>

    X>запускаем:
    X>

    X>./iospeed 1>/dev/null

    X>получаем:
    X>

    X>printf time =323
    X>stdio time =183


    X>да и как вообще может быть такое, чтоб printf() который вообще ничего не знает о типе, выполнялся быстрее типизированного кода?


    Это такой забавный троллинг? Или Вы серьезно не умеете оптимизировать под разные типы системных вызовов?
    Вот, я ни в коем случае не хаю C++ — я на нем реально пишу очень и очень много. Но подходы к написанию C-кода и C++-кода в реалиях очень сильно разные, в отличии от тестовых задач, написанных в лоб в одном и том же стиле для обоих языков.

    В случае printf vs std::cout разница точно такая же между select vs poll (epoll/kevent более продвинутые варианты, и они в данном случае для сравнения не годятся). Либо тоже самое, как делают некоторые студенты считаться данные из файла функцией read не блоками, а по-байтно склеивая все это в единый массив.

    Конечно, в предложенном варианте std::cout будет быстрее.
    Но в реальном коде вызовы выглядят не:

    for (i = 0; i < n; i++) std::cout << a[i];


    A вот так:
    std::cout << "Привет " << name << "! Как у тебя дела? Что делал " << day << "? Какие планы на " << next_day ....


    Код на C:
    printf("Привет %s! Как у тебя дела? Что делал %s? Какие планы на %s? " .... "", name, day, next_day ...);


    И в этом случае printf быстрее, что может стать "узким горлышком" для std::cout, когда их очень много. Правда теряешь на compile-time проверках типов (хотя современные компиляторы умеют проверять содержимое формат-строки), и опять же это не такая сильная проблема — код отлаживается один раз, запускается множество раз.

    Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов.
    Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.
    Re[25]: асинхронная сериализация
    От: niXman Ниоткуда https://github.com/niXman
    Дата: 16.01.14 01:49
    Оценка:
    Здравствуйте, andrew.f, Вы писали:

    AF>Это такой забавный троллинг?

    это ты так прикидываешься?

    AF>Или Вы серьезно не умеете оптимизировать под разные типы системных вызовов?

    где в моем примере использование системных вызовов? или ты таки считаешь, что printf() это системный вызов?

    AF>И в этом случае printf быстрее, что может стать "узким горлышком" для std::cout, когда их очень много.

    снова прикидываешься?

    AF>Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов.

    AF>Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.
    ни о чем
    пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
    Re[24]: асинхронная сериализация
    От: andrew.f  
    Дата: 16.01.14 01:54
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    EP>Здравствуйте, smeeld, Вы писали:


    S>>И это компилируется в компактный простой ассемблерный листинг с jmp/jne/je.. скачками в пределах файла.


    EP>Этот вариант даже медленнее чем вариант с goto.


    Смотря что ты оптимизировал. Это лишь вариант ручного инлайнига сильно разветвленного кода, в отличии от типичной мешанины, когда когда обработка ошибок в одном месте с основным потоком кода.

    В реалиях C++ код выглядит примерно также, у него точно также после каждого вызова есть проверка результата выполнения, и в случае ошибки — возврат на уровень выше вызовом throw. Тот самый прямой happy-path.

    S>>В случае же использования С++ try/catch сплошные call XXXXXXX

    S>> в дебри либ и фреймворков, которые вызываются по ходу выполнения потока, постоянно прерывая его.

    EP>Подробнее.
    Re[23]: асинхронная сериализация
    От: andrew.f  
    Дата: 16.01.14 02:25
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    EP>Здравствуйте, andrew.f, Вы писали:


    EP>>>Так мы же про общий случай, сортировка — это просто иллюстрация.

    AF>>qsort какой то неудачный пример — функция стандартная, и ей "сто лет в обед",

    EP>Да какая разница? Вот например GLib:

    EP>
    EP>gint                (*GCompareFunc)                     (gconstpointer a,
    EP>                                                         gconstpointer b);
    EP>void                g_array_sort                        (GArray *array,
    EP>                                                         GCompareFunc compare_func);
    EP>


    AF>>и есть масса других вариантов библиотек сортировки.


    EP>Покажи хотя бы пример интерфейса сортировки на C, которая будет принимать любой предикат, любой тип последовательности, с любыми элементами.


    Ты же сам прекрасно знаешь, что в этом случае код на макросах пишут.
    Альтернативный вариант — в качестве объекта абстрации выбирается функция, но все объявления функций делаются через static inline.
    Функция предикат, тоже пишется со static inline. И о чудо — при передаче в качестве параметра адреса static inline функции в другую static inline функцию — все это замечательно сворачиваeтся без лишних call XXXX.
    Можешь сам попробовать — написать простой пример с подобными вызовами и посмотреть ассемблеровскую портянку, пропустив через gcc -S ....

    Сорри, но из готовых алгоритмов, что у меня есть — код проприетарный, то есть закрытый.
    Поищу открытые проекты с подобными наработками — поделюсь.

    AF>>В контр-пример можно привести, что printf в большинстве случаев работает быстрее std::cout (можно глянуть на тесты boost::format — там это замечательно видно).


    EP>Пример не равноценный — std::cout медленный потому что много чего делает, и имеет customization points.

    EP>А qsort медленный — потому что интерфейс такой

    Вот в том то и дело, что сравниваются абсолютно неадекватные вещи, и при этом говорится один язык лучше второго, потому что вот в этом конкретном случае реализовано лучше.
    Идеального варианта, подходящего под все случае жизни никто до сих пор не придумал.

    EP>Покажи интерфейс сортировки, или например rbtree.


    Я про это выше написал. Поищу вариант открытой библиотеки — скину.

    AF>>И опять же под линуксом Firefox (всеми горячо любимым gcc) официально собирается с -Os, и он правда в такой варианте сборки работает быстрее, чем с -O2/-O3.


    EP>Есть тесты?


    Тесты на mozilla.org.

    Самый простой вариант — качаем официальную сборку Firefox for Linux с mozilla.org и набираем в адресной строке about:buildconfig. Смотрим параметры сборки. Для 64-битных систем до сих пор 32 битные сборки, по банальной причине — памяти кушает больше, а на производительности никак не сказывается.
    В дистрибутивах тоже самое — Firefox собран с -Os и без поддержки exceptions и rtti, но зато не (Chrome, LLVM тоже без исключений и rtti собираются, если что).
    Например, у меня Debian:

    gcc gcc version 4.7.2 (Debian 4.7.2-5) -D_FORTIFY_SOURCE=2 -Wall -Wpointer-arith -Wdeclaration-after-statement -Werror=return-type -Wtype-limits -Wempty-body -Wsign-compare -Wno-unused -Wcast-align -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -std=gnu99 -fgnu89-inline -fno-strict-aliasing -ffunction-sections -fdata-sections -fno-math-errno -pthread -pipe -DNDEBUG -DTRIMMED -g -Os -freorder-blocks -fomit-frame-pointer
    g++ gcc version 4.7.2 (Debian 4.7.2-5) -D_FORTIFY_SOURCE=2 -Wall -Wpointer-arith -Woverloaded-virtual -Werror=return-type -Wtype-limits -Wempty-body -Wsign-compare -Wno-invalid-offsetof -Wcast-align -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fno-exceptions -fno-strict-aliasing -fno-rtti -ffunction-sections -fdata-sections -fno-exceptions -fno-math-errno -std=gnu++0x -pthread -pipe -DNDEBUG -DTRIMMED -g -Os -freorder-blocks -fomit-frame-pointer -D_FORTIFY_SOURCE=2
    Re[25]: асинхронная сериализация
    От: smeeld  
    Дата: 16.01.14 07:41
    Оценка:
    Здравствуйте, andrew.f, Вы писали:

    AF>Даже boost::asio замечательно написанный, но сильно перегруженный постоянными перекладываниями из одного контейнера в другой дескрипторов сокетов.

    AF>Даже в его случае, его можно выкинуть, написать все на чистом API и получить лишние 10-20% производительности в реальном приложение, а не на "сферическом коне в вакууме", когда тесты гоняются на внутреннем интерфейсе машине, или в лучшем случае в локальной сети между двумя серверами.

    boost::asio и на тестах через внутренний интерфейс сливает релизации на чистом C API.
    Re[27]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 16.01.14 08:26
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    EP>Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat".

    EP>Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор.
    EP>И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
    Посмотрел дизасм. Оказывается оптимизатор убирает все мои if-ы и векторизует цикл. Если ограничить размер массива и сделать внешний цикл, а в if-е сравнивать что-нибудь еще, что оптимизатор не может выбросить, векторизация не выполняется вообще. Если добавить volatile — все становится очень плохо, так как компилятор генерирует кучу загрузок и не может в векторизацию, независимо от налиция или отсутствия ветвлений.
    Re[27]: асинхронная сериализация
    От: Lazin Россия http://evgeny-lazin.blogspot.com
    Дата: 16.01.14 08:31
    Оценка:
    Здравствуйте, Evgeny.Panasyuk, Вы писали:

    EP>Во-первых у тебя memory-bounded вычисления — большую часть времени будет занимать пересылка данных в/из памяти. Чтобы обойти этот эффект, попробуй сделать num_samples = 64k (чтобы попасть хотя бы в L3), и добавить внешний цикл "repeat".

    EP>Во-вторых попробуй добавить volatile на i, чтобы отключить оптимизатор.
    EP>И в-третьих тут один цикл с несколькими проверками — тут branch predictor должен неплохо сработать. В реальном же коде, эти проверки будут размазаны тонким слоем по всему call graph, и не всегда будут вызываться в циклах. Плюс они забивают predictor table.
    Если выключить оптицизацию, код для циклов генерируется одинаковый, только во втором есть branch. Время работы тоже примерно одинаковое, первый цикл, без branch-а, может быть на 5-10% быстрее, но не больше. Это при том, что тело цикла это две загрузки и одно сохранение, в общем — все очень быстро и влезает в L2 кэш.
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.