Pull to refresh
-2
0
Александр @iCpu

Бот

Send message

И третье, это прекрасные камеры, хорошая оптика и большая матрица. Сравните характеристики КАМ-О с Mastcam-Z. Внезапно, КАМ-О и КАМ-С по многим параметрам вполне на уровне: в чём-то уступают, в чём-то лучше.

А вот это вот "У меня на хуавее снимки красивее" даже не начинайте. Ваш "хуавей" сейчас не в космосе летит, одним боком нагреваясь до 120 градусов, а другим охлаждаясь до -180 градусов. Ваш "хуавей" не в вакууме сейчас, пережив перед этим ни капельки не комфортный запуск в космос. На ваш "хуавей" сейчас не светит солнечные 0,5 миллигрей в сутки. У вашего "хуавея" нет ограничений ни по энергопотреблению, ни по каналу связи, ни по тепловыведению. И даже так, ваш "хуавей" полагается на кучу алгоритмов улучшения картинки, без которых лишние мегапиксели лишь преумножают боль.

Можно, декомпозиция (хоть ФПшная, хоть ООПшная) именно про это. Сложные задачи сложны именно потому, что найти такое представление, ну, сложно.

"в виде совокупности малого числа простых задач" - в общем случае, нельзя. Можно "в виде совокупности некоторого числа более простых задач". Вроде нюансы и софистика, но если сам математический алгоритм на 10 000 различных операций, ты как их не переписывай, меньше 10 000 операций не получится. И как их не записывай, особо понятнее они не станут.

Нет, не решает. Я могу только повториться: в моём опыте я не видел ни одной ООПшной программы, решающей сложную задачу, и при этом легко и удобно расширяемой.

Unity Game Engine? Как бы самый первый пример, который приходит в голову. Можно сколько угодно сейчас попу рвать, "а ты вообще его видел", "а ты попробуй туда запихни сложный функционал", но на нём выходят тысячи игр самой разной сложности игровых механик. И выпускают их даже люди со знаниями уровня "Heoll Wolrd!". Значит, их ООП подходы работают.

С другой стороны, я не встречал на профильных площадках ни одного упоминания игрового движка на Haskell, например. Да и на других чисто функциональных языках. Я не говорю, что их нет, или что функциональный подход плох. Но вот как есть.

Что за правила вызова тут подразумеваются?

Зависит от языка и среды. Я не настолько глуп, чтобы под одну гребёнку грести плюсы, жабу и жабоскрипт (которые все трое ООП).

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

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

компилятор не начал вам гарантировать, что объект не лезет в какой-нибудь другой объект, или что для корректного использования этого объекта надо сначала провести определённый ритуал настройки другого объекта, и так далее

А компилятор должен это делать? В какой из букв ООП это сказано? А если он гарантировал, а мне нужно эту гарантию нарушить из-за изменившихся условий, то что? Кто первый пойдёт в пешее эротическое?

Как бы, гарантии должен давать не компилятор, а создатель кода для класса. Его задача - дать удобный предсказуемый функционал. И если для этого нужно иметь скрытые состояния и неявные вызовы (а ты попробуй окошки одинаковыми на winapi и на x11 нарисуй без говнокода под капотом и кучи скрытых состояний), значит, они будут лежать скрытыми под капотом. И будьте уверены, в Хаскеле будет тот же самый говнокод скрыт под примерно такими же вызовами, с поправкой на язык.

И не надо закатывать глазки и устало вздыхать. Вот человек поделился своей историей https://habr.com/ru/companies/sportmaster_lab/articles/728880/comments/#comment_25471164. Вполне реальная, у меня так же бомбануло, когда сервак с другой стороны перестал отдавать нормальные данные с первой попытки, а стал спамить ошибками и невалидными ответами с 200-м кодом. И мне пришлось переписывать класс загрузчика несколько раз, добавляя всё больше скрытых состояний, "дазаткнизьмов" и валидаторов. Но клиентский код почти не изменился (кроме возможности передать собственный валидатор). Публичный интерфейс не поменялся. Класс, как чёрная коробка, остался бинарно совместим со своей предыдущей версией. Значит, свою задачу ООП выполнило. Не нравится? Да вообще всё равно.

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

Но как пример перехода большой задачи в сложную - вполне себе хороший. И на ООП скрыть всякие досылки, keep-alive и прочие пулы, - куда проще, чем на вот такой уже выоптимизированной до свищей и табличных обработок кодовой базе.

Для начала, давайте капельку отвлечёмся от реального мира к теории. Помним же, что есть большие и сложные задачи? Первая - эта та, в которой простые действия повторяются на больших объёмах данных. Вторая - это где большое число внутренних связей и логики, и которую сложно описать. Не то, чтобы тут это кто-то не знал, просто напоминаю, чтобы не было недопонимания. Ну так вот...

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

Нельзя представить сложную задачу в виде совокупности малого числа простых задач. Она на то и сложная, чтобы быть месивом ещё на этапе формулировки. А у нас всё чётко: garbage in - garbage out. Если формулировка месиво, то и реализация соответствует. Через какие решения её не реализуй.

Но, позвольте, разве ООП не решает свою задачу? Разве наследование публичного интерфейса со всеми обязательствами на уровне языка не решает проблему соответствия правил вызова между разными объектными файлами? Разве структурирование в классы и объекты не повышает предсказуемость поведения кода? Всё они успешно делают, и достаточно оптимально.

Если не решают, значит, вы их неправильно готовите.

Вот, кстати, да. Очень важный момент: оптимизировать надо только то, что не оптимально, и только так, чтобы повышалась эффективность в контексте данной задачи.

В контексте конкретно поставленной задачи площадной числодробилки, "чистый код" с виртуальными вызовами (которые почему-то не были выоптимизированы, видимо не хватило флагов компилятора) будет хуже "грязного". Вот только после перехода от switch к таблице констант читаемость кода понизилась значительно.

Но "числодробилка" - не всегда основная или даже значимая задача. Есть например задача "рисовать форточки". И я получал награду "лучший порноактёр года" все эти форточки реализовывать через свичи и юнионы. Хватит! Навинапишился я с этими хвиндами! Вот конкретно тут мне куда проще и понятнее построить пару лишних фабрик, и рядом табачную мануфактуру "Manual" задокгенить, чтобы было чего потом покурить.

Кроме того, товарищ @JustJeremy нам немного насвистел в уши. Фигуры в реальном коде определяют через координаты и иные параметры. Я ещё ни разу не встречал реального кода, где треугольник бы определяли через основание и высоту (хотя для ряда задач, согласен, это возможно). То есть для обоснования своей спорной точки зрения "Ой, смотрите, как красиво код получается табличкой!" нам подсунули изначально туфту - и из неё делают какие-то далеко идущие выводы.

А давайте, товарищ@JustJeremy, немного повысим ставки. А давайте у нас там будет не вычисление площади для искусственно подобных фигур, а, например, различные алгоритмы хеширования? И не просто sha1 и md5, а ещё и ГОСТовые возьмём! А для оправдания виртуализации, пусть у нас будут различные таблицы инициализации, не только стандартные, но и определённые пользователем. И соль! Покажите, как у вас красиво все структуры поместятся в union, как красиво табличкой сложатся функции, и как потрясающе на порядки вырастет производительность из-за замены виртуальных методов на свищи!

И не надо свистеть, что "Это другое". Это - то самое.

UPD: Оп! Не сразу заметил, что это перевод. Ну, что же. Перевели? Значит, подумали, что статья хорошая. Ну вот и отдувайтесь за автора.

Ладно, согласен, неправильно выразился. llvm использует код для сишного restrict при оптимизации использования ссылок. Из-за этого в определённый момент такая оптимизация не работала.

Ну, то есть, спора по поводу явной большей семантической корректности Сишного и Плюсового кода нет? Ок.

Я на расте не писатель. И не собираюсь. Но я точно знаю, что дерево поиска ошибок у любого компилятора конечно. Нам просто нужно попасть на один уровень глубже.

Потому что & и &mut на расте - это прямое переиспользование сишного restrict. И способы обхода этого ключевого слова на Си известны.

Вы указали на безошибочный код с лишним копированием и сказали: "А на Rust мне язык не даёт так сделать."
Я показал корректный код, в котором лишнее копирование никогда не происходит. При этом, то же самое можно сделать через простую переменную и копирование, просто тогда всегда будет оверхед по памяти.
Но вы не довольны. Хотя код на расте явно дырявый: компилятор не может бесконечно глубоко в дерево уходить, на каком-нибудь пуле объектов всё закончится. И даже если у вас попытку перезаписи поймает менеджер памяти, где он это сделает и куда вывалится? У вас есть там нужная обработка? Нет? Ой-ой-ой...

Но если хотите, почему нет? Проведите тесты производительности в оптимистичном (частота помещения в себя 0%), обычном (5%), пессимистичном (30%) и терминальном (100%). В один и несколько потоков. Запишите график использования и фрагментации. Потом повторите, запихнув между умножениями другие операции. Не забываем проверять с разными ключами компилятора: не только с -O, но и c SSE\AVX, с необычными оптимизациями разных компиляторов. Мы же взрослые дяди? Можно ещё и необычные std подключать, реализация от микрософта или gcc далеко не всегда и не во всём топчик. И тестить на всех системах, от древних Windows до новых Android, промежая сборками под микроконтроллеры и одноплатники.

Посмотрим на фрагментацию памяти во всех режимах!

Это же вы хотите всё делать как в продакшене? Прошу! Доказывайте, как серьёзный дядя, что такая реализация хуже, по сути, никакой в Расте.

А давайте ещё представим что у нас многопоток? И не простой, а помесь OpenMP и MPI. Не будем расслабляться!
А потом давайте представим, что у нас могут быть MatrixView с частично пересекающимися областями, и что сравнения указателей не хватит. А чо нет-то?
Что ещё бы представить? Можно ещё разряженные матрицы. Или потоки матричных преобразований с оптимизатором. Или что-нибудь ещё в этом духе. Красиво жить не запретишь!

А, может, не будем представлять? Это такая редкая операция, что вы на неё положили болт 50ой резьбы. Вы её просто отбросили как невозможную в расте. Хотя, на деле, ничего невозможного в ней нет: прилетит ссылка через четвёртые руки - и всё равно заломает программу, так или иначе.

Хотите специальных оптимизаций - делайте специальные реализации. Пишите кастомный аллокатор, делайте статические переменные. Или добавляйте ссылку на буфер непосредственно в класс.

Ну вот, добавили овердофига бойлерплейта, а сам метод, к которому предъявлялись претензии, так и не реализовали, ну как же так xD

Месье любит несвежие портянки?

Кстати, как вы думаете, move конструктор будет как-то осмысленно отличаться от копирования в данном случае?

Хм... А вот сейчас я задумался. У std::array вообще есть конструктор перемещения? Вроде, нет. Ладно, уел)) А std::vector точно просто обменивает указатели при перемещении.

В общем, как же должен выглядеть "правильный идиоматичный код на С++", который бы не имел тех же проблем (из описанных в посте), что и текущий пример, так и осталось неясным.

Конкретный пример некорректен. Почему нельзя матрицу умножать на себя и поместить результат в себя же? Потому что какой-то DancingOnWater решил, что конкретно этот случай он не будет реализовывать? Тогда обложись assert'ами или поменяй интерфейс функции.
Но если ты хочешь при этом интерфейсе работоспособность оставить, тогда, конечно, нужна обвязка вроде такой.

Претензию к тому, что язык допускает передачу одной и той же переменной и как константную ссылку, и как не константную, и что разработчик 99% продолбится в глаза мимо этого случая, принимаю.

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

Можно промахнуться даже мимо кнопки "сделать хорошо". Ошибиться можно и в Rust'е, там тоже полно подводных камней и неявных правил. Вам уже сверху накидали примеров. Константность по умолчанию важна только если ты впервые язык видишь и вообще ничего не читал. У меня сейчас на автомате const& пишется везде, а потом думается в другую сторону.

М-да, даешь пример, чтобы подсветить идею, а тут начинается...

Пардонте, нужно было пример лучше выбирать. На каком-нибудь 03 или 07 стандарте я бы согласился, что у плюсов всё очень не просто с выразительностью или с безопасностью. В 20+ к таким мелочам докапываться - это просто оскорбительно.

А в 23 стандарте можно даже перегрузить operator[](int i, int j)

Для начала, не изобретать велосипед, а взять готовый
https://www.boost.org/doc/libs/1_81_0/libs/numeric/ublas/doc/overview.html
или
https://www.boost.org/doc/libs/1_81_0/libs/qvm/doc/html/index.html#_quaternions_vectors_matrices

Но если хочется стрелять по ногам, обернуть себе std::array в матрицу
class Matrix : public std::array<double, 9>{};
и реализовать всё на ссылках
void rxr(Matrix const& a, Matrix const& b, Matrix & atb)

А можно реализовать move-конструктор и оператор перемножения - и оставить работу компилятору
Matrix(Matrix && other) = default;
Matrix operator* (Matrix const& other) const;
/*...*/
Matrix a,b,с;
Matrix atb(a*b);
atb = std::move(atb*c);

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

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

Очень странное зрелище, знаете ли, человеческое фиговое дерево.

Ну, во-первых, это уже огромные сетки.

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

И в-третьих, эти сетки не то, что новое не умеют создавать, они даже catastrophic forgetting ещё не смогли преодолеть. Не верите? Создайте контрольные запросы с фиксированными сидами, переобучите SD на новый стиль рисования или UL2 на определённого автора\серию книг, и сравните результаты на этих же запросах и сидах. Я кринжанул после скармливания в GPT всех книг по Вахе 40к.

Предсказуемость в данном случае это именно фоторелистичность выхлопа DALLE и "неотличимость от человеческой журналистской или творческой писанины" выхлопа ChatGPT

Но это не обобщённые, а узкоспециализированные нейросети. Любые попытки вывести их за пределы чётко поставленной перед ними на этапе обучения проектирования задачи даёт крайне плачевный результат. Я уже приводил пример, что SD сходит с ума, если передать не знакомый ему текст. Другой пример, научите DALLE выводить текст. А я на вас посмотрю.

За последние два года эффективность обучения тех же GPT-подобных сетей выросла на порядок

Вы не можете просто запихнуть "больше" возможностей в "меньше" нейронов. Если вы хотите наращивать функционал, вам неизбежно придётся увеличивать число и размеры слоёв. SD уже прямо сейчас не очень уютно себя ощущает при <10ГБ видеопамяти. И генерация на чём-нибудь менее производительном 20-й серии nVidia или AMD RX 6ХХХ вызывает жжение пониже копчика. Дообучение сетей на новых данных, как бы это удивительно не было, размеры обычно не уменьшает.

Вы можете тешить себя надеждами, что возможности оптимизаций ещё не исчерпаны - и вы, скорее всего, правы. Но на оптимизации "на порядки" надеяться очень наивно, даже "в разы" - маловероятно.

Не, в теории там может быть что угодно. Тем более, я и не спорил, что это возможно.

А на практике? Пока что любые попытки делать обобщённые нейронки упираются в безумное время обучения и системные требования при абсолютно непредсказуемом результате. И не мудрено, у нас в голове десятки миллиардов нейронов работают в абсолютном параллелизме. Лучшее, что мы смогли сделать для бытового использования - десятки тысяч ядер. Даже с учётом частот, мы ещё хотя бы на три порядка отстаём от нужных значений, как по ядрам, так и по памяти.

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

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

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

Information

Rating
Does not participate
Location
Новосибирск, Новосибирская обл., Россия
Date of birth
Registered
Activity