Обновить

Почему программисты стали писать медленный код и это почти никого не волнует

Уровень сложностиСложный
Время на прочтение4 мин
Охват и читатели27K
Всего голосов 67: ↑61 и ↓6+66
Комментарии118

Комментарии 118

Если массив лежит последовательно в памяти, CPU может читать его очень эффективно. 

А с чего это массив не лежит последовательно? Массив на то и массив, что лежит последовательно. Если это не так, то язык предлагает только видимость массива. В этом и заключается отличие одних языков программирования от других. Язык программирования C++ гарантирует, что массив — это упорядоченный набор объектов, располагающийся в памяти в идущих подряд ячейках памяти.

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

Я не супер силён в плюсах, но в .net есть классы и структуры. Массив классов хранит ссылки на экземпляры классов (последовательно), но сами данные по ссылкам могут находиться в разных местах. Массив структур хранит данные внутри себя и данные лежат последовательно. Предполагаю что в плюс при хранении ссылок в массиве, сами данные тоже могут быть размазаны по всей памяти, в зависимости от времени создания конкретного экземпляра, на который ссылается элемент массива.

Возможно это и имелось ввиду, но написано, что массив может не лежать последовательно в памяти, что не является верным утверждением и может относится разве что к связанным спискам, которые по факту массивами не являются.

В приведенном примере с sum_array на вход подаётся массив 32-битных int'ов, а он гарантированно лежит последовательно в Rust'e.

Массив на то и массив, что лежит последовательно. Если это не так, то язык предлагает только видимость массива

Давайте только вспомним, что в современных операционных системах приложение работает не с физической памятью, а с виртуальной. Блок виртуальной памяти управляется операционкой и отображается в набор страниц физической памяти (обычно по 4Kb), которые в общем случае не будут расположены последовательно. Можно ещё вспомнить как C++ хранит многомерные массивы. Короче, абстрактно автор прав: ветер дует, дождь идёт, огонь горит. Но в деталях все не так очевидно. Поэтому сделанные выводы это лирика - они не практичны.

В статье речь была про два массива в первом массив структур на три инт поля и каждую итерацию 2/3 данных не нужны, но грузятся и занимают кэш линию. Во втором примере чтение идёт по линейному массиву интов без забивания кеша ненужными данными (поля y и z)

А с чего это массив не лежит последовательно?

Массивы разные бывают.

Кому и таблица массив (да, луа, я смотрю на тебя)

А с чего это массив не лежит последовательно? Массив на то и массив, что лежит последовательно. Если это не так, то язык предлагает только видимость массива. В этом и заключается отличие одних языков программирования от других. Язык программирования C++ гарантирует, что массив — это упорядоченный набор объектов, располагающийся в памяти в идущих подряд ячейках памяти. 

Вы смешиваете названия абстракций в языке программирования и их реализацию

C++ гарантирует, что

Ну вот string вроде как массив, но до C11 это не гарантировалось.

СкрипачКеш нам не нужен!

Мой посыл в том, что детерминированность работы кода была бы совсем не лишней. А то сейчас мы в такой ситуации, что если CPU промахнулся мимо кеша то все, "суши весла"

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

Именно поэтому первая рекомендация при оптимизации кода - первым делом выполнять профайлинг.

Согласен про разные архитектуры, это база. Я скорее о том, что кэш-локальность сейчас "бьет" алгоритмическую сложность, из-за кэша std::vector с линейным поиском на реальных данных часто обгоняет std::unordered_map с его O(1). Ирония в том, что выбор структуры данных перестал гарантировать результат, пока не поймешь, как она ляжет в кэш линию.

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

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

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

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

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

А и то, и другое одновременно — ещё лучше!

А это не задача программиста, а задача оптимизирующего компилятора, на минуточку.

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

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

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

Коэффициент попаданий в кэш сейчас - 96% или больше (ну, от размера кэша зависит, конечно).

И от эффективности маркетологов - положите данные в память неудобным для процессора образом и у вас и близко такой цифры не будет.

А это не задача программиста, а задача оптимизирующего компилятора

Простите, но как компилятор может заменить std::unordered_map на std::vector? Это решение программиста, а не вопрос выравнивания адресов. На небольших наборах данных вектор за счет кэш-локальности просто "выносит" мапу.

Что касается "всего 4%" промахов, один cache miss стоит 200–300 циклов CPU и часто это очень много.

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

Я б немного перформулировал, нет тех кто заинтересован в том, чтобы программисты НЕ gовнокодили программы, кроме самих программистов (ну должно бы быть так, чтобы они были заинтересованы, профессионализм там, ответственность за профессию и прочее забытое). Корпорациям на это все равно, они перекладывают затраты на пользователей: надо больше больше железа да, пожалуйста, - распределяем на стоимость услуги конечного пользователя. А как сделать так чтобы это купили, еще Голдратт в 80-90х придумал: впариваем ненужную им услугу за те же деньги и говорим им, что она очень нужна. Итого их заинтересованность нулевая. У государств по факту тоже, благодаря круговороту рост цен - рост зп у них растет ВВП - современный предмет гордости государства.

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

Компаниям безусловно выгодна быстрая и дешёвая разработка. Ну а тут, как известно, "быстро, дёшево, качественно – выберите 2 из 3".

И вам ещё очень повезёт, если это будет 2 из 3, а не 0 из 3.

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

Но с другой стороны я глубоко уважаю нативный код.

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

Очень много заинтересованных лиц, чтобы программисты gовнокодили

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

Ага, ага.

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

Это лень.

Не всегда. Вообще, читаемость всегда превыше оптимизации - это аксиома.

Только не при оптимизации.

Оптимизацыя - это уже не лень, а отдельный этап разработки.

Есть давняя проблема в том, что программисты разрабатывают и пишут алгоритмы, сложность которых растёт быстрее, чем эффективность использования существующего железа. Я забыл цифры, но условно возможности 286 процессора использовались процентов на 60 при появлении 386, в котором стало использоваться только 30 процентов. Не успели достичь 60 процентов на 386, как появился 486. Не думаю, что сейчас ситуация другая.

Или другой пример - в 70-80 существовали мат библиотеки ещё на фортране, оптимизированные для машин, где, условно, умножение было медленнее сложения в 10-20 раз. Когда появились сигнальные процессоры, у которых операция умножения со сложением и инкрементами адресов выполнялись за один цикл, эффективность этих алгоритмов существенно изменилась и, к примеру, бпф на 64 работало с такой же скоростью, как и дпф в лоб, а на 32 и меньше даже медленее.

То что происходит, плата за сложность и переносимость. Возьмите какую нить сапр с жизненным циклом лет 20 - под каждый новый чип и платформу выпускать аппаратно зависимый рантайм? То есть на старой платформе он работает с ммх и sse2, на текущей уже авх или как там его, а через 2 года появится порт на супер5дматрикс экстеншен в облаке, и все эти 10 портов надо фиксить, расширять, тестировать ещё 15 лет? Потому что то что эффективно сейчас при портировании может перестать таковым быть.

В общим да, купить ещё один сервак это ответ современной индустрии. Ну или жёсткий лимит ресурсов

Возьмите какую нить сапр с жизненным циклом лет 20 - под каждый новый чип и платформу выпускать аппаратно зависимый рантайм?

Как это ни парадоксально, но именно какие-нибудь САПР с 20-летним жизненным циклом хорошо работают на всяком железе. В отличие от всяких продуктов веб-разработки, жизненный цикл которых измеряется месяцами, если не днями. И которые, зачастую, вообще изначально неработоспособны прямо на том железе, для которого из писали. Ну или теряют эту работоспособность при первом же обновлении Андроида, который и сам является точно таким же продуктом.

Я не говорил хорошо или плохо, я писал про эффективность использования аппаратно програмной платформы.

Вот gimp возьмите - там на виндовсе уже поддерживается многоядерность? Я тут фотки старые отсканированные разрезал, так и не смог в винде ничего с ними сделать, зато под линуксом почти без тормозов. Так это только ос разные, не аппаратура.

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

К моему огромному сожалению, Autocad 10 на Windows 7 даже не запускается :(

и не должен. Он под 10 писался

14 вышел во времена windows 95/NT4, так что 10 ещё раньше.

10 был под DOS.

Как это ни парадоксально, но именно какие-нибудь САПР с 20-летним жизненным циклом хорошо работают на всяком железе.

Ну, надо сказать, что на 30 лет, увы, делать что-то можно только на Win32 API. Всё остальное тухнет — вот тут из того же ALT убрали QT4, например. А ведь 20 лет прошло всего-то.

Я забыл цифры, но условно возможности 286 процессора использовались процентов на 60 при появлении 386

На самом деле, уже тогда всё было значительно хуже, и 286, и 386 долгое время существовали в реальном режиме 8086, причём когда на 286 появился защищённый режим и его более-менее освоили (чтобы иметь доступ к памяти выше 1 МБ), уже появился 386, защищённый режим которого оказался несовместим с таковым у 286, в итоге, программистам пришлось 2 раза проделать одну и ту же работу.

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

Ну да, всё верно, именно эту книжку я наверное и читал тогда)

"бпф на 64 работало с такой же скоростью, как и дпф в лоб, а на 32 и меньше даже медленее."

Работает оно может с той же скоростью, но транзисторов согласитесь на умножение нужно больше. И вообще умножение всё ещё медленнее. Просто там все сложнее, например intel Arrow Lake: умножение занимает 3 такта, но можно сразу начинать много операций умножения на каждое ядро, то есть если умножений много можно сделать условно за каждый такт 3 или даже 5. А вот с операцией сложения вообще весело там. Если сделать +500 то это не занимает вообще ни одно такта процессора. А вот +1000 уже 1 такт. Но также процессор Lion Cove может сделать 5 сразу сложений если результ не используется для следующего сложения. А если используется то там +500 все равно не занимает ни одного такта.

Не, intel в 90 не мог в dsp)

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

В принципе Ваш пример тоже про это - если попытаться переписать бпф для многоядерного интела, от классического варианта тоже вряд ли что останется

Какую-то книжку читал, там по сюжету ГГ попал в тюрьму, где его обучили кипятить чай, сжигая щепки, соструганные с подоконника. Потом он вышел, и больше так не делал, почему-то.

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

P.S. Астрологи объявили неделю идиотских аналогий. Количество разбитых лиц увеличилось вдвое.

Астрологи объявили неделю идиотских аналогий.

Ну, коли так...

Согласен, сейчас на производительность мало кто обращает внимание, пока совсем не припрет, особенно на мелкие детали.

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

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

Достаточно не фигарить на Питоне, тогда вся программа может попасть в кеш. Целиком.

Так стоит ли вообще переживать из-за производительности

Стоит обмазывать сервис метриками и запускать профилирование время от времени. А поводов переживать у нас и так достаточно.

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

К тому моменту 90% уже написанного нечто жрущего память, ресурсы процессора , тратящего беспощадно электроэнергию... уже будет выкинуто на свалку (нет, не истории) небытия, о котором уже мало кто вспомнит.

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

То, что я писал ещё каких-то 18 лет назад, сегодня осталось только в моей памяти, в памяти моих коллег и возможно в памяти тысяч пользователей(в чем я малость сомневаюсь), которые когда-то пользовались платёжной системой SUPERKACCA в Узбекистане, которая работала на платёжном сервисе Aves от Штрих-м. Компания закрылась, код весь утерян даже на сайте ещё живущей штрих-м уже давно нет упомянаний об этом софте. Обрывками осталась информация на некоторых ещё живущих форумах.

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

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

Скажу так, писали Aves неглупые люди. У меня ,например, повторить что по качеству кода за много лет так и не получилось, хотя пример был перед глазами. Не ожидал что кто-то вспомнит про этот софт.

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

Не надо было блокнот переписывать ))
Работает он во много раз быстрее по одной причине - он написан на С++/WinAPI. Новый блокнот из последних windows такой тормозной потому что написан на всяком Metro Apps / WPF / какая там сейчас последняя мода.

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

Бизнесу нужны фичи и юзерам нужно богатые функции.

Нет. Юзерам нужно, чтобы продукт работал. Бизнесу нужно, чтобы через три года (ещё лучше через год, но тут уже могут начаться вопросы) продукт сломался. Какие новые функции появляются у блокнота? Какие новые функции появляются у популярных приложений для смартфонов (какой-нибудь youtube условный)? Вообще никаких. Бизнесу просто надо, чтобы через 3 года юзер снова потратил деньги.

Бизнесу просто надо, чтобы через 3 года юзер снова потратил деньги.

На блокнот?

Ну вот сейчас в блокнот Вин11 внедряютса всякие ИИ-функцыи. Оказываетса, в контекстном меню даже есть "Найти через Bing".

На новый ноутбук с предустановленной Windows 11.

Новый ноутбук он и так купит. С windows 11 конечно, ведь 10 уже давно eol. А блокнот там воще пофиг какой; процентов 99 юзеров запускают его приблизительно никогда. А таскбар, который видит 100% юзеров, никак не удается передвинуть.

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

Сложность задач состоит в изучении нового фреймворка для каждого нового продукта, верно? Или в чём?

Скорее, для каждой отдельной бизнес-задачи. Есть отдельные фреймворки для реализации сетевых протоколов. Фреймворки для реализации абстракций над сетевыми протоколами. Фреймворки для низкоуровневой работы с БД. ORM.

В большинстве случаев, вы не будете писать своё решение. Чаще, вам скажут: «эй, друг, используй эту ORM! Да, там сейчас лишь один запрос к БД, но будет больше. Давай делать наш код сопровождаемым»!

Зачастую, мы говорим о том, что время программиста стоит больше, чем ресурсы сервера. И очень часто мы сталкиваемся с дилеммой баланса между скоростью разработки и скоростью выполнения программы. При этом, если сеньор или лид знают как оптимизировать код, они скажут об этом на ревью. Но, в большинстве случаев, если вы будете требовать изменить определение массива из-за компоновки его членов в памяти, вас сочтут токсиком и пожалуются ПМ-у.

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

Это вообще задача из разряда чистой архитектуры и чистого кода: создавать больше интерфейсов и инвертировать зависимости. Эти подходы дяди Мартина делают код более медленным, но значительно улучшают сопровождаемость. Основной принцип, который рождает эти парадигмы, я уже озвучил: стоимость времени специалиста сейчас выше, чем стоимость серверных мощностей. Оптимизация кода ухудшает его читаемость, усложняет работу по изменению кода, когда меняется задача от бизнеса. Поэтому, да, мы будем делать Repository или Bridge или Adapter, чтобы иметь возможность использовать разные ORM или разделить слои обработки данных со слоем их хранения. Сюда ещё добавится сериализация и десериализация данных. В итоге, мы получим не только сопровождаемость и читаемость, но и потенциальные возможности горизонтального масштабирования. Например, нам будет легче добавить какой-нибудь шардинг. Или отсылать данные сразу в несколько СУБД, чтобы получить отдельно хранение и выборку для аналитики. Здесь идёт конфликт между оптимизацией непосредственно кода и системным дизайном, архитектурой ПО. Не всегда точечные задачи по компоновке массивов в памяти дают ожидаемый прирост производительности. Например, в сравнении с потенциальными возможностями горизонтального масштабирования системы.

Если взять libreoffice, и сравнить с Лексиконом.. вся разница в фреймворке, да?

Когда я впервые увидел разницу в бенчмарке, она была почти в 3 раза. И честно говоря, я тогда немного офигел.

Разница в разы.

И в итоге программа становится в разы медленнее. Иногда это разница между миллисекундами и секундами.

А здесь уже на несколько порядков. Но только иногда?

Так стоит ли вообще переживать из-за производительности

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

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

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

Вы выбрасываете один виток эволюции и экономику.

Основная экономическая идея — это размен ТТХ программы на цену создания этой программы.

Реально производительность «обычного» софта, который не использует возможности какой-то из подсистем компьютера на 100%, определяется эргономикой — сколько времени пользователь способен терпеть, прежде чем софт среагирует. А требования к ОЗУ определяются тем, сколько средний пользователь может выделить этой программе памяти — по порядку величины полная память средней машины, делённая на кол-во одновременно работающих программ (сейчас это 8Гб поделить этак на 5-10).

Эргономически нам нужна реакция до 0.1 сек, если примерно, но часто хватает до нескольких секунд.

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

А реально прогресс в софте в плане «новые программы производительнее и во всех аспектах лучше прежних» — это крайняя редкость. Из промышленных примеров я знаю лишь VM/370, которая была во всех аспектах круче предшественников.

Экономика разработки изменилась. Раньше компьютеры были дорогими, а программисты — относительно дешевыми.

Это было до середины 80х. То есть, для большинства здесь присутствующих, компьютеры были дешевле программиста «всегда».

Примеры производительности на языке C++ настолько же отдалены от реальности как современная разработка от оптимизации. Если Сишный код один в один ложится на железо (и через компилятор можно еще понять что во что превратилось), то Javascript-еру нужно продраться через дебри фреймворка, API, VM, а уже затем вспомнить, что где-то там процессор молотит его тяп-ляп код.

Мне кажется, у Javascript-теров вообще не принято замерять производительность, а значит и думать о ней не надо.

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

С одной стороны ИИ отнимает хлеб, с другой бдительные граждане, которые не прочь посидеть на досуге с дисасемблером.

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

А чего тег "сложно"? Вроде ж азбучные истины :)

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

Недавно друг попросил с его сыном поговорить, так сказать проверить и дать напутствие. Сын его на программиста выучился. Я ему объяснил и показал как вместо всяких if-else можно оптимизировать код и воспользоваться булевой алгеброй для оптимизации. Нас учили не разбрасываться гигабайтами и гигагерцами, а считать каждый бит и каждый такт. Когда я ему показывал таблицы истинности и у него глаза на лоб полезли, то я спросил "вам разве такое не показывали". Оказалось, что совсем нет. Ни булевую алгебру, ни написание на ассеблере, ни устройство процессора и компьютера, т.е. слово "стробирующий сигнал" для него как разговор двух профессоров услышанный техничкой в коридоре. Учился он даже не левых блогерских на курсах, а во вполне в нормальном гос.учреждении.

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

Ко мне половина студентов 3го курса приходят без знаний даже на уровне хелло ворлдов.

А вы говорите стробирование...

Какой-то апофеоз снобизма.

Вот и не могут они ничего написать без библиотек с нуля.

А зачем писать системные/инфраструктурные/лоулвл вещи с нуля?
Вы код пишете чтобы решать с его помощью какие-то задачи или чтобы самоутверждаться с помощью рождения очередного велосипеда?
Если второе, то лучше уж в опенсорс поконтрибьютить, в те самые библиотеки.

Я ему объяснил и показал как вместо всяких if-else можно оптимизировать код и воспользоваться булевой алгеброй для оптимизации.

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

устройство процессора

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

слово "стробирующий сигнал" для него как разговор двух профессоров услышанный техничкой в коридоре

Оно для него примерно настолько же полезно и нужно, как оператору современного трактора требуется знание как запрягать коня и править плуг.

Учился он даже не левых блогерских на курсах, а во вполне в нормальном гос.учреждении.

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

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

И вот такие вот нас и учили, рассказывая базу из начала девяностых и про абсолютно мертвые технологии по методичкам 15летней давности.
Реальный толк был только от преподавателей-аспирантов, которые параллельно работали разработчиками и понимали что такое реальная разработка.

Если человек закончил ПТУ по специальности "Программирование программ" то да, ему всё это не нужно. Исключительно практическое направление подойдёт.

Но ВУЗ должен давать базу, с профилем специальности. Иначе зачем нужен ВУЗ? И чем он будет отличаться от ПТУ?

Я окончил ВУЗ по специальности где-то лет 20 назад. Из того, что преподавали пригодилось не более 1-2%

Такие дела

Единственная проблема — было невозможно заранее предсказать, какие именно 2% потребуются.

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

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

Дорогой друг, говоря в самом начале статьи про "миллиарды операций в секунду" - посмотрите на смысл слова "гига". И вспомните - когда появился первый процессор с тактовой частотой в гигагерцы.
Подсказываю - первым поколением процессоров, перешагнувших 1 гигагерц был Pentium 3. И это был 1999 год. То есть даже другой век. А мы уже четверть в этом прожили. И да, P3 мог делать 3 микрооперации на такт.
Так что вопрос не в миллиардах операций уж точно. И сейчас, если взять старый Haswell - то там было до 4-5 реальных операций за такт. На каждом из ядер. Берем топовый 4770.
3.4 (4.1 в бусте, но не на всех ядрах) умножаем на 4 (операций на так), умножаем на 4 (количество физических ядер) = 40 миллиардов
Тот процессор мог выполнять до 40 миллиардов инструкций x86 за секунду.
Это уже десятки миллиардов.
12 лет назад.
Сейчас же скорее - речь подходит к триллону у серьёзных моделей. И это мы не учитываем возможности специализированных ускорителей (OpenCL или Cuda тех же видеокарт)
Такие вот дела.

Сейчас уже 5 ГГц в бусте, 14 ядер, и почти 5к ядер в видеокарте - это в ноутбуке трёхлетней давности. На десктопах до 6 ГГц. Прогресс ни в коем случае не остановился.

Если не ошибаюсь, первый из x86 c 1 ГГц был K7 Athlon, и P3 его догнал, а P4 начали с 1,1-1,4. И Р4 был быстр если не останавливать конвейер (алгоритмы без ветвлений могли выполняться в разы быстрее чем с ветвлениями). С P3 был опыт, когда ломался со включенным L2 кешом - с выключенным, комп работал стабильно, но медленнее. Если вернуться ещё обратно к P2, то первые целероны мало кто хотел покупать из-за их медленности без L2, a целерон-А, из-за быстрого L2, был ровно или больше желаем чем P2.

Причём функционально она делает примерно то же самое.

Совсем не то же самое. Иначе у вас вообще нет никаких проблем - просто возьмите софт того времени и успешно работайте.
А при реализации этого и выяснится "не то же самое". Что в 90е размеры файлов измерялись килобайтами и мегабайтами, а сейчас файл-контейнер в 80гигов - норм. Что разрешение всем подавай fullhd минимум, а не 640х480. "Замыленное" изображение шрифта уже не норм - вы слышали в 90х вообще такой вопрос? Архитектур развелось куча и мы хотим одну и ту же программу, работающую на всех. Размеров экранов десятки вместе со смартфонами и мы тоже хотим разом под всех интерфейс писать.

И это все даже не считая главного факта: мы стали софт писать гораздо быстрее и больше относительно 90х, прям вот на много порядков. Оглянитесь вокруг на степень автоматизации: вот этого всего не достичь при старом подходе.

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

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

Дам одну подсказку для оптимизации. Никогда не делите на константу, а по возможности всегда лучше умножать на дробное число, а не делить и это относится ко всем процессорам и архитектурам(ао всяком случае на x86 и arm точно это так)

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

я пробовал где-то в 2012 - тогда деление было реально дольше.

Дам одну подсказку для оптимизации. Никогда не делите на константу

Эта оптимизация давным-давно вшита в компиляторы

Никогда не делите на константу,

Никогда не делайте цикл в цикле.

Ненаписанный код не занимает процессорного времени. Ни когда не пишите код. Читайте комменты на Хабре.

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

Массив структур? Не слышали!

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

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

Дальше мы просто выбираем оптимальное решение для нашего случая.

Закон Вирта.
Доступность мощного и объемного железа и погоня за скоростью разработки породило фреймворки и разбаловало уже давно. Это не код на ассемблере писать для Z80 с 48кб ОЗУ и накопителем на магнитофоне. Раньше каждый байт памяти и такт процессора были на вес золота, что заставляло разработчиков писать максимально оптимизированный код.

Почему программисты стали писать медленный код

Потому что их за это не наказывают.

То, на что обычно часто забивают при написании кода: [микро]оптимизация, документирование, следование паттернам/проработанной архитектуре, полное покрытие тестами.

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

Пока ты пишешь "красиво" , конкуренты займут эту нишу, юзеры привыкнут к "грязному" продукту (а дальше будут с азартом ждать обновлений и исправлений - снова за свои деньги).

Человек такое существо, если можно что-то не делать - делать не будет. Статья как ностальгический вздох системного программиста - «раньше трава была зеленее, а ассемблер — понятнее». Но соглашусь - иногда стоит "любить" код который пишешь, даже в разрез с задачами бизнеса.

А ещё раньше мы вообще в кодах писали!

struct Point { float x, y, z; } ;

std::vector<float> xs(10'000'000), ys(10'000'000), zs(10'000'000);

Ладно на С++ разница 3 раза. А если вы вдруг пишете такой код для CUDA и вместо double float (по 4 байта), то во втором варианте 32 точки считаются из памяти за 3 обращения, а в первом варианте - за 96! Потому что во втором варианте одновременные обращения идут рядом (xs[0..31], ys[0..31], zs[0..31]), и такие запросы к памяти объединяются, а во первом - обращения вперемешку (ps[0].x, ps[1].x, ... , ps[31].x, ps[0].y, ..., ps[0].z, ...) не объединяются, а кэша может и не быть (на старых картах).

Проблема описанная в статье, ещё вызвана тем что ни кто с нуля не станет разрабатывать.

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

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

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

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

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

Странно. Может у конкретного программиста большие проекты плохо работают?

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

Ну так проблема не в либах и не в ардуине ;)

Спасибо, интересно - есть над чем задуматься... )

Я считаю, что вся проблема в том, что программисты похожи на стадо овец. Они слепо действуют стадно. Например, считают Google образом для подражания, это же мега-корпариция крутых спецов. И стоит только кому-то сказать, что это сборище криворуких инвалидов, тебя тут же в коментах просто сотрут с лица земли. Сколько лет выходят версии Android Studio? Она как сыпала ошибками так и сыпет. Как были проблемы с рендером превьюшек для view, так и есть. Проблем сотни. Но все делаю вид, что за столько лет это норм. А это мы еще не говорим об API Android, каких-то всратых костылях и т.д. Они даже в своих приложениях не могу нормально обрабатывать отступы. Ошибка сохраняется на протяжении 8 месяцев. Но Google все еще крутые спецы.

А вы капитализацию Apple

жабаскрипт видели?

Я так и не понял что не так с первым примером тем более в статье с названием "Почему программисты стали писать медленный код". Во-первых, непонятно зачем автор предлагает анализировать на уровне CPU если там и на уровне C++ видно что большая часть времени уйдет на работу с памятью, а не на ЕДИНСТВЕННУЮ математическую операцию. Во-вторых, задача решена оптимально потому что современный компилятор С++ проведет векторизацию циклов и сделает оптимальный код с 128 битным доступом к памяти и использованием SSE регистров. Поэтому если что то в этом коде и не оптимально, так это постановка задачи, а не ее реализация программистом. И какое это имеет отношение к смыслу статьи непонятно. А приложение заметок жрет столько памяти и медленно работает не потому что кто то написал медленный код на С++, а наоборот...потому что кто то поленился написать это на С++ и использовал громозкие фреймворки на каком нибудь новомодном языке которые вместо программ на 10 строк сгенераровали 100 мегабайт кода и заняли пол гигабайта памяти.

Для начала можно просто не брать языки и их фреймворки из правкой части диаграммы =)

Языки которые нужно запретить на территории РФ
Языки которые нужно запретить на территории РФ

Во второй колонке справа...

Достаточно Купер открыть. Поиск каждого товара по полминуты. Всем пофиг - работает же. Это еще и проблема большого числа управленческих слоев Сбера.

Достаточно его и не открывать.

Плохой продукт - не пользуемся.

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

Надежность ортогональна скорости в 96% случаях

Я может не знаю как великие продукты пишутся. Обычные приложения мы оптимизируем до состояния ‘быстро’, что значит пол секунды от клика до результата. Если нам дать в 10 раз больше аппаратной скорости, мы обрадуемся и будем меньше заморачиваться с профайлером. При любом оборудовании эффективная скорость отклика пол секунды.

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

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


Вот, кстати, пример того, что может современное железо, если с умом подходить:
https://www.youtube.com/watch?v=AtCMF8nUK7E

Видимо, падение производительности в общем случае - неизбежное зло. Особенно с vibe coding. Как системным программистам, нам очевидно, что cache locality, data-oriented design, правильная многопоточность, предсказуемость ветвления исполнения, агрессивное кэширование I/O даст выдающуюся производительность. Но определенно есть обратная пропорциональная связь между Производительностью и Объемом функционала. Поэтому бизнес с легкостью пожертвует неэффективностью реализации, ради быстрой delivery фичи, пока это не мешает бизнесу. А потом, когда будет мешать, будут оптимизировать и позовут специалистов как вы. Как мне кажется.

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

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

В начале статьи приводится пример с редактором кода, жрущего полгига ОЗУ. А затем приводится пример на плюсах. Хотя казалось бы, причем тут плюсы, если этот редактор написан на джаваскрипте? Первый ошибок не прощает, второй их хавает за обе щеки. На первом кодер упрется в core dumped до того как научится делать элементарные вещи, не говоря уже о полноценном приложении, на втором может написать веб приложения по методичке вуза, не понимая львиную долю своих действий. На первом пишется интерпретатор, на втором пишется 80% всего современного goвнософта для этого интерпретатора. Потом приводится пример с питоном и упускается тот немаловажный факт, что для запуска любой программы на питоне, перед этим нужно запустить другую программу - интерпретатор. И никого не колышет, что действительной необходимости в этом вообще-то нет. И пример с несколькими слоями абстракции не совсем корректен. Разрабатывая на Си, половина слоев никуда не девается, однако остаётся возможность выжать из железа по-максимуму. Короче, на мой взгляд, автор отводит внимание от вещей, действительно убивающих софт. И обращает внимание на вещи, с которыми в целом можно мириться и не испытывать нехватку памяти и переизбыток седых волос

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации