Я говорю о том, как мы пишем простые подпрограммы, чтобы повысить производительность, не делая трудным для чтения ваш код ... например, это типично для того, что мы узнали:
for(int i = 0; i < collection.length(); i++ ){
// stuff here
}
Но я обычно делаю это, когда foreach
не применимо:
for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}
Я думаю, что это лучший подход, так как length
метод будет вызываться только один раз ... моя девушка говорит, что он загадочный. Есть ли другой простой трюк, который вы используете в своих собственных разработках?
code-quality
performance
Cristian
источник
источник
Ответы:
вставить лекцию о преждевременном обсуждении "корня зла"
Тем не менее, вот некоторые привычки, которые я приобрел, чтобы избежать ненужной эффективности, а в некоторых случаях сделать мой код более простым и более правильным.
Это не обсуждение общих принципов, а о некоторых вещах, о которых следует помнить, чтобы избежать внесения ненужных недостатков в код.
Знай своего биг-о
Это, вероятно, следует объединить с длительным обсуждением выше. Весьма здравый смысл, что цикл внутри цикла, где внутренний цикл повторяет вычисления, будет медленнее. Например:
Это займет ужасное количество времени, если строка действительно длинная, потому что длина пересчитывается на каждой итерации цикла. Обратите внимание, что GCC фактически оптимизирует этот случай, потому что
strlen()
помечен как чистая функция.При сортировке миллиона 32-битных целых чисел пузырьковая сортировка была бы неправильным способом . В общем, сортировка может быть выполнена за O (n * log n) (или лучше, в случае радикальной сортировки), поэтому, если вы не знаете, что ваши данные будут небольшими, ищите алгоритм, который по крайней мере O (n * войти n).
Аналогично, при работе с базами данных следует помнить об индексах. Если у вас
SELECT * FROM people WHERE age = 20
и у вас нет индекса по людям (возрасту), для этого потребуется последовательное сканирование O (n), а не намного более быстрое сканирование индекса O (log n).Целочисленная арифметическая иерархия
При программировании на C имейте в виду, что некоторые арифметические операции стоят дороже, чем другие. Для целых чисел иерархия выглядит примерно так (сначала дешевле):
+ - ~ & | ^
<< >>
*
/
Конечно, компилятор обычно Оптимизировать вещи , как
n / 2
кn >> 1
автоматически , если вы ориентируетесь на основной компьютер, но если вы ориентируетесь встроенное устройство, вы можете не получить такую роскошь.Также
% 2
и& 1
имеют разную семантику. Деление и модуль обычно округляются до нуля, но его реализация определена. Хорошо>>
и&
всегда округляет к отрицательной бесконечности, что (на мой взгляд) имеет гораздо больше смысла. Например, на моем компьютере:Следовательно, используйте то, что имеет смысл. Не думайте, что вы хороший мальчик,
% 2
когда используете, когда изначально собирались писать& 1
.Дорогие операции с плавающей точкой
Избегайте тяжелых операций с плавающей запятой, таких как
pow()
иlog()
в коде, который в них действительно не нуждается, особенно при работе с целыми числами. Взять, к примеру, чтение числа:Мало того, что это использование
pow()
(и преобразованияint
<->,double
необходимые для его использования) довольно дорого, но оно создает возможность для потери точности (кстати, код выше не имеет проблем с точностью). Вот почему я морщусь, когда вижу, что этот тип функций используется в нематематическом контексте.Также обратите внимание, что приведенный ниже «умный» алгоритм, который умножается на 10 на каждой итерации, на самом деле является более кратким, чем приведенный выше код:
источник
strlen()
проверяет строку, на которую указывает аргумент указателя, то есть она не может быть константой Кроме того,strlen()
действительно отмечен как чистый в glibcstring.h
pure
или,const
и даже задокументировал их в заголовочном файле из-за тонкой разницы между ними. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.htmlИз вашего вопроса и цепочки комментариев кажется, что вы «думаете», что это изменение кода повышает производительность, но вы на самом деле не знаете, так ли это или нет.
Я поклонник философии Кента Бека :
Моя методика повышения производительности кода - сначала получить код, прошедший модульные тесты и хорошо проанализированный, а затем (особенно для циклических операций) написать модульный тест, который проверяет производительность, а затем реорганизовать код или подумать о другом алгоритме, если тот выбранный не работает, как ожидалось.
Например, для проверки скорости с помощью кода .NET я использую атрибут Timeout от NUnit, чтобы записать утверждения, что вызов определенного метода будет выполнен в течение определенного периода времени.
Используя что-то вроде атрибута timeout NUnit с примером кода, который вы дали (и большое количество итераций для цикла), вы могли фактически доказать, действительно ли ваше «улучшение» кода действительно помогло с выполнением этого цикла.
Один отказ от ответственности: хотя это эффективно на «микро» уровне, это, конечно, не единственный способ тестирования производительности и не учитывает проблемы, которые могут возникнуть на «макро» уровне - но это хорошее начало.
источник
Имейте в виду, что ваш компилятор вполне может включить:
в:
или что-то подобное, если
collection
это не изменилось в цикле.Если этот код находится в критической по времени части вашего приложения, было бы полезно выяснить, так ли это на самом деле или нет, и действительно ли вы можете изменить параметры компилятора, чтобы сделать это.
Это сохранит удобочитаемость кода (как и ожидалось увидеть большинство людей), и в то же время обеспечит вам несколько дополнительных машинных циклов. Тогда вы можете сосредоточиться на других областях, где компилятор не может вам помочь.
На заметку: если вы изменяете
collection
внутри цикла, добавляя или удаляя элементы (да, я знаю, что это плохая идея, но это происходит), тогда ваш второй пример либо не будет зацикливаться на всех элементах, либо попытается получить доступ к прошлым конец массива.источник
Этот вид оптимизации обычно не рекомендуется. Эта часть оптимизации может быть легко выполнена компилятором, вы работаете с языком программирования более высокого уровня, а не с ассемблером, так что думайте на одном уровне.
источник
Это может не относиться к программированию общего назначения, но в настоящее время я занимаюсь в основном встроенной разработкой. У нас есть конкретный целевой процессор (который не собирается работать быстрее - он будет казаться странно устаревшим к тому времени, когда они выйдут из системы через 20 с лишним лет), и очень ограниченные сроки синхронизации для большей части кода. Процессор, как и все процессоры, имеет определенные особенности, касающиеся быстрых или медленных операций.
У нас есть методика, используемая для обеспечения того, чтобы мы генерировали наиболее эффективный код при сохранении читабельности для всей команды. В тех местах, где наиболее естественная языковая конструкция не генерирует наиболее эффективный код, мы создали макросы, которые гарантируют использование оптимального кода. Если мы сделаем дополнительный проект для другого процессора, мы можем обновить макросы для оптимального метода на этом процессоре.
В качестве конкретного примера, для нашего текущего процессора ветки очищают конвейер, останавливая процессор на 8 циклов. Компилятор принимает этот код:
и превращает его в эквивалент сборки
Это займет либо 3 цикла, либо 10, если он перепрыгнет
isReady=1;
. Но в процессоре естьmax
инструкция с одним циклом , поэтому гораздо лучше написать код для генерации этой последовательности, которая гарантированно всегда занимает 3 цикла:Очевидно, что цель здесь менее ясна, чем оригинал. Итак, мы создали макрос, который мы используем всякий раз, когда мы хотим логическое сравнение Greater-Than:
Мы можем сделать аналогичные вещи для других сравнений. Для постороннего код немного менее читабелен, чем если бы мы использовали только естественную конструкцию. Однако это быстро становится понятным, если потратить немного времени на работу с кодом, и это гораздо лучше, чем позволить каждому программисту экспериментировать со своими методами оптимизации.
источник
Ну, первый совет - избегать таких преждевременных оптимизаций, пока вы не будете точно знать, что происходит с кодом, чтобы вы были уверены, что на самом деле делаете это быстрее, а не медленнее.
Например, в C # компилятор оптимизирует код, если вы зацикливаете длину массива, поскольку он знает, что ему не нужно проверять диапазон при индексировании при обращении к массиву. Если вы попытаетесь оптимизировать его, поместив длину массива в переменную, вы нарушите связь между циклом и массивом и фактически сделаете код намного медленнее.
Если вы собираетесь микрооптимизировать, вы должны ограничиться тем, что, как известно, использует много ресурсов. Если прирост производительности незначителен, используйте вместо этого наиболее читаемый и поддерживаемый код. Как компьютерная работа меняется со временем, так что то, что вы обнаружите, теперь немного быстрее, может не остаться таким.
источник
У меня очень простая техника.
Есть много раз, когда это экономит время, чтобы обойти этот процесс, но в целом вы будете знать, если это так. Если есть сомнения, я придерживаюсь этого по умолчанию.
источник
Воспользуйтесь преимуществами короткого замыкания:
if(someVar || SomeMethod())
занимает столько же времени, чтобы кодировать, и так же читабельно, как:
if(someMethod() || someVar)
все же это собирается оценить быстрее со временем.
источник
Подождите шесть месяцев, заставьте своего босса покупать всем новые компьютеры. Шутки в сторону. Время программиста намного дороже, чем аппаратное обеспечение в долгосрочной перспективе. Высокопроизводительные компьютеры позволяют программистам писать код простым способом, не заботясь о скорости.
источник
Старайтесь не оптимизировать слишком много заранее, тогда, когда вы оптимизируете, немного меньше беспокоитесь о читабельности.
Там немного я ненавижу больше, чем ненужную сложность, но когда вы попадаете в сложную ситуацию, часто требуется сложное решение.
Если вы пишете код наиболее очевидным способом, тогда сделайте комментарий, объясняющий, почему он был изменен, когда вы делаете сложное изменение.
Тем не менее, именно к вашему значению я нахожу, что много раз использование логической противоположности подходу по умолчанию иногда помогает:
может стать
На многих языках до тех пор, пока вы вносите соответствующие изменения в часть «вещи», и она все еще читаема. Это просто не подходит к проблеме так, как большинство людей думают о том, чтобы сделать это в первую очередь, потому что это имеет значение в обратном направлении.
в c # например:
также может быть написано как:
(и да, вы должны сделать это с помощью соединения или строителя строк, но я пытаюсь привести простой пример)
Есть много других приемов, которые можно использовать, которые несложно выполнить, но многие из них не применимы ко всем языкам, как например использование середины в левой части назначения в старом vb, чтобы избежать штрафа переназначения строки или чтения текстовых файлов в двоичном режиме. в .net, чтобы преодолеть штраф за буферизацию, когда файл слишком велик для чтения.
Единственный другой действительно общий случай, который я могу придумать, который применим везде, - это применить некоторую булеву алгебру к сложным условным выражениям, чтобы попытаться преобразовать уравнение во что-то, что дает больше шансов воспользоваться условным замыканием или превратить сложное множество вложенных операторов if-then или case в уравнение целиком. Ни один из них не работает во всех случаях, но они могут значительно сэкономить время.
источник
источник
Используйте лучшие инструменты, которые вы можете найти - хороший компилятор, хороший профилировщик, хорошие библиотеки. Получите правильные алгоритмы или, что еще лучше, используйте подходящую библиотеку, чтобы сделать это за вас. Тривиальные циклы оптимизации - это маленькая картошка, к тому же вы не так умны, как оптимизирующий компилятор.
источник
Самым простым для меня является использование стека, когда это возможно, всякий раз, когда шаблон использования общего случая соответствует диапазону, скажем, [0, 64), но имеет редкие случаи, у которых нет небольшой верхней границы.
Простой пример C (до):
И после:
Я обобщил это примерно так, так как такие виды горячих точек часто возникают при профилировании:
Вышеупомянутый стек используется, когда выделяемые данные достаточно малы в этих 99,9% случаях, и использует кучу в противном случае.
В C ++ я обобщил это с помощью небольшой последовательности, соответствующей стандарту (похожей на
SmallVector
реализацию), которая вращается вокруг той же концепции.Это не грандиозная оптимизация (я получил сокращение, скажем, от 3 секунд до завершения операции до 1,8 секунд), но для ее применения требуются такие тривиальные усилия. Когда вы можете сократить что-то с 3 до 1,8 секунд, просто введя строку кода и изменив две, это очень хороший результат для такого небольшого доллара.
источник
Ну, есть много изменений производительности, которые вы можете внести при доступе к данным, которые будут иметь огромное влияние на ваше приложение. Если вы пишете запросы или используете ORM для доступа к базе данных, вам необходимо прочитать некоторые книги по настройке производительности для используемой вами базы данных. Скорее всего, вы используете известные плохо работающие техники. Нет причин делать это, кроме невежества. Это не преждевременная оптимизация (я проклинаю того парня, который сказал это, потому что он был настолько распространен, что никогда не беспокоился о производительности), это хороший дизайн.
Просто быстрый пример повышения производительности для SQL Server: используйте соответствующие индексы, избегайте курсоров - используйте логику на основе множеств, используйте предложения sargable where, не складывайте представления поверх представлений, не возвращайте больше данных, чем нужно, или больше столбцы, чем вам нужно, не используйте коррелированные подзапросы.
источник
Если это C ++, вы должны привыкнуть,
++i
а неi++
.++i
никогда не будет хуже, это означает то же самое, что и отдельное утверждение, а в некоторых случаях это может быть улучшение производительности.Не стоит менять существующий код на случай, если он поможет, но это хорошая привычка.
источник
У меня есть немного другой взгляд на это. Простое следование советам, которые вы здесь получаете, не будет иметь большого значения, потому что есть некоторые ошибки, которые вы должны сделать, которые вы затем должны исправить, и из которых вам нужно учиться.
Ошибка, которую вам нужно сделать, - спроектировать структуру данных так, как это делают все. То есть с избыточными данными и многими уровнями абстракции, со свойствами и уведомлениями, которые распространяются по всей структуре, пытаясь поддерживать ее согласованность.
Затем вам нужно выполнить настройку производительности (профилирование), и она покажет вам, как во многих отношениях вам обходится куча циклов - это многоуровневая абстракция со свойствами и уведомлениями, которые распространяются по всей структуре, пытаясь поддерживать ее согласованность.
Возможно, вам удастся решить эти проблемы без значительных изменений в коде.
Тогда, если вам повезет, вы сможете узнать, что чем меньше структура данных, тем лучше, и что лучше быть способным терпеть временную несогласованность, чем пытаться держать многие вещи в строгом соответствии с волнами сообщений.
То, как вы пишете циклы, не имеет к этому никакого отношения.
источник