Я хотел бы спросить людей, имеющих опыт работы с системами в масштабе Visual Studio: что делает их медленными? Являются ли слои за слоями абстракций, необходимые для сохранения кодовой базы в пределах возможностей человеческого понимания? Это огромное количество кода, которое нужно пройти? Является ли это современной тенденцией к подходам, экономящим время программистов, за (ошеломляюще огромные) расходы в отделе тактов / использования памяти?
Я думаю, что вы догадались, что некоторые из них, но я хотел бы предложить то, что я считаю самым важным фактором, работая над достаточно большой базой кода (не уверен, что он такой же большой, как Visual Studio - в миллионах строк кода) категория и около тысячи плагинов) в течение примерно 10 лет и наблюдения явления происходят.
Это также немного менее спорным, так как он не входит в API, или функции языка или что-нибудь подобное. Это относится к «затратам», которые могут породить дебаты, а не «расходы», и я хочу сосредоточиться на «расходах».
Свободная координация и наследие
Я заметил, что слабая координация и долгое наследие приводят к большому количеству накопленных отходов.
Например, я нашел около ста ускоряющих структур в этой кодовой базе, многие из которых избыточны.
У нас было бы как дерево KD для ускорения одного физического движка, другого для нового физического движка, который часто работал параллельно со старым, у нас были бы десятки реализаций октодеревьев для различных алгоритмов сетки, другое дерево KD для рендеринга , подбор и т. д. и т. д. и т. д. Это все большие, громоздкие древовидные структуры, используемые для ускорения поиска. Каждый из них может занимать сотни мегабайт в гигабайтах памяти для ввода очень среднего размера. Они не всегда создавались и использовались постоянно, но в любой момент времени 4 или 5 из них могли бы быть в памяти одновременно.
Теперь все они хранили одни и те же данные, чтобы ускорить их поиск. Вы можете представить ее как аналогичную старую аналогичную базу данных, которая хранит все свои поля в 20 различных избыточных картах / словарях / деревьях B + одновременно, одинаково организованных по одним и тем же ключам, и постоянно ищет их все. Теперь мы берем в 20 раз больше памяти и обработки.
Кроме того, из-за избыточности у нас мало времени на оптимизацию любого из них с помощью стоимости обслуживания, которая идет с этим, и даже если бы мы это сделали, это дало бы только 5% эффекта, который в идеале был бы.
Что вызывает это явление? Слабая координация была причиной номер один, которую я видел. Многие члены команды часто работают в своих изолированных экосистемах, разрабатывая или используя сторонние структуры данных, но не используя те же структуры, которые использовали другие члены команды, даже если они были откровенно вопиющими дубликатами тех же проблем.
Что заставляет это явление сохраняться? Наследие и совместимость были главной причиной, которую я видел. Поскольку мы уже заплатили за реализацию этих структур данных, а большие объемы кода зависели от этих решений, зачастую было слишком рискованно пытаться объединить их с меньшим количеством структур данных. Несмотря на то, что многие из этих структур данных были концептуально избыточными, они не всегда были похожи друг на друга в своих интерфейсах. Поэтому замена их была бы большим, рискованным изменением, а не просто позволила бы им потреблять память и время обработки.
Эффективность памяти
Обычно использование памяти и скорость, как правило, связаны, по крайней мере, с общим объемом. Вы часто можете обнаружить медленное программное обеспечение по тому, как оно загружает память. Не всегда верно, что увеличение объема памяти приводит к замедлению, поскольку важна «горячая» память (к какой памяти обращаются все время - если программа использует загрузочную память, но только 1 мегабайт используется все время, то это не так уж важно по скорости).
Таким образом, вы можете обнаружить потенциальных свиней на основе использования памяти в большинстве случаев. Если приложение при запуске занимает от десятков до сотен мегабайт памяти, оно, вероятно, не будет очень эффективным. Десятки мегабайт могут показаться маленькими, когда у нас есть гигабайты DRAM в наши дни, но самые большие и самые медленные кэши ЦП все еще находятся в мизерном диапазоне мегабайт, а самые быстрые - в диапазоне килобайт. В результате программа, которая использует 20 мегабайт только для запуска и ничего не делает, на самом деле все еще использует довольно «много» памяти с точки зрения аппаратного ЦП, особенно если все 20 мегабайт этой памяти будут доступны повторно и часто, когда программа работает.
Решение
Для меня решение состоит в том, чтобы искать более скоординированные, меньшие команды для создания продуктов, которые могут отчасти отслеживать свои «расходы» и избегать «покупки» одних и тех же товаров снова и снова и снова.
Стоимость
Я окунусь в более противоречивую сторону «затрат», чуть-чуть с феноменом «расходов», который я наблюдал. Если язык в конечном итоге приходит с неизбежным ценником для объекта (например, тот, который обеспечивает отражение во время выполнения и не может вызвать непрерывное выделение для серии объектов), этот ценник является дорогостоящим только в контексте очень гранулированного элемента, такого как одного Pixel
или Boolean
.
Тем не менее, я вижу много исходного кода для программ, которые обрабатывают большую нагрузку (например, имеют дело с сотнями тысяч до миллионов Pixel
или Boolean
экземпляров), оплачивая эти затраты на таком детальном уровне.
Объектно-ориентированное программирование может усугубить это. И все же это не стоимость «объектов» как таковых или даже ООП по вине, а просто то, что такие расходы оплачиваются на таком гранулярном уровне крошечного элемента, который будет создан миллионами.
Так что я наблюдаю другие явления «затрат» и «расходов». Стоимость - копейки, но копейки складываются, если мы покупаем миллион банок содовой по отдельности вместо того, чтобы договариваться с производителем о крупной покупке.
Решением здесь для меня является «массовая» покупка. С объектами все в порядке даже в тех языках, в которых каждый имеет цену в несколько копеек, при условии, что эта стоимость не оплачивается отдельно в миллион раз за аналогичный эквивалент банки с газировкой.
Преждевременная оптимизация
Мне никогда не нравилась формулировка Кнута, используемая здесь, потому что «преждевременная оптимизация» редко заставляет реальные производственные программы работать быстрее. Некоторые интерпретируют это как «ранняя оптимизация», когда Кнут имел в виду больше как «оптимизация без надлежащих знаний / опыта, чтобы узнать ее истинное влияние на программное обеспечение». Во всяком случае, практический эффект от истинной преждевременной оптимизации часто собирается сделать программное обеспечение медленнее , так как ухудшение ремонтопригодности средств есть немного времени , чтобы оптимизировать критические пути , которые действительно имеет значения .
Это последнее явление, которое я наблюдал, когда разработчики, стремящиеся сэкономить копейки на покупке одной банки содовой, которую никогда больше не купят, или, что еще хуже, дом, тратили все свое время, зажимая копейки (или, что еще хуже, мнимые копейки из неспособность понять их компилятор или архитектуру аппаратного обеспечения), когда миллиарды долларов были потрачены впустую в другом месте.
Время очень ограничено, поэтому попытка оптимизировать абсолюты без надлежащей контекстной информации часто лишает нас возможности оптимизировать места, которые действительно важны, и, таким образом, с точки зрения практического эффекта, я бы сказал, что «преждевременная оптимизация делает программное обеспечение намного медленнее. "
Проблема в том, что существуют типы разработчиков, которые возьмут то, что я написал выше об объектах, и попытаются установить стандарт кодирования, который запрещает объектно-ориентированное программирование или что-то в этом роде. Эффективная оптимизация - это эффективная расстановка приоритетов, и она абсолютно бесполезна, если мы тонем в море проблем с техническим обслуживанием.