Недавно я прочитал в статье статью о программировании игр, написанную в 1996 году, что использование глобальных переменных быстрее, чем передача параметров.
Было ли это правдиво, и если да, то это все еще верно сегодня?
Недавно я прочитал в статье статью о программировании игр, написанную в 1996 году, что использование глобальных переменных быстрее, чем передача параметров.
Было ли это правдиво, и если да, то это все еще верно сегодня?
Короткий ответ - Нет, хорошие программисты делают код быстрее, зная и используя соответствующие инструменты для задания, а затем оптимизируя методичным способом, когда их код не отвечает их требованиям.
Более длинный ответ. Эта статья, которая, на мой взгляд, не особенно хорошо написана, ни в коем случае не является общим советом по ускорению программы, но "15 способов делать быстрые блики". Экстраполируя это на общий случай, отсутствует точка писателя, независимо от того, что вы думаете о достоинствах статьи.
Если бы я искал рекомендации по эффективности, я бы поставил нулевое доверие в статье, которая не идентифицирует или не показывает одно изменение кода конкретного, чтобы поддерживать утверждения в примере кода, и не предполагая, что измерение кода может быть хорошей идеей. Если вы не собираетесь показывать, как сделать код лучше, зачем его включать?
Некоторые из советов - устаревшие годы - указатели FAR перестали быть проблемой на ПК давным-давно.
Серьезный разработчик игр (или любой другой профессиональный программист, если на то пошло) будет хорошо рассмеяться о таких советах:
Вы можете либо вынуть assert полностью, или вы можете просто добавить
#define NDEBUG
при компиляции окончательной версии.
Мой совет вам, если вы действительно хотите оценить достоинства любого из этих 15 советов, а так как статье 14 лет, было бы скомпилировать код в современном компиляторе (см. Visual С++ 10) и попробовать для идентификации любой области, где использование глобальной переменной (или любой другой подсказки) сделает ее быстрее.
[Просто шутить - мой реальный совет состоял бы в том, чтобы полностью игнорировать эту статью и задавать вопросы о производительности конкретных в Stack Overflow, когда вы нажимаете на проблемы в своей работе, которые вы не можете решить. Таким образом, полученные вами ответы будут проверяться экспертами, поддерживаться примером кода или хорошими внешними доказательствами и текущими.]
При переключении с параметров на глобальные переменные может произойти одна из трех:
Вам нужно будет измерить производительность, чтобы увидеть, что быстрее в нетривиальном конкретном случае. Это было верно в 1996 году, сегодня это правда, и это правда завтра.
Оставляя на мгновение производительность, глобальные переменные в большом проекте вводят зависимости, которые почти всегда делают техническое обслуживание и тестирование намного сложнее.
При попытке найти законное использование переменных глобалов по соображениям производительности сегодня я очень согласен с примерами в ответе Preet: очень часто требуемые переменные в программах или устройстве микроконтроллера водители. Крайним случаем является регистр процессора, который предназначен исключительно для глобальной переменной.
При анализе производительности глобальных переменных по сравнению с передачей параметров актуальным является способ, которым компилятор реализует их. Глобальные переменные обычно хранятся в фиксированных местах. Иногда компилятор генерирует прямую адресацию для доступа к глобальным переменным. Иногда, однако, компилятор использует еще одну косвенность и использует таблицу символов для глобальных переменных. IIRC gcc для AIX сделал это 15 лет назад. В этой среде глобалы малых типов всегда были медленнее, чем локальные и передаваемые параметры.
С другой стороны, компилятор может передавать параметры, нажимая их в стек, передавая их в регистры или их смесь.
Все уже дали соответствующие предостережения в отношении того, что это определенная платформа и программа, нужно на самом деле измерять тайминги и т.д. Таким образом, с учетом сказанного уже позвольте мне ответить на ваш вопрос напрямую по конкретному случаю игрового программирования на x86 и PowerPC.
В 1996 году были определенные случаи, когда нажатие параметров в стеке требовало дополнительных инструкций и могло привести к кратковременному срыву внутри конвейера процессора Intel. В таких случаях может произойти очень небольшое ускорение из-за того, что можно избежать передачи параметров вообще и чтения данных из буквенных адресов.
Это больше не относится к x86 или PowerPC, используемому в большинстве игровых консолей. Использование глобальных переменных обычно медленнее, чем передача параметров по двум причинам:
Что значит "быстрее"?
Я знаю, что понимание программы с глобальными переменными занимает у меня намного больше времени, чем без.
Если дополнительное время, затрачиваемое программистом (-ами) меньше времени, получаемого пользователями при запуске программы с помощью глобальных переменных, то я бы сказал, что использование global выполняется быстрее.
Но учтите, что программа будет работать 10 человек один раз в день в течение 2 лет. И что он занимает 2,84632 сек без глобалов и 2.84217 сек с глобалями (увеличение 0.00415 сек). Это на 727 секунд меньше TOTAL. Получение 10 минут времени работы не стоит введения глобального в отношении времени программиста.
До степени любой код, который избегает инструкций процессора (т.е. более короткий код), будет быстрее. Однако насколько быстрее? Не очень! Также обратите внимание, что стратегии оптимизации компилятора могут в любом случае привести к меньшему коду.
В наши дни это всего лишь оптимизация для очень специфических приложений, как правило, у сверхсрочных критических драйверов или кода микроконтроля.
Устраняя проблемы ремонтопригодности и правильности, в основном существуют два фактора, которые будут влиять на производительность в отношении глобальных переменных и параметров.
Когда вы делаете глобальный, вы избегаете копии. Это немного быстрее. Когда вы передаете параметр по значению, его необходимо скопировать, чтобы функция могла работать на локальной копии и не повредить копию данных вызывающего абонента. По крайней мере, в теории. Некоторые современные оптимизаторы делают довольно сложные вещи, если они идентифицируют, что ваш код хорошо себя ведет. Функция может автоматически включаться, и компилятор может заметить, что функция ничего не делает с параметрами и просто оптимизирует любое копирование.
Когда вы делаете глобальный, вы врежете кешу. Когда у вас есть все ваши переменные, аккуратно содержащиеся в вашей функции, и несколько параметров, данные будут иметь тенденцию быть в одном месте. Некоторые из переменных будут в регистрах, и некоторые из них, вероятно, будут в кеше сразу, потому что они правы "рядом" друг с другом. Использование большого количества глобальных переменных в основном является патологическим поведением для кеша. Нет никакой гарантии, что различные глобальные переменные будут использоваться одними и теми же функциями. Местоположение не имеет очевидной корреляции с использованием. Возможно, у вас достаточно небольшой рабочий набор, который не имеет значения, где что-либо есть, и все это заканчивается в кеше.
Все это просто добавляет до точки, сделанной плакатом над мной:
При переключении с параметров на глобальные переменные, одна из трех вещей может случиться:
* it runs faster * it runs the same * it runs slower
Вам нужно будет измерить производительность видеть, что быстрее в нетривиальном конкретный корпус. Это было в 1996 году, истинно сегодня, и это правда завтра.
В зависимости от конкретного поведения вашего точного компилятора и точных деталей аппаратного обеспечения, которое вы используете для запуска вашего кода, возможно, что в некоторых случаях глобальные переменные могут быть очень слабыми. Возможно, эта возможность стоит попробовать на каком-то коде, который работает слишком медленно, как эксперимент. Это, вероятно, не стоит посвящать себя, поскольку ответ на ваш эксперимент может измениться завтра. Итак, правильный ответ - это почти всегда идти с "правильными" шаблонами проектирования и избегать уродливого дизайна. Ищите лучшие алгоритмы, более эффективные структуры данных и т.д., Прежде чем намеренно пытаться спагеттизировать ваш проект. Гораздо лучше выигрыш в долгосрочной перспективе.
И, помимо аргумента time time time vs user time, я добавлю аргумент time time time time time to the Moore. Если вы предполагаете, что закон Мура сделает компьютеры примерно наполовину такими же быстрыми с каждым годом, то ради простого круглого числа мы можем предположить, что прогресс происходит в устойчивом 1% -ном прогрессе в неделю. ЕСЛИ вы смотрите на микрооптимизацию, которая может улучшить такие вещи, как 1%, и это добавит неделю к проекту из-за усложнения вещей, тогда просто удержание недели будет иметь тот же эффект в среднем времени выполнения для ваших пользователей.
Возможно, микро-оптимизация и, вероятно, будет устранена оптимизациями, которые ваш компилятор мог бы генерировать без применения таких методов. Фактически использование глобалов может даже препятствовать некоторым оптимизации компилятора. Надежный и обслуживаемый код, как правило, имеет большую ценность, и глобальные переменные не способствуют этому.
Использование глобальных переменных для замены параметров функции делает все такие функции неретеррантными, что может быть проблемой при использовании многопоточности, а не в обычной практике разработки игр в 1996 году, но более распространенной с появлением многоядерных процессоров, Это также исключает рекурсию, хотя это, вероятно, менее проблематично, поскольку рекурсия имеет свои проблемы.
В любом значительном тексте кода, вероятно, будет больше пробега в оптимизации алгоритмов и структур данных более высокого уровня. Кроме того, есть варианты, открытые для вас, кроме глобальных переменных, которые не позволяют передавать параметры, в особенности переменные класса С++.
Если привычное использование глобальных переменных в вашем коде делает измеримую или полезную разницу с его производительностью, я сначала задал вопрос дизайну.
Для обсуждения проблем, присущих глобальным переменным, и некоторых способов их избежать, см. "Покс на глобалях" Джона Ганнсле. Статья относится к разработке встроенных систем, но в целом применима; это то, что некоторые разработчики встроенных систем считают, что у них есть все основания использовать глобальные переменные, возможно, по тем же причинам, которые были ошибочными, чтобы оправдать его в разработке игр.
Хорошо, если вы планируете использовать глобальные параметры вместо передачи параметров, это может означать, что у вас есть длинная цепочка методов/функций, которые вы должны передать этому параметру. Это так, вы действительно будете экономить циклы процессора, переключаясь с параметра на глобальную.
Итак, ребята, которые говорят, что это зависит, я думаю, что они просто ошибаются. Даже при передаче параметра REGISTER все еще будут БОЛЬШЕ циклов процессора и БОЛЬШИХ накладных расходов для нажатия параметров до вызываемого абонента.
ОДНАКО - Я никогда этого не делаю. Процессоры сейчас превосходят, и временами, когда это могло быть проблемой, было 12Mhz 8086s. В настоящее время, если вы не пишете встроенный или супер-турбозарядный код производительности, придерживайтесь того, что хорошо выглядит в коде, что не нарушает логику кода и процветает, чтобы быть модульным.
И, наконец, оставьте генерацию кода машинного языка компилятору - ребята, которые его разработали, лучше всего знают, как работает их ребенок, и сделают ваш код в лучшем виде.
В целом (но это может сильно зависеть от реализации компилятора и платформы), передача параметров означает запись их в стек, который вам не понадобится с глобальной переменной.
Тем не менее, глобальная переменная может означать включение обновления страницы в MMU или контроллер памяти, тогда как стек может быть расположен в гораздо более быстрой памяти, доступной процессору...
Извините, нет хорошего ответа для такого общего вопроса, просто измерьте его (и попробуйте разные сценарии тоже)
Это было быстрее, когда у нас были процессоры < 100 МГц. Теперь, когда эти процессоры в 100 раз быстрее, эта "проблема" на 100 раз меньше. Тогда это было неважно, это было очень важно, когда вы делали это в сборке и не имели (хорошего) оптимизатора.
Говорит парень, запрограммированный на 3-МГц процессоре. Да, вы читали, что права и 64k недостаточно.
Я вижу много теоретических ответов, но никаких практических советов для вашего сценария. Я предполагаю, что у вас есть большое количество параметров для передачи по нескольким вызовам функций, и вы беспокоитесь о накопленных накладных расходах со многих уровней кадров вызовов и многих параметров на каждом уровне. В противном случае ваша озабоченность совершенно необоснованна.
Если это ваш сценарий, вы должны, вероятно, поместить все параметры в структуру "context" и передать указатель на эту структуру. Это обеспечит локальность данных и сделает так, чтобы вам не приходилось передавать более одного аргумента (указателя) при каждом вызове функции.
Доступ к этим параметрам немного дороже, чем истинные аргументы функции (вам нужен дополнительный регистр для удержания указателя на базу структуры, в отличие от указателя кадра, который будет служить этой цели с аргументами функции), и по отдельности (но, возможно, не с эффектами кеша, которые были учтены) более дорогими, чем глобальные переменные в обычном, не-ПОС-коде. Однако, если ваш код находится в общей библиотеке /DLL, используя независимый по позиции код, стоимость доступа к параметрам, переданным указателем на структуру, дешевле, чем доступ к глобальную переменную и идентичную доступу к статическим переменным, из-за GOT и GOT-относительной адресации. Это еще одна причина, по которой никогда не использовать глобальные переменные для передачи параметров: если вы, возможно, в конечном итоге поместите свой код в общую библиотеку /DLL, любые возможные преимущества в производительности внезапно обернутся!
Как и все остальное: да и нет. Нет никакого ответа, потому что это зависит от контекста.
контрапунктов:
Представьте себе программирование на Itanium, где у вас есть сотни регистров. Вы можете поместить довольно много глобалов в те, которые будут быстрее, чем типичный способ реализации глобальных переменных на C (некоторый статический адрес (хотя они могут просто жестко задавать глобальные переменные в инструкциях, если они являются длиной слова)). Даже если глобальные значения находятся в кеше все время, регистры все еще могут быть быстрее.
В Java чрезмерное использование глобальных переменных (статика) может снизить производительность из-за блокировок инициализации, которые необходимо выполнить. Если 10 классов хотят получить доступ к некоторому статическому классу, все они должны ждать, пока этот класс завершит инициализацию своих статических полей, которые могут занять время от времени до бесконечности.
В любом случае глобальное состояние - это просто плохая практика, это повышает сложность кода. Хорошо разработанный код, естественно, достаточно быстрый 99,9% времени. Похоже, что новые языки удаляют глобальное состояние вместе. E удаляет глобальное состояние, поскольку оно нарушает их модель безопасности. Haskell удаляет состояние все вместе. Тот факт, что Haskell существует и имеет реализации, превосходящие большинство других языков, является достаточным доказательством для меня, что я больше никогда не буду использовать глобальные переменные.
Кроме того, в ближайшем будущем, когда у всех нас есть сотни ядер, глобальное состояние действительно не поможет.
В некоторых случаях это может быть правдой. Глобальная переменная может быть такой же быстрой, как указатель на переменную, где ее указатель хранится в/только через регистры. Итак, это вопрос о количестве регистров, которые вы можете использовать.
Чтобы оптимизировать вызов функции, вы могли бы сделать несколько других вещей, которые могли бы улучшиться с помощью глобальных переменных-хаков:
Я нашел этот "обзор атрибутов gcc": http://www.ohse.de/uwe/articles/gcc-attributes.html
и я могу дать вам эти теги для googling: - Правильный хвостовой вызов (в основном это относится к императивным основам функциональных языков) - TABLES_NEXT_TO_CODE (в основном это относится к Haskell и LLVM)
Но у вас есть "код спагетти", когда вы часто используете глобальные переменные.