Pull to refresh

Comments 15

Относительно функции calculatePositionsAndBounds есть предложение использовать std::transform. Так можно избавиться от ссылки на контейнер в замыкании и еще больше прояснить смысл творящегося.

Нечто вроде такого:
vector<Shape> calculatePositionsAndBounds(float const dt, vector<Shape> const shapes)
{
	vector<Shape> updatedShapes;
	updatedShapes.reserve(shapes.size());
	
	transform(shapes.begin(), shapes.end(), back_inserter(updatedShapes), [dt](Shape const shape)
	{
		return Shape{shape.id, shape.vertices, calculatePosition(shape, dt), shape.velocity, shape.bounds, shape.cellsRange, shape.color, shape.massInverse};
	});
	
	return updatedShapes;
}
Да, действительно так лучше, спасибо. Я оставлю ссылку на оригинальный коммит, но в HEAD заменю на std::transform.
Я не пойму что за треш у вас под спойлером 'CalculatePositionsAndBounds'?
Как вы умудрились применить std::move к const обьекту?
Это же абсолютно бессмысленное действие.
std::move приведет обьект к rvalue ссылке, только и всего.
Но переместить его содержимое вы все равно не сможете из-за модификатора const
Подробнее:
Функция calculatePositionsAndBounds одним из аргументов у вас принимает vector const shapes.
Затем вы передаете std::move(shapes) в функцию updateOne принимающую vector shapes.
Этим кодом вы просто копируете shapes, так как move приводит shapes к &&, но из-за константности исходного аргумента перемещение не сработает — только копирование.
С тем же успехом вы могли вообще не использовать move
Хотя foo() и принимает const &, сами данные не являются константными, а это значит, что они могут быть изменены до и в момент вызова accumulate(), например, другим потоком. Именно поэтому все данные должны приходить в виде копии.

Мы же вроде договорились, что пишем в функциональном стиле и такого не делаем? Т.е. тут должно быть что-то в духе const vector(const int), если уж заниматься дуристикой. К тому же передача по значению не спасает от того, что вектор испортит другой поток в то время, как мы копируем его в нашу функцию.

Сейчас всего 330. Триста тридцать, Карл!

Мы вызываем по несколько malloc и free и копируем несколько мегабайт данных на каждый вызов каждой функции, Карл! Так даже самая первая имплементация Лиспа не делала.

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

Если соблюдать копирование при передаче, то изменение данных другим потоком исключена.
Мы вызываем по несколько malloc и free и копируем несколько мегабайт данных на каждый вызов каждой функции

Именно это и является проблемой производительности — согласно профайлеру 80% времени уходит на обслуживание вектора. Оптимизицая была отложена до следующей статьи.
Использую техники для средств которые под них не заточены к хорошему не приводит. Как минимум придётся отказаться от части преимуществ обычного подхода, появятся новые проблемы привнесенные из новых техних, и не факт что полученные плюсы перекроют минусы. Это так же как есть суп вилкой, вроде макарошки доставать быстрее, но суп хлебать стало дольше. Мне доводилось видеть как на c++ с применением qt клепают формочки используя ФП. Мало того что нарушаются принципы ФП (за которыми вы честно старались следить), так еще возникают проблемы с самой структурой программы, когда любое изменение логики внезапно заставляет менять функции нарушая контракты. Это всё конечно можно невилировать используя другие техники, но то, что выходит в итоге кроме как зоопарком назвать нельзя. Есть ещё похожие примеры: создание ООБД в РСУБД. РСУБД такое позволит, но за извращение придётся платить. Как по мне лучше не «мешать», а оставить ФП для ФП языков. Выигрыш будет бесспорно в той части для которой вы ФП примените, но общая сумма всех минусов ни куда не денется.
С++ мультипарадигменный язык, который ни одну парадигму полноценно не поддерживает, но комбинируя их можно выжать максимум возможного. Также не очень эффективно использовать С++ для чистого ООП, для этого больше подойдут C# или Java.
Спасибо, я это знаю. И всё же С++ в первую очередь ориентирован на процедурное и объектно-ориентированное программирование, но функциональное программирование в нём это скорее стиль чем более-менее чёткое следование парадигме. Возможность использовать разные парадигмы это его плюс бесспорно, но за всё надо платить и как следствие получаем проблемы при не самых распространённых подходах его использования.
А что до эффективности использования. В первую очередь надо выбирать инструментарий на котором будет эффективно решаться задача, а не перепиливать полюбившиеся и хорошо известные под что-то на что они не заточены изначально или заточены слабо.
Ну не знаю, как по мне, читаемость кода получилась так себе (с длиной строк беда). Насчет тестируемости — нет сравнения тестов для ООП и ФП решений, ну а голословно можно было с тем же успехом утверждать и обратное. Распараллеливание — да, наверное, аналогичные вещи можно сделать красивее, но, опять-таки, утверждать такое без личных примеров — странно, особенно с учетом того, что одна из целей статьи — самому разобраться в технологии. Сомнителен и довод против — возможно, замедление вызвано тем, что Ваше мышление не перестроилось, зачем же сразу клеймить само ФП…

Концовка вообще шикарная: «Я не самоубийца, чтобы использовать это в своей работе, но интуиция подсказывает, что штука хорошая»)
в одном потоке с 2Д сеткой мы могли симулировать 8000 фигур. Сейчас всего 330

330 это после распараллеливания?

Нет, о распараллеливании я планировал написать в следующий раз.
Более в функциональном духе было бы использовать ranges вместо передачи повсюду векторов. Плюс это бы сильно помогло с проблемой низкой производительности.
Ranges я планирую применить в следующей статье.
Sign up to leave a comment.

Articles