Pull to refresh

Comments 76

они слишком абстрактны и нужны только для очень абстрактного кода

скорее «они очень абстрактны потому, что нужны для любого не очень абстрактного кода».

Моя личная претензия к с++20 (даже не к ranges как таковым) — отсутствие генераторов, с помощью которых уже можно было бы выразить и ranges, и корутины.
Чем генераторы от корутин отличаются???
корутина — частный случай генератора. И даже если за счет плюсовой шаблонной магии любой генератор можно выразить корутиной, приделать к ним range-like интерфейс всё равно непросто.
Вообще у нормальных людей короутина — это частный случай генератора.
Вы наверно хотели сказать что генератор это частный случай корутин.
Antervis
Генераторы можно делать с помощью корутин, а можно делать и без корутин. А вот корутины с помощью генератора делать это я слабо себе представляю. И да в С++20 корутины же будут. В vs19 их можно включить кстати. Также корутины можно эмулировать по средством потоков или файберов.
Вот например
int main()
{
  auto gen = generator(std::tuple<int, int, int>)
  {
    for (int z = 1; ; ++z)
      for (int x = 1; x <= z; ++x)
	for (int y = x; y <= z; ++y)
	  if (x*x + y*y == z*z)
	    co_yield(std::make_tuple(x, y, z));
  };

  for (int i = 0; i < 100 && (bool)gen; i++)
  {
    auto val = gen.next();
    printf("(%i,%i,%i)\n", std::get<0>(val), std::get<1>(val), std::get<2>(val));
  }

  return 0;
}

cpp.sh/8dy27
Вы наверно хотели сказать что генератор это частный случай корутин.
Ммм. Не стоит комментировать, едучи в автобусе, даже если там больше нечем особо заняться…

Выразался очень смутно, но имел в виду то же, что и вы. Собственно как пишет Wikipedia: Generators, also known as semicoroutines, are a special case of (and weaker than) coroutines, in that they always yield control back to the caller (when passing a value back), rather than specifying a coroutine to jump to

Но это у нормальных людей так. У Antervis генераторы явно не такие, как у нормальных людей, раз через них и ranges и корутины выражаются… потому и возник вопрос: а что он, собственно, под словом «геренатор» подразумевает?

Конечно та же Wikipedia рассказывает как корутины эмулировать на генераторах… но тут надо понимать, что эмулировать можно вообще всё на почти всём (главное вначале машину Тьюринга построить, а дальше задача сводится к предыдущей), вопрос же не в этом.
Вы наверно хотели сказать что генератор это частный случай корутин.
Antervis
Генераторы можно делать с помощью корутин, а можно делать и без корутин
генератор в частном случае может являться zero-cost абстракцией. Корутина таковой (в нынешней инкарнации) не является, и поэтому делать zero-cost генераторы через корутины не получится. Из-за этого нет смысла объединять интерфейсы корутин и ranges, чего, собственно, мне бы и хотелось. «Генератором» я и назвал такой объединенный интерфейс, который не обязан удовлетворять требованиям корутины, но позволяет писать код через yield/await.
И да в С++20 корутины же будут. В vs19 их можно включить кстати
и в gcc есть ветка с корутинами.
генератор в частном случае может являться zero-cost абстракцией.
корутина в частном случае — тоже может. В общем — ни там, ни там нет.

На самом деле с короутинами история STL повторяется. Если посмотреть на существующую реализацию, то память уже не выделяется, но какие-то «следы» в коде остаются… думаю со времением и их изведут.

Корутина таковой (в нынешней инкарнации) не является, и поэтому делать zero-cost генераторы через корутины не получится.
Почему нет? В языке это ничто не запрещает, а компиляторы, я думаю, подтянутся…

«Генератором» я и назвал такой объединенный интерфейс, который не обязан удовлетворять требованиям корутины, но позволяет писать код через yield/await.
Осталось понять чем это от существующего предложения отличается…

и в gcc есть ветка с корутинами.
А в clang не нужна ветка — там это опция компилятора.
корутина в частном случае — тоже может.

В языке это ничто не запрещает, а компиляторы, я думаю, подтянутся…
теоретически язык вообще мало что запрещает, пока что только с std::unordered_* облом. А пока практической возможности нет, смысла тянуть это в язык тоже нет.
Если посмотреть на существующую реализацию, то память уже не выделяется
строчка 5 асма — call operator new.
А пока практической возможности нет, смысла тянуть это в язык тоже нет.
C++ так не работает. STL стал «zero-cost абстракцией» примерно лет через десять после того, как его в стандарт включили. Важна теоретическая возможность, а не то, что делают реальные компиляторы «здесь и сейчас». Если вам этот подход не нравится — вам нужно работать с каким-нибудь другим языком, C++ всегда так был устроен…

Если посмотреть на существующую реализацию, то память уже не выделяется
строчка 5 асма — call operator new.
Смтрю в книгу — вижу фигу. А ничего что эта строчка при работе программы (то есть функии bar в вашем случае) никогда не вызывается? От выделения памяти все компиляторы уже давно избавились, а вот оптимизации… да, «провисают» пока. Ну ничего — лет через 10 поправят.
UFO just landed and posted this here
По крайней мере, в значительной части случаев это всё ведёт к более краткому, выразительному и понятному коду.
К сожалению подавляющее большинство программистов не умеют в математику и для них функциональный подход выглядит сложнее, чем даже все эти короутины и ranges. Смиритесь.

Я тоже не понимаю почему — это просто такой факт, который я вижу на практике.
Ну или можно сравнить с весьма компактным вариантом, который не пытается эмулировать контейнеры там, где они не нужны:
Код
template <typename T>
double integrate(T generator) {
    double acc = 0;
    double t;
    while(generator(t)) {
        acc += dt_fixed * f(t);
    }
    acc -= 0.5 * dt_fixed * f(0);
    acc -= 0.5 * dt_fixed * f(tau);
    return acc;
}

long long int i = 0;
double res = integrate( [&i, n_nodes](double& t)
{
    t = static_cast<double>(i);
    ++i;
    return i < n_nodes;
} );


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

Тоже хороший вариант, добавил его в репозиторий в виде v6.cpp. Выполняется около 4.5 с при компиляции и g++, и clang++.
Меня в нём смущает висячая long long i, чтоб её убрать, надо делать генератор объектом класса (который хранит i), писать конструкторы и в итоге получится не сильно короче, чем с итераторами. Хотя этот вариант в целом попроще.

Чтоб убрать висячий каунтер, надо всего то обернуть код в {}, в крайнем случае можно вынести в отдельную функцию. Без обид, но опять создаете проблемы на пустом месте.

Мне кажется, не на пустом. Представьте, вам справку писать придётся по этому коду. И вы напишете "Вот так это нужно использовать… И не забываем оборачивать в {}!"? Не самый это изящный вариант. И функции нечистые я не люблю. А с итератором сразу понятно, что внутри грязная функция спряталась.

Это можно использовать как угодно, главное знать что должен из себя представлять генератор (именно об этом в справке и напишут). Если так изящнее — ОК, пусть будет изящнее. Просто нужно учитывать, что часто впоследствии код читают другие люди, и для них важнее быстро разобраться, чем всячески избегать нечистых функций.
А ключик -march=native в g++ влияет на производительность?
Скорее на воспроизводимость. Он означает «включи те фичи, которые вот тут, на этом конкретном процессоре, имеют смысл».

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

Потому что фиг его знает что именно у вас там в CPU есть — это ж не только от модели CPU может зависеть, некоторые фичи могут и от версии операционки или BIOS зависеть!

Как ни странно, но для моего Xeon-а — нет, не влияет. Только для clang++ v1 стал на 0.5 с быстрее с ним, для остальных всё осталось в пределах +- 0.1-0.2 с.

Будем интегрировать методом трапеций вот такую функцию:
А какой в этом смысл, если эта функция прекрасно интегрируется аналитически? На что у меня потребовалось меньше времени, чем на прочтение следующего абзаца.

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

Для заранее известного ответа можно использовать системы компьютерной алгебры, которые с произвольной точностью считать умеют. И метод трапеций очевидно не так быстро сходится, по сравнению с другими — тоже не самый лучший выбор. Закономерно возникает сомнение, что и дальше по тексту у вас будет оптимальное решение.

Статья не про численное интегрирование. И не про интегрирование вообще. Она даже не в хабе «Математика». Статья про новую фишку C++20.


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

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

Потому что важно не то, что умеет автор стартьи (он же не для себя статью пишет!), важно что знают и умеют читатели.

Так вот про интегралы и как их считать — рассказывается даже в школьной программе, а вот эллиптических — там уже нет.

Если ваша задача рассказать про фишки C++, а не про математику кривых — то лучше, чтобы эллиптических интегралов в статье бы не было.

И ещё больше бы выиграла, если бы код из неё можно было бы взять и использовать «как есть» в реальных задачах.
Возможно, но тут проблема курицы и яйца: пока ranges не в релизе — их мало кто использует, а когда будет набрана статистика использования — писать «вводную» статью типа обсуждаемой… уже поздновато.
Статья бы, конечно, проиграла, если бы в качестве примера был взят эллиптический интеграл.
То есть, если бы автор начал статью не с «найти интеграл функции ...» а с «посчитать длину кривой» было бы хуже?

Потому что важно не то, что умеет автор статьи (он же не для себя статью пишет!), важно что знают и умеют читатели.
Если статья о том, что все и так знают, то какой вообще смысл её читать?
Если статья о том, что все и так знают, то какой вообще смысл её читать?
Естественно статья должна включать что-то, чего люди не знают. Но все «посторонние» вещи — желательно свести к минимуму.

То есть, если бы автор начал статью не с «найти интеграл функции ...» а с «посчитать длину кривой» было бы хуже?
Да, потому что понять — откуда там вообще берутся проблемы гораздо сложнее.
Если статья о том, что все и так знают, то какой вообще смысл её читать?

Статья не об интегралах, а о ranges в C++20. Интегралы тут вообще сбоку, как пример.

Нет уж, спасибо, как-то не хочется видеть обозрение трехстрочных фич языка на примерах с постшкольным матаном (я в курсе про термин, если что, поправлять не надо).
Извлечение корня изучают ещё в школе и даже раньше, чем тригонометрию. Это операция, обратная возведению в квадрат. Что страшного в корне, я упорно не понимаю.

Математику за то и не любят, что её дают на примерах, отдалённых от реальности; и многие вещи, звучащие страшно и непонятно, на деле оказываются простыми и очевидными — если их излагать не как «вешь в себе», а применительно к реальным задачам.
Математику за то и не любят, что её дают на примерах, отдалённых от реальности
Это можно ставить в упрек статьям по математике, но никак не статьям в которых сама математика — пример использования чего-то.

З.Ы. Мое сообщение выше действительно стоило плевка в карму? Серьезно?
Я не знаю, моя карма пострадала сильно больше, если вам станет от этого легче.
Я вам поставил (а не «плюнул») минус не столько за содержание, сколько за мотивацию. Вы шли мимо, увидели, что кого-то сильно минусуют, и решили попинать его за компанию, ничем при этом не рискуя. Это низко и недостойно. Самоутверждаются за чужой счёт только те, кому утвердиться больше нечем — у вас нет ни статей, ни сколько-нибудь значимых комментариев в обозримый промежуток времени.

По содержанию. Изображая из себя знающего человека вы прокололись, назвав эллиптический интеграл «термином» — в то время как это никакой не термин, а функция. По основной теме статьи — ranges — вы также ничего сказать не смогли.

Вы никак не подтвердили свою квалификацию, чтобы позволить себе подобного рода высказывания. Я считаю, что вам и подобным вам нет места на Хабре. Ваше последующее прилюдное нытьё по поводу потери единицы кармы лишь подтвердило это.

Ну а если другим членам сообщества, минусующим мои комментарии, ваши более интересны — имеют на то полное право.

P.S. Чтобы читать о фичах языка без матана и прочей шелухи, нужно читать просто документацию.
Вы шли мимо, увидели, что кого-то сильно минусуют, и решили попинать его за компанию
Это только в вашем видении. А в моем видении я прочел статью, спустился в комментарии, прочел их, и в ответ на утверждение с которым не согласен выразил свое мнение, без хамства и брани. Собственно, для этого и существуют комментарии.
Изображая из себя знающего человека вы прокололись, назвав эллиптический интеграл «термином»
Вообще-то, под «термином» подразумевался «матан». Очень любят его поправлять, вот я и подстраховался.
прилюдное нытьё по поводу потери единицы кармы
Обидно конечно, но меня возмутила совсем не потеря кармы, а то, в ответ на какой комментарий она последовала. Я бы понял, если бы там был переход на личности, превышение градуса, введение в заблуждение, или хотя бы напряженный спор между нами. Но там было весьма нейтрально выраженная одним коротеньким комментарием точка зрения по совершенно безобидной теме. Скорее я поверю что вы просто в отместку за минуса прошлись по всем несогласным не глядя, чем в мотивацию которую вы описали выше.
Нет, «в отместку» досталось только вам. Если вам это показалось несправедливым — мне жаль, но никто не считает минусы в свою сторону справедливыми. Возможно, это добавит вам мотивации написать свою статью, накопить кармы и поставить минус мне в ответ. Возможно также, что прочитав вашу статью, я изменю своё мнение и сменю минус на плюс (хотя вряд ли вы об этом узнаете).
«Воспользуемся трудами его плодов. Код скажет всё сам за себя:»
image
Лучше расскажите как в C++20 построить график этой функции и сохранить его pdf.
for(long long i = 1; i < n_fixed - 1; ++i) {
double t = dt_fixed * static_cast(i);
acc += dt_fixed * f(t);
}
Чего-то я туплю, но почему у вас этот подсчет называется методом трапеции?
Тем не менее в примерах мы почти до самого конца "забудем" про настоящий метод трапеций и для простоты будем рассматривать его версию с постоянным шагом, держа при этом в голове то, что сетка может быть произвольной.

Меня интересовал вопрос — как сетку в интегрирующую функцию передать, а не как именно написать метод трапеций для этой функции. По сути здесь речь об удобстве написания/поддержки без потери производительности. Честный метод трапеций, с переменным шагом там есть в конце (в репозитории — файл v5.cpp).

Честно говоря я не про шаг спрашивал…
Мне всегда казалось что площать трапеции вычисляется как-то так:
s=(f(t0)+f(t1))*h/2
где t1=t0+delta_t

Это и написано в v5.cpp. Если же t_{i+1} — t_i = const (то есть не зависит от i), то из метода трапеций получается то, что написано в v1-v4. Просто формулу трапеций так можно преобразовать в случае постоянного шага. Вычислений при этом меньше, скорость — выше.

Я думаю претензия была просто в том, что не стоит метод правых прямоугольников назвывать методом трапеции.

Хотя было бы, наверное, проще так и написать, чем пытаться «эзоповым языком» на этот вариант вывести.

Метод правых/левых прямоугольников менее точен, чем метод трапеций. Я же говорил про то, что википедия зовёт формулой Котеса.

Вот только формула Котеса опирается на тот факт, что у нас трапеции имеют одинаковую ширину.

А вы проде как шаг менять собрались…
Общности в таком подходе — хоть отбавляй, а что с производительностью? С потреблением памяти? Если раньше у нас всё суммировалось на процессоре, то теперь приходится сначала заполнить участок памяти, а потом читать из него. А общение с памятью — довольно медленная вещь.


Хм, ну подождите, если ставится задача «делаем универсальную функцию интегрирования трапециями, в которую снаружи передается набор узлов», то без трат памяти не обойтись. Кто-то где-то в другом месте этот набор генерирует и так или иначе записывает его в память, чтобы нам передать. Этот генератор может быть разным по устройству, не обязательно компилируемым вместе с функцией интегрирования, возможно набор узлов вообще по почте приходит.

К слову, если ваш второй пример запустить, то (количество узлов я взял поменьше, а то у меня чего-то bad alloc вылезал) результат таков: 1.3 секунды занимает заполнение вектора и 3.8 секунды собственно вычисление интеграла, отсюда и берется «худший результат из сравниваемых». Да и вообще сравнение получается немного странным:
1) Постоянный шаг, вычисление аргумента «по месту».
2) Постоянный шаг, вычисление аргумента где-то снаружи, но с учетом времени этого вычисления (!?).
3) 4) Постоянный шаг, вычисление аргумента «по месту» (если я правильно понимаю этот код, простите мое плохое знание С++ и ленивых вычислений).
5) Переменный шаг, вычисление аргумента «по месту».
То есть везде какие-то отличия по сути, что же мы сравниваем?

Но вообще спасибо за статью, узнал новое для себя!

P.S.: Я же верно понимаю, что для варианта 5 теряется совместимость с вектором?

А почему во всех вариантах вычисление сетки должно учитываться (по времени), а во втором — нет? Так нечестно.
std::vector плох не только скоростью. Если интегрируемая функция достаточно тяжело вычисляется на каждом шаге, то общение с памятью не будет "бутылочным горлышком, конечно. Но может банально памяти не хватить под этот вектор, например.
5 вариант не потерял совместимость с вектором, просто t_i в вектор нужно положить другие (в смысле другие числа, а тип тот же; vector<double>). Но работает это не быстро (14 секунд в моих условиях, из них 9 на заполнение вектора, из которых можно 1.5 секунды сэкономить, если сделать заранее reserve).
А сравниваем мы и скорость, и удобство, ну и потребление памяти, если хотите.

А почему во всех вариантах вычисление сетки должно учитываться (по времени), а во втором — нет? Так нечестно


Потому что во всех вариантах это именно вычисление, а во втором — работа с памятью. И в зависимости от свойств системы результат получается разным. Я бы сказал так, что либо второй вариант в вашей статье лишний, либо наоборот — все остальные лишние. И, разумеется, самой идеи рассказать таким образом о ranges это никак не умаляет.

std::vector плох не только скоростью


Конкретно вектор ничем вообще не плох. Плох (или «плох») любой способ формирования набора узлов, который требует промежуточной памяти. А тут уже нет никакого выбора — либо вам без памяти не обойтись (набор получен по сети), либо нет никакого смысла хранить его в памяти (и вычисляем на каждом шаге, любым из предложенных вами способов).

5 вариант не потерял совместимость с вектором


Пардон, но если посмотреть на код в for()?
Пардон, но если посмотреть на код в for()?

for (auto t: t_nodes | ranges::v3::views::drop(1)) работает и в том случае, если t_nodes есть vector<double>. О времени для такого варианта я выше и говорил.

А можете тогда пояснить, что делает ranges::v3::views::drop(1)? А то в документации на сайте Ниблера написано что-то невнятное.

Отбрасывает первый элемент и оставляет всё остальное.
Что вот мне совсем не нравится в ranges — очень скудная документация. В примерах у Ниблера drop даже не встречается. Но есть take — это, наоборот, взять n первых элементов, отбросив всё остальное. И оба они ленивые, то есть можно работать с бесконечной последовательностью, а уж потом сделать take, например.

Вот, значит я правильно понял описание. И тогда единственный смысл этой операции — отбросить первый узел (который до цикла был взять через begin). При этом весь код до вызова integrate() может быть удален, насколько я понимаю (само собой потребуется альтернативный код для заполнения вектора). Но тогда непонятно, почему вдруг время так выросло по сравнению с вариантом 2.

Там основное время — заполнение вектора, а поскольку шаг в этом примере переменный, то функция step_f, вычисляющая t_i, уже не такая простая. Я подозреваю, что всё дело в ней. Сами же integrate в v2 и v5 тоже разные, честная версия из v5 заметно медленнее версии из v2, в которой используется тот факт, что шаги одинаковые. Но шагов в v5 меньше, чем в v2. В общем, довольно проблематично их сравнивать.

Да, наверное дело в этом.
Ну и в очередной раз мы говорим о проводимых сравнениях. :)

Насчет модификаторов интервала, я не совсем понял (и сходу не нагуглил, точнее нагуглил нечто обратно противоположное) — это что-то общее для всех контейнеров С++, или только для интервалов?
Прочитал наконец статью по ссылке — элементы подпространства ::view и есть по сути модификаторы для любых контейнеров.
Хм, ну подождите, если ставится задача «делаем универсальную функцию интегрирования трапециями, в которую снаружи передается набор узлов», то без трат памяти не обойтись.
Не обязательно. Если снаружи передаётся короутина, то она может порождать набор «лениво».
Если корутина — то да, а если именно массив значений, полученный по сети?
Если это массив значений, то никакие ranges не нужны.
… и это та мысль, которую я хотел высказать, но боялся )))

А если стоит задача генерировать узлы «на месте», то можно генерировать их прямо в теле функции, а можно передать указатель на функцию-генератор (привет Си), а можно корутину, или вот ranges, или вообще что угодно еще, с примерно одинаковым результатом.
вообще что угодно еще, с примерно одинаковым результатом.
Не совсем. Ranges — это некоторая абстракция, которая легко получается из корутин или генераторов.

Вообще мне кажется на практике ragnges, реализованные через корутины могут оказаться самым распространённым вариантом…
UFO just landed and posted this here
Вообще я не призывал обойтись без темплейтов, просто приводил альтернативные примеры получения (не хранения) набора узлов «на месте».

Зачем войд-звездочка и пара функций? Вот вам без звездочек и пары функций (сказал он с хитрой улыбкой):
#include <iostream>

double integrate(int generator()) {
    for(int i {}; i < 42; i++) {
        std::cout << 1.1 * static_cast<double>(generator()) << std::endl;
    }
    return 0.0;
}

int Generator()
{
    static int count {};
    return count++;
}

int main() {
    integrate(Generator);
    return 0;
}

UFO just landed and posted this here
А вы на каждый набор параметров генератора будете писать свою функцию?


Все зависит от обстоятельств. Если генератор в принципе должен быть параметризован (что не факт, и, кстати, выше это требование не предъявлялось), то это можно сделать разными способами, а можно не суметь сделать вообще.

А запускать вычисления в том же потоке ещё раз с нуля как будете?


Тут конечно не поспоришь, в приведенном выше примере придется мутить несколько более сложный генератор (и конечно в стиле "о Боже, что это").
UFO just landed and posted this here
Эм, не совсем понял, что вы имеете в виду в привязке к конкретному примеру — интегрировать в несколько потоков? Или в каждом из потоков считать свой интеграл, но по одному и тому же генерируемому функцией-генератором набору? Если второе, то thread_local с тем же самым вышеприведенным кодом вполне будет работать, почему нет?
UFO just landed and posted this here
Здесь я должен честно признать, что в моих задачах всегда хватало «запуска пачки std::thread руками» (образно выражаясь). Я смутно себе представляю, зачем может потребоваться делать то, о чем вы говорите.
UFO just landed and posted this here
общий параллелизм охота ограничить каким-то фреймворком для управления параллелизмом


Ну, это тогда надо смотреть, что за фреймворк… Если он действительно ведет себя так, как описано выше, то возможно не только в вычислении интеграла возникнет фатальная проблема.
Про icc — надо дизасемблировать и тогда сравнивать. Например, он любит анролить и инлайнить поболее gcc/clang.

range что с сеткой, что без неё всё равно остаётся последовательным.
А хотелось бы магии, чтобы для задач вроде подобной сгенерировался такой код, который разбил бы range на subranges, и запустил их в параллель на нескольких ядрах.
Но для этого сами исходники (итератор, который знает свои границы и умеет шагать вперёд) не очень хороши. Ну разве что сделать тестовый прогон без финальных вычислений, и там эти самые границы разметить (собрать таблицу значений итератора), а потом уже выдать каждому потоку/задаче по собственному диапазону/подтаблице. Но это уже не так прямолинейно.

Тут недавно про julia писали, так там это есть из каробки. И для любителей дифуров много чего есть.

ps: Еще надо быть готовым что при распараллеливании результаты вычислений могут отличаться, так как (a+b)+c не равно a+(b+c) для double.

Есть такая штука, как OpenMP. В v1.cpp она сходу даёт ускорение в 10 раз (у меня 16 ядер), но заставить её работать с range-based for loop (for (auto t: ts)...), и тем более с ranges у меня не получается. Возможно, просто время позднее и руки кривые.

Интересно как изменится производительность в V2 вектор заменить на «голый» массив?
P.S.
… а v4 в три раза быстрее, чем v1
а вот это совсем странно.
Sign up to leave a comment.

Articles