Re[8]: замеры) нас не догонят)
От: B0FEE664  
Дата: 31.07.23 16:33
Оценка:
Здравствуйте, pilgrim_, Вы писали:

_>Тут s_count=80'000.

Тут s_count=8'000'000;
https://godbolt.org/z/1vhEzfE4c
Добавил "многопоточность", а она ни на что не влияет . Что я делаю не так?:

  Скрытый текст
    // par
    {
        v_dest.clear();
        const some_time v_start = std::chrono::system_clock::now();
        std::copy_if(v_src.begin(), v_src.end(),
                     std::back_inserter(v_dest),
                     [](int x) { return !(x & 0b1); });
        std::transform(std::execution::par_unseq, v_dest.begin(), v_dest.end(), v_dest.begin(), [](int n){ return n/2;});

        const some_time v_stop = std::chrono::system_clock::now();
        std::cout << "par: "
        << std::chrono::duration_cast<std::chrono::microseconds>(v_stop - v_start)
        << '\n';
        //std::cout << v_dest << '\n';
    }

    // with mutex
    {
        v_dest.clear();
        const some_time v_start = std::chrono::system_clock::now();

        std::mutex mtx;
        std::for_each(std::execution::par_unseq, v_src.begin(), v_src.end(),
                      [&mtx, &v_dest](int n)
                      {
                        if ( !(n & 0b1) )
                        {
                          n /= 2;
                          std::lock_guard<std::mutex> lock(mtx);
                          v_dest.push_back(n);
                        }  
                      }
                     );

        const some_time v_stop = std::chrono::system_clock::now();
        std::cout << "par with mutex: "
        << std::chrono::duration_cast<std::chrono::microseconds>(v_stop - v_start)
        << '\n';
        //std::cout << v_dest << '\n';
    }

mcq: 6942us
mcq: 7745us
for: 8207us
for: 8221us
algo: 8282us
algo: 9553us
for div: 24985us
for div: 24839us
algo div: 34929us
algo div: 36110us
par: 6198us
check 1: 5840us
par with mutex: 5927us
check 2: 4767us

И каждый день — без права на ошибку...
Re[9]: замеры) нас не догонят)
От: sergii.p  
Дата: 01.08.23 06:55
Оценка:
Здравствуйте, B0FEE664, Вы писали:

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


BFE>Добавил "многопоточность", а она ни на что не влияет . Что я делаю не так?:


#define PARALLEL
#include <execution>


видимо, для MSVC и clang тоже надо что-то магическое дефайнить.
Re[2]: Может я чего-то не понимаю....
От: YuriV  
Дата: 01.08.23 19:28
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну и в-третьих, а чем, в данном случае, хуже "лобовое" решение с использованием обычного цикла?


R>
R>  for(int x : numbers_in) if (!(x % 2)) numbers_out.push_back(x / 2);
R>


R>А, ну и по сути вопроса — "почему так сложно?" — а ХЗ, почему. Мозги у людей как-то странно сложены.


Следует добавить, что третье решение — правильное, потому что решает задачу при максимальной простоте и читаемости, а не демонстрирует возможности с++ биб-ки. Это решение понятно и смузеру, и старпёру с базовыми знаниями по программированию. Остальные решение это оверинжениринг.
Re[9]: замеры) нас не догонят)
От: pilgrim_ Россия  
Дата: 01.08.23 23:16
Оценка: 8 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE>Тут s_count=8'000'000;

BFE>https://godbolt.org/z/1vhEzfE4c
BFE>Добавил "многопоточность", а она ни на что не влияет . Что я делаю не так?:

А почему ты ожидал ускорения от использования "многопоточки"? В частности для par (copy_if/transform+par_unseq), код копирования/фильтрации однопоточный, что фильтрация ,что трансформация примитивны, и если их убрать (копировать всё, возвращать оригинальное значение) на скорость это почти не влияет, но в твоём случае (для par) пробег по памяти в 1.5 раза больше. Хз что тут меряем, да ещё и на godbold (к слову доступно всего 2 потока, в FAQ написано про 3).

==
А твой код был однопоточным, поэтому быстрым
По умолчанию алгоритмы execution используют дефолтный, последовательный backend (по asm листингу видно), надо добавить т.н. parallel backend, пока только один доступный — Intel TBB (-ltbb, но на godbold надо выбрать из предустановленных).

Убрал ranges, т.к. завёлся только gcc 11.2 с c++17 (как в примере для TBB на godbold), trunk правда тоже работает. Свежие версии gcc требуют отладочные либы TBB, которых нет в наличии.

https://godbolt.org/z/a15v7zWo3

mcq: 8050
mcq: 7462
for: 10950
for: 10982
for div: 21935
for div: 18560
par: 7672
check 1: 6953
par with mutex: 203897
check 2: 8464

Re[10]: замеры) нас не догонят)
От: pilgrim_ Россия  
Дата: 01.08.23 23:27
Оценка:
Здравствуйте, sergii.p, Вы писали:


SP>
SP>#define PARALLEL
SP>#include <execution>
SP>


Не, для gcc надо либу явно линковать (-ltbb, какой-то хак gcc — по используемой либе определяет соотв. макрос, анализируемый уже в коде на предмет доступности TBB), на godbold выбирается из предустановленных в GUI. В транке gcc видно начало поддержки OpenMP как ещё одного бэкенда, но не доделано, видать в бранче каком развивается.
Re[2]: modified_copy_if (my custom algo)
От: Sm0ke Россия ksi
Дата: 02.08.23 14:23
Оценка:
Произвёл модификацию кода

Добавил свой алгоритм, сделанный на основании ranges::copy_if
( modified_copy_if: В предикат передаётся исходное значение, а в результат пишется проекция )
( В стандартном copy_if: проекция передаётся в предикат, но в результат пишет исходное )

Добавил custom div

Исходник: https://godbolt.org/z/K5hKP5fnv

Замеры на ноуте: (ms vc community 2022)

mcq: 2557us
mcq: 2257us
for loop: 2252us
for loop: 2627us
algo: 2456us (filter_view + transform)
algo: 2471us (filter_view + transform)
algo div: 21614us (transform div() + filter rem zero + transform quot)
algo div: 18330us (transform div() + filter rem zero + transform quot)
algo div custom: 13423us (transform my_div() + filter rem zero + transform quot)
algo div custom: 12781us (transform my_div() + filter rem zero + transform quot)
for div: 10058us
for div: 9904us
for div custom: 9812us
for div custom: 10002us
algo modified_copy_if: 1455us (custom algo)
algo modified_copy_if: 1453us (custom algo)


Замеры в готболте: (gcc)

mcq: 3334us
mcq: 2005us
for loop: 2518us
for loop: 2351us
algo: 1681us (filter_view + transform)
algo: 1671us (filter_view + transform)
algo div: 12756us (transform div() + filter rem zero + transform quot)
algo div: 13324us (transform div() + filter rem zero + transform quot)
algo div custom: 1831us (transform my_div() + filter rem zero + transform quot)
algo div custom: 2302us (transform my_div() + filter rem zero + transform quot)
for div: 9182us
for div: 8855us
for div custom: 1635us
for div custom: 1606us
algo modified_copy_if: 1570us (custom algo)
algo modified_copy_if: 1517us (custom algo)
Отредактировано 02.08.2023 14:26 Sm0ke . Предыдущая версия .
Re[3]: modified_copy_if (my custom algo)
От: rg45 СССР  
Дата: 02.08.23 14:40
Оценка: :))
Здравствуйте, Sm0ke, Вы писали:

S>Добавил свой алгоритм, сделанный на основании ranges::copy_if

S>( modified_copy_if: В предикат передаётся исходное значение, а в результат пишется проекция )
S>( В стандартном copy_if: проекция передаётся в предикат, но в результат пишет исходное )

S>Добавил custom div


S>Исходник: https://godbolt.org/z/K5hKP5fnv


Похоже, теперь я чего-то не понимаю. Я с самого начала думал, что здесь проходят соревнования, у кого короче.
--
Re[4]: modified_copy_if (my custom algo)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 02.08.23 14:42
Оценка: -1
Здравствуйте, rg45, Вы писали:

S>>Исходник: https://godbolt.org/z/K5hKP5fnv


R>Похоже, теперь я чего-то не понимаю. Я с самого начала думал, что здесь проходят соревнования, у кого короче.


Все соревнования всегда сводятся к тому, у кого длиннее
Маньяк Робокряк колесит по городу
Re[10]: замеры) нас не догонят)
От: B0FEE664  
Дата: 03.08.23 09:32
Оценка:
Здравствуйте, pilgrim_, Вы писали:

BFE>>Добавил "многопоточность", а она ни на что не влияет . Что я делаю не так?:


_>А почему ты ожидал ускорения от использования "многопоточки"?

Я ничего не читал про возможности параллельного выполнения, кроме описания на cppreference.com из которого мало что следует. Чисто теоретически все операции над элементами в std::transform могут быть выполнены параллельно и за одно и тоже время. Т.е. вот этот вызов:
std::transform(std::execution::par_unseq, v_dest.begin(), v_dest.end(), v_dest.begin(), [](int n){ return n/2;});

в идеале может быть выполнен за за время равное одному вызову [](int n){ return n/2;}.
Думаю, что на практике такая операция будет выполнятся над кусками.
Понятно, что многое зависит от аппаратной части и архитектуры процессора, за которыми я не слежу. Просто хотелось увидеть, как оно сейчас на практике. Похоже, что никак.
И каждый день — без права на ошибку...
Re: Может я чего-то не понимаю....
От: vdimas Россия  
Дата: 04.08.23 16:23
Оценка: 1 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE>Допустим, есть вектор с целыми числами, и требуется поделить все его числа на два. Те, которые не делятся, нужно просто выкинуть.


Если "в одну строчку", то удобно использовать ренжи.
Заодно позволит не создавать лишние буфера в процессе многостадийной обработки:
#include <iostream>
#include <ranges>
#include <boost/phoenix.hpp>

int main() {
    using namespace std;
    using views::filter;
    using views::transform;
    using boost::phoenix::placeholders::_1;

    //===========================================================

    auto const v1 = { 1, 2, 3, 4, 5, 3 };

    auto rng = v1 | filter(_1 % 2 == 0) | transform(_1 / 2);
    auto const vec = rng | ranges::to<vector>();

    //===========================================================

    ostream_iterator<int> const out { cout, " " };

    ranges::copy(v1, out), cout << std::endl;
    ranges::copy(rng, out), cout << std::endl;
    ranges::copy(vec, out), cout << std::endl;

    return 0;
}
Re[2]: Может я чего-то не понимаю....
От: Sm0ke Россия ksi
Дата: 05.08.23 13:27
Оценка:
Здравствуйте, vdimas, Вы писали:

V>
V>#include <iostream>
V>#include <ranges>
V>#include <boost/phoenix.hpp>

V>int main() {
V>    using namespace std;
V>    using views::filter;
V>    using views::transform;
V>    using boost::phoenix::placeholders::_1;

V>    //===========================================================

V>    auto const v1 = { 1, 2, 3, 4, 5, 3 };

V>    auto rng = v1 | filter(_1 % 2 == 0) | transform(_1 / 2);
V>    auto const vec = rng | ranges::to<vector>();

V>    //===========================================================

V>    ostream_iterator<int> const out { cout, " " };

V>    ranges::copy(v1, out), cout << std::endl;
V>    ranges::copy(rng, out), cout << std::endl;
V>    ranges::copy(vec, out), cout << std::endl;

V>    return 0;
V>}
V>


чтож, неплохо!
Посмотрим детальнее

Вы забыли включить #include <vector> ( придираюсь к мелочам :Р )
Вроде рабочий вариант: https://godbolt.org/z/W17nbz8qx

ranges::to — из 23-го стандарта
Согласно cppref поддержка есть в:
* clang 17 (которого пока нет на готболт)
* msvc 19.34 (и он там есть)

Вы используете using-и, чтобы укоротить основную строчку, однако они сами добавляют отдельные строчки (хз честно это или нет).

V>Если "в одну строчку", то удобно использовать ренжи.

Там у вас две строки, а если в одну, то for всё равно короче)

vec = v1 | filter(_1 % 2 == 0) | transform(_1 / 2) | ranges::to<vector>();
for( auto it : v1 ) if( it % 2 == 0 ) { vec.push_back(it / 2); }


Однако для повторного использования забиндить цепочку изменений в переменную rng — конечно удобнее.
Отредактировано 05.08.2023 13:32 Sm0ke . Предыдущая версия .
Re[3]: Может я чего-то не понимаю....
От: vdimas Россия  
Дата: 06.08.23 19:17
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Вы используете using-и, чтобы укоротить основную строчку, однако они сами добавляют отдельные строчки (хз честно это или нет).


Однократно на некий файл, допустим, с несколькими функциями манипуляций данными.


V>>Если "в одну строчку", то удобно использовать ренжи.

S>Там у вас две строки

В одну, в одну ))
Копирование в вектор было необязательным, поэтому вынес в отдельную строку, но можно было приписать последней операцией в строке.
Т.е. суть в том, что на каждом этапе стоит оперировать концепцией ренжей и только ими, сохранять в переменную можно тоже просто ренж — специально это продемонстрировал, показав ranges::copy(rng, out) без сохранения в промежуточный контейнер.

А уже конечный ренж копировать или в хранилище, или передать в output_iterator, или еще куда-нить для последующей обработки.


S>а если в одну, то for всё равно короче)


На некоторых задачах да, но стремление к декларативности операций порой рулит, особенно если задача не диктует битовыжимание.

Опять же, ценность порой представляют не готовые трансформаторы данных, а вся инфрастуктура вокруг них, где ты относительно дешево можешь описывать свои прикладные повторно-используемые трансформаторы. Собсно, ради такого подхода к решению задач весь огород и нагородили.


S>ranges::to — из 23-го стандарта


Есть такое.
Можно юзать ренжи из буста (первоначальный вариант был бустовый), бо в стандарт еще не все плюшки переехали.
В смысле ренжей, атомик и потоков (пусть даже в версии 4) и прочих emplace, optional и т.д. Boost, конечно, оказал чудовищное влияние на язык и его стандарты.
Красавцы, чо! ))


S>Однако для повторного использования забиндить цепочку изменений в переменную rng — конечно удобнее.


Концепт ренжей прямо в стандарте описан как тип, который имеет операции begin(rng) и end(rng), при том что дефолтная реализация пытается взять rgn.begin() и rng.end(), т.е. подхватывает имеющиеся стандартные контейнеры и пользовательские типы, оформленные в STL-like манере, экономя на стороне прикладного кода избыточный однообразный код, где ты оперировал парой итераторов явно, замусоривая исходник. Казалось бы — на поверности... Но когда-то сделано не было в STL из-за отсутствия полноценной поддержки частичной специализации шаблонов... Но тут, как грится, лучше поздно, чем никогда. ))
Отредактировано 06.08.2023 19:21 vdimas . Предыдущая версия . Еще …
Отредактировано 06.08.2023 19:19 vdimas . Предыдущая версия .
Отредактировано 06.08.2023 19:18 vdimas . Предыдущая версия .
Отредактировано 06.08.2023 19:17 vdimas . Предыдущая версия .
Re[8]: замеры)
От: Skorodum Россия  
Дата: 31.08.23 08:41
Оценка:
Здравствуйте, ArtDenis, Вы писали:

AD>Как тут уже сказали, перед замером надо "прогревать" процессор. ОС выставит для него максимальную частоту и переключит твой поток на мощное ядро. Затем, один и тот же тест надо прогонять много раз, каждый раз считать время, а затем брать например, медианное значение.

Если измеряется быстродействие алгоритма в идеальных условиях, то надо брать лучшее значение, а не медианное.
(Идею не моя, а Александреску, он говорл о замерах алгоритмов в ФБ.)
Re[9]: замеры)
От: ArtDenis Россия  
Дата: 31.08.23 13:37
Оценка:
Здравствуйте, Skorodum, Вы писали:

S>Если измеряется быстродействие алгоритма в идеальных условиях, то надо брать лучшее значение, а не медианное.

S>(Идею не моя, а Александреску, он говорл о замерах алгоритмов в ФБ.)

Я просто исхожу из того, что средства измерения тоже имеют погрешности и их тоже надо учитывать. А лучшее время может отказаться совсем некорректным. Но вопрос конечно спорный.
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[10]: замеры)
От: Skorodum Россия  
Дата: 01.09.23 07:12
Оценка:
Здравствуйте, ArtDenis, Вы писали:

AD>Я просто исхожу из того, что средства измерения тоже имеют погрешности и их тоже надо учитывать. А лучшее время может отказаться совсем некорректным. Но вопрос конечно спорный.

В этом и суть: погрешность измерений только ухудшает результат. Самаое маленькое значение — самое близкое к достижимому в идеальных условиях.
Другое дело если хочется сравнить производительность не в рафинированных условиях.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.