Как стать автором
Обновить

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

Программист вдруг обнаружил, что сотни тысяч вызовов sscanf() в цикле — это не очень быстро? Или о чем статья?

Вызов любой функции сотни тысяч раз в цикле не очень быстро (все конечно зависит от того, что такое "быстро"). Статья о том, что глядя на этот код вовсе не подозреваешь, что здесь что-то не так. В самом деле, нафига вам знать длину строки, чтобы определить, где конец float? Думаю, чтение документации на cppreference могло бы предостеречь, но


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

Но функция анализа и разбора строки — это же вроде бы очевидно, что прям совсем не быстро :) Это же не функция min().
Статья о том, что глядя на этот код вовсе не подозреваешь, что здесь что-то не так.

Весьма спорно. Для меня, например, очевидно, что разбор текстового файла в сотни тысяч строк с вызовом для каждой строки по три раза sscanf() — это пипец как не быстро.
Кроме того: strlen(VERTEX_STR); — серьезно? Каждый раз вызов strlen() для одной и той же константной строки? У меня серьезные сомнения в том, что квалификация этого программиста позволяет ему писать статьи-наставления :)

Каждый раз вызов strlen() для одной и той же константной строки

А компилятор не умеет такое оптимизировать? Или он не знает, что strlen чистая функция?

Да, согласно godbolt'у в свежих версиях самых распространённых компиляторов при полной оптимизации просто пишет число 7 в регистр. А вот все остальные вызовы остаются на своих местах.

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

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

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

Рискую нарваться на минусы, но: Нет, нельзя было


Поясняю:


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

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


Но, допустим, что задача была заведена 3 года назад, валялась минором, пришел новый программист, и чтобы ввести его в курс дел — решили на пару недель его отправить на фикс миноров: и с кодом немного познакомится, и с процессами в компании. И вот берет он эту задачу, тратит Х часов, находит и исправляет проблему, проходит ревью, и вот наступил момент — надо решить, в какой из следующих релизов пойдет задача. В ближайший через 1.5 месяца? Нее, там уже код фриз, только крит баги можно вливать. Во второй следующий? Нет-нет, там скоуп уже сформирован, выделены QA и perf-QA, бОльший скоуп они взять не смогут. В третий следующий? Погоди-ка, ты поменял функцию parse_json, так? Но она же еще используется при загрузке метаданных, и при подгрузке списка файлов обновления, и еще в 4 местах. Это надо все проверить, регресс тесты, перф тесты… В тот релиз скорее всего не получится, там будет огромный апдейт, добавляются 5 новых островов, будет не до того. Вот, в 6й следующий релиз будет самое оно, через год и 8 месяцев, там пока не сильно много фичей запланировано.
(спустя два года)
Задача перенеслась еще на 4 версии вперед, но однажды её точно зарелизят...

В третий следующий? Погоди-ка, ты поменял функцию parse_json, так? Но она же еще используется при загрузке метаданных, и при подгрузке списка файлов обновления, и еще в 4 местах. Это надо все проверить, регресс тесты, перф тесты… В тот релиз скорее всего не получится, там будет огромный апдейт, добавляются 5 новых островов, будет не до того.

Отличный пример, почему feature-based релизы толстых проектов проигрывают time-based релизам :) В time-based, какое из подмножества 100500 правок собралось в те 105, которые попали в конкретную версию, уже не так важно — прогресс идёт своим чередом.


А обновления игры это отличный случай как раз для time-based.

Да ладно бы игроки, как сами разработчики жили с таким, что запуск продукта минуты занимает? Бывало оптимизировать даже дебаг сборку приходится, хотя в релизе проблем нету. Потому что это же тормозит разработку, тестирование, да всё на свете.

Иногда бывает очень познавательно скомпилировать программу разными компиляторами и посмотреть на полученный машинный код. Ведь не просто так получилось, что у нас есть несколько компиляторов, а не один.
cерьезно? Каждый раз вызов strlen() для одной и той же константной строки?

А что такого? Компилятор даже при отключенных оптимизациях заменяет подобный вызов на константу. В данном случае 7.
вы может и удивитесь, но даже применение объектов причём якобы быстрых [например структур] — это на самом деле не быстро и нафиг не нужно
максимальная быстрота достигается когда вообще убран всякий оверхед
например у меня есть статическая бд со строками коих там я даже не знаю сколько, десятки миллионов — порядок такой
так вот на шарпе [внезапно!] написан коннектор до этой бд, оттуда дёргаются указатели [внезапно!] а не какие то нахрен не нужные строчки, и даже не массивы, потому что это тоже оверхед
всё потому что эти строчки далее в кол-ве 100-300 шт. комбинируются уже в итоговые строчки, которые как раз таки и нужны на выходе
понятное дело что если тягать всё это на указателях и смещениях без объектов и по сути рулить указателями и собирать через это всё дело итоговые наборы байт — то работать оно будет астрономически быстро
а оно так и работает))) 300 миллионов обращений в мою базу и сборка 10 миллионов строк происходит за секунду на одном ядре
я и память этой базы расшарил если что, несколько коннекторов пользуются всем этим делом враз
причём сам коннектор в общем то тривиален, а вот сборка самих баз для коннектора — это ужас и кошмар, т.е. например 50 таблиц работающие исключительно на смещениях друг от друга это полнейшая жесть, в реализации оного вы погрязните навсегда
такие дела и это реальность
ну а вообще — это всего лишь подготовительный этап для создания ещё более забавных бд, где строк вообще нет) я там буду юзать абстракции, которые являются словоформами из зализняка, однако же это всё в итоге как то в строки превращать нужно и очень быстро, потому и озадачились в общем то тривиальной проблемкой, т.е. как из базы где условный десяток миллионов подстрок собрать строку и эта строка состоит ну например из 100 тыщ этих подстрок из базы
п.с.: не продаётся ни за какие деньги, даже за очень очень страшные деньги, НЕА
Статья о том, что глядя на этот код вовсе не подозреваешь, что здесь что-то не так.


Ну у меня сразу работает ассоциация слова «парсер» с parser/lexer generators, parser combinators. Слова «ASCII» и «97 Mb» — сразу намёк на быстрые генераторы, в частности Ragel, а если и тут скорости не хватает, надо читать статьи про самописные парсеры.

А функции на C работают с локалями => есть хорошие шансы просесть по скорости + получить скрытую проблему когда локаль, скажем, русская с "," вместо ".".

— Я склонен полагать, что просто это место не было когда-то узким. Ну а сейчас, с оптимизацией других мест, этот парсер стал достаточно критичным по скорости. Вообще программа адски быстрая, судя по статье, а формат лишь один из многих поддерживаемых.
А функции на C работают с локалями => есть хорошие шансы просесть по скорости + получить скрытую проблему когда локаль, скажем, русская с "," вместо ".".

Если локализация явно не активирована чем-то вроде setlocale(LC_ALL, "") — то таких проблем не будет.


Но, да, есть проблема принципиальная: нельзя уточнить локаль для конкретной операции. (ios_base::imbue конечно поможет, но тут другой оверхед.)

Вызов любой функции сотни тысяч раз в цикле не очень быстро (все конечно зависит от того, что такое "быстро").

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


Какой-нибудь специализированный strtof здесь явно удобнее. Просто странно — если программа пишется на расслабоне, с формат-строками, то откуда претензии к скорости? А если заморочились с оптимизацией, руками прописан поиск ключевых слов, все эти SKIP_WHILE(isspace), и т.п. — то и чтение float'а должно быть оптимизированным. И strlen(VERTEX_STR) я бы в константу вынес, от греха.

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

Ну слушайте, если бы вам было 20 лет — вы бы эти статьи читали как детектив.
Было бы 30 — "ну да, надо бы проверить свой код, а заодно на ревью обращать внимание на такое".
Но вам за 40, и такие статьи для вас как "небо голубое".
Оставьте возможность двадцатилетним — учиться, а тридцатилетним — не забывать, что они еще с очень многими вещами не сталкивались =)

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

Вы так говорите, как будто никогда не работали в режими аврала.
И вы точно не работали в геймдеве. Если такое случится — вы точно не будете больше никогда оставлять такие комментарии.

Проблема известна минимум два года, пруф:
www.reddit.com/r/gtaonline/comments/9vgo0g/how_the_fuck_are_20_minute_load_times_acceptable
За это время не удосужились выделить несколько часов на исправление? А самим разработчикам интересно каждый раз втыкать по 10 минут на лоадинг скрин?
ждем статью расследование: как я заменил std::vector на std::list для ускорения программы в 1000 раз!

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

возможность ошибиться и научиться новому — у них появится только после своей ошибки либо после такой статьи
мда, я смотрю тяжело быть программистом на плюсах
Может и «data += strlen(VERTEX_STR);» заменить стоит?
По уму, раз уж VERTEX_STR задекларирован как массив, намного лучше использовать sizeof(VERTEX_STR)-1 в этом месте. А иначе вообще нет никакого смысла декларировать его как массив, только лишние расходы на инициализацию.
Есть хорошее правило: не знает как работает — не используешь.
Его далеко не всегда можно соблюдать, но в случае работы со строками в С — уж точно обязательно к исполнению.
Как минимум у любого программиста работающего со строками должен быть звоночек после осознания что strlen проходит по всей строке для выдачи результата.

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

Здесь не нужен ещё один человек. Достаточно приучить себя смотреть в исходники используемых функций.

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

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

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

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

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

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

Кстати, недавно смотрел исходники функции itoa для стандартной библиотеки языка C под андроид — android.googlesource.com/kernel/lk/+/qcom-dima-8x74-fixes/lib/libc/itoa.c

И меня смутил такой момент: из функции itoa вызывается функция strrev, внутри которой вызывается strlen, хотя внутри вызывающией функции itoa длина строки известна. Получается не такая уж оптимальная библиотека?
Вообще странно — упор на скорость, 100 метровые файлы, а парсер не на конечных автоматах.

Давно же есть генераторы лексеров/парсеров. Какой-нибудь Ragel в одном из режимов сделает такую лапшу из goto, которая раза в 2 будет быстрее чего угодно. Но в любом случае, никакой flex не сделает там ничего квадратичного и дикого.

Парсер ручной работы, так сказать

Где-то пробегала статья, о том, что вручную, с помощью intrinsics, у мастеров получаются очень быстрые парсеры, затыкающие генераторы за пояс. В частности, для разбора JSON. Но тут явно не тот случай. То есть, кмк, переход на flex сильно бы ускорил разбор.

С другой стороны, как я понял, текущая скорость вполне устраивает, поэтому цель достигнута без привлечения доп. инструментария. Единственное НО — это перед всасыванием этого ASCII нужно сбросить локаль в «C», иначе пойдут ошибки в русской локали, например.
100 метровые файлы у него бинарные. А ASCII файл, про который речь в статье, судя по тому, что в нем 7982 треугольников, занимает всего несколько десятков килобайт.
Спасибо. Значит неправильно понял. Кстати, я глянул на этот кусок — его радикально переписали.

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

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

Ага, "профайлер" оно зовётся. Только вот проблемки какие -


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


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


3) Многие профайлеры показывают, например, 80% времени в karuka(), а пришло туда управление по пути foo() -> buka() -> do_buka() -> karuka() или bar() -> zuka() -> zuka_real() -> karuka_wrapper() -> karuka(), вы будете вычислять долго и нудно. Ибо сохранить значение регистра PC (aka IP aka IAR...) — одно, а сохранить полный трейс — другое.


4) Профайлер может врать, временами жестоко. Вот актуальный пример: у меня есть питоновский нагруженный демон с логгингом через log4cplus. Так вот — профайлинг демона целиком показывает, что логгинг занимает ~10%. А профайлинг только логгинга без всего остального показывает абсолютные затраты раз в 100 меньше.


5) Как обобщение предыдущего: профилировать бутерброд из нескольких языков (например, C внизу, C# наверху, а в нём интерпретатор скриптов на Prolog, которым делают хуки) — крайне сложно, если вообще возможно (как увязать выполнение кода на C с конкретным местом в скрипте?)


И это я не вспоминаю, что что-то происходит только в продуктине, а ещё надо попытаться повторить это в пробирке...


Ну и наконец 6) просто постановить, что что-то надо проверить на оптимальность именно профилированием, и провести это через начальство — может быть неподъёмной задачей.


В общем, ваш сарказм понятен, но, мягко говоря, преждевременен.

Ну такое. Если у вас нет профайлера то оптимизация програм больше чем 10к строк(условно) будет почти невозможной задачей.
Из опыта — оптимизировал программу которая делает интернет запросы — самое узкое место расшыфровка https cертификата в библиотеке которая в другой библиотеке — как мне эту проблему без профайлера искать?

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


Но вот утверждать, что профайлер устраняет всё шаманство — явный перебор. Он может хорошо сократить набор тех мест, где таки надо шаманить :)

Ошибку Rockstar может совершить каждый

Ошибка рокстар не в том, что её разраб плохо распарсил json. Их ошибка — в игнорировании фидбэка от кастомеров. Понадобилось несколько лет багрепортов и левый человека с отладчиком чтобы исправить проблему, которая должна была быть починена в течении двух недель после первого репорта. Сколько еще лет должно пройти чтоб они починили например баг с рандомным сбросом прогресса в criminal mastermind challange — непонятно.
Ну это просто минимизация расходов.
А ведь ошибка Rockstar совсем не в этом… Почему у них никто не задался вопросом: «а почему на 4-х ядрёной машине в какой-то момент пыхтит только 1 ядро?» Ну, как пыхтит, скорее вейпит на крыльце.
Сразу написать идеальный код большого объема нельзя. Даже крупную блок-схему сразу в «финалку» не получится. Плохо, когда есть возможность существенного улучшения продукта за незначительные усилия, но перекрывается «и так сойдёт» («и так приносит прибыль» и т.д. и т.п.). И пример Rockstar очень показательный, когда жадность зашкаливает. Та же проблема с читерами. Да там есть простое решение, но это ж деньги! Аж 0,1%!
Это мы просто в капиталистическом мире живём — его особенности. Вообще капитализм — это распределённый «жадный» алгоритм управления обществом (если смотреть с точки программиста)
Комми минус поставил тупо
Не каждый, поскольку не каждый из них работает в Rockstar
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.