В информатике алгоритмическая эффективность — это свойство алгоритма , которое относится к объему вычислительных ресурсов, используемых алгоритмом. Алгоритмическую эффективность можно рассматривать как аналог инженерной производительности для повторяющегося или непрерывного процесса.
Для максимальной эффективности желательно минимизировать использование ресурсов. Однако различные ресурсы, такие как сложность времени и пространства , нельзя сравнивать напрямую, поэтому то, какой из двух алгоритмов считается более эффективным, часто зависит от того, какая мера эффективности считается наиболее важной.
Например, пузырьковая сортировка и timsort — это алгоритмы сортировки списка элементов от наименьшего к наибольшему. Пузырьковая сортировка организует список за время, пропорциональное квадрату числа элементов ( , см. обозначение Big O ), но требует лишь небольшого объема дополнительной памяти , которая постоянна относительно длины списка ( ). Timsort сортирует список за время, линейно зависящее от длины списка ( ), но имеет требования к пространству , линейно зависящие от длины списка ( ). Если для данного приложения необходимо сортировать большие списки с высокой скоростью, timsort является лучшим выбором; однако, если важнее минимизировать объем памяти , занимаемый сортировкой, пузырьковая сортировка является лучшим выбором.
Важность эффективности по отношению ко времени была подчеркнута Адой Лавлейс в 1843 году применительно к механической аналитической машине Чарльза Бэббиджа :
«Почти в каждом вычислении возможно большое разнообразие схем последовательности процессов, и различные соображения должны влиять на выбор среди них для целей вычислительной машины. Одной из основных задач является выбор такой схемы, которая будет стремиться сократить до минимума время, необходимое для завершения вычисления» [1]
Ранние электронные компьютеры имели как ограниченную скорость , так и ограниченную память с произвольным доступом . Поэтому возникал компромисс между пространством и временем . Задача могла использовать быстрый алгоритм, использующий большой объем памяти, или она могла использовать медленный алгоритм, использующий малый объем памяти. Поэтому инженерный компромисс заключался в использовании самого быстрого алгоритма, который мог поместиться в доступной памяти.
Современные компьютеры значительно быстрее ранних компьютеров и имеют гораздо больший объем доступной памяти ( гигабайты вместо килобайтов ). Тем не менее, Дональд Кнут подчеркнул, что эффективность по-прежнему является важным фактором:
«В устоявшихся инженерных дисциплинах легко достижимое улучшение на 12% никогда не считается незначительным, и я считаю, что та же точка зрения должна преобладать в программной инженерии» [2]
Алгоритм считается эффективным, если его потребление ресурсов, также известное как вычислительная стоимость, находится на некотором приемлемом уровне или ниже. Грубо говоря, «приемлемый» означает: он будет работать за разумное количество времени или места на доступном компьютере, как правило, в зависимости от размера входных данных. С 1950-х годов компьютеры показали резкий рост как доступной вычислительной мощности, так и доступного объема памяти, поэтому текущие приемлемые уровни были бы неприемлемыми даже 10 лет назад. Фактически, благодаря приблизительному удвоению мощности компьютеров каждые 2 года , задачи, которые приемлемо эффективны на современных смартфонах и встроенных системах, могли быть неприемлемо неэффективными для промышленных серверов 10 лет назад.
Производители компьютеров часто выпускают новые модели, часто с более высокой производительностью . Стоимость программного обеспечения может быть довольно высокой, поэтому в некоторых случаях самым простым и дешевым способом повышения производительности может быть просто покупка более быстрого компьютера, при условии, что он совместим с существующим компьютером.
Существует множество способов измерения ресурсов, используемых алгоритмом: две наиболее распространенные меры — это скорость и использование памяти; другие меры могут включать скорость передачи, временное использование диска, долгосрочное использование диска, энергопотребление, общую стоимость владения , время отклика на внешние стимулы и т. д. Многие из этих мер зависят от размера входных данных для алгоритма, т. е. объема данных, которые необходимо обработать. Они также могут зависеть от способа организации данных; например, некоторые алгоритмы сортировки плохо работают с данными, которые уже отсортированы или отсортированы в обратном порядке.
На практике существуют и другие факторы, которые могут повлиять на эффективность алгоритма, такие как требования к точности и/или надежности. Как подробно описано ниже, способ реализации алгоритма также может существенно повлиять на фактическую эффективность, хотя многие аспекты этого связаны с вопросами оптимизации .
В теоретическом анализе алгоритмов обычной практикой является оценка их сложности в асимптотическом смысле. Наиболее часто используемой нотацией для описания потребления ресурсов или «сложности» является нотация Big O Дональда Кнута , представляющая сложность алгоритма как функцию размера входных данных . Нотация Big O является асимптотической мерой сложности функции, где примерно означает, что временные требования для алгоритма пропорциональны , опуская члены низшего порядка , которые вносят меньший вклад, чем в рост функции при произвольном увеличении . Эта оценка может вводить в заблуждение, когда является малым, но, как правило, достаточно точна, когда является большим, поскольку нотация является асимптотической. Например, пузырьковая сортировка может быть быстрее, чем сортировка слиянием , когда необходимо отсортировать только несколько элементов; однако любая реализация, вероятно, будет соответствовать требованиям производительности для небольшого списка. Обычно программисты заинтересованы в алгоритмах, которые эффективно масштабируются для больших размеров входных данных, и сортировка слиянием предпочтительнее пузырьковой сортировки для списков длины, встречающейся в большинстве программ с интенсивным использованием данных.
Вот несколько примеров нотации «О большое», применяемой к асимптотической временной сложности алгоритмов:
Для новых версий программного обеспечения или для сравнения с конкурирующими системами иногда используются бенчмарки , которые помогают оценить относительную производительность алгоритмов. Например, если создается новый алгоритм сортировки , его можно сравнить с его предшественниками, чтобы убедиться, что он, по крайней мере, эффективен, как и прежде, с известными данными, принимая во внимание любые функциональные улучшения. Бенчмарки могут использоваться клиентами при сравнении различных продуктов от альтернативных поставщиков, чтобы оценить, какой продукт лучше всего соответствует их конкретным требованиям с точки зрения функциональности и производительности. Например, в мире мэйнфреймов некоторые фирменные продукты сортировки от независимых компаний-разработчиков программного обеспечения, таких как Syncsort, конкурируют с продуктами от основных поставщиков, таких как IBM, по скорости.
Некоторые тесты предоставляют возможности для проведения анализа, сравнивающего относительную скорость различных компилируемых и интерпретируемых языков, например [3] [4] , а игра Computer Language Benchmarks Game сравнивает производительность реализаций типичных задач программирования на нескольких языках программирования.
Даже создание " сделай сам " бенчмарков может продемонстрировать относительную производительность различных языков программирования, используя множество критериев, указанных пользователем. Это довольно просто, как показывает пример "Nine language performance roundup" Кристофера В. Коуэлла-Шаха. [5]
Проблемы реализации также могут влиять на эффективность, например, выбор языка программирования или способ, которым алгоритм фактически кодируется, [6] или выбор компилятора для конкретного языка, или используемые параметры компиляции , или даже используемая операционная система . Во многих случаях язык, реализованный интерпретатором , может быть намного медленнее языка, реализованного компилятором. [3] См. статьи о компиляции just-in-time и интерпретируемых языках .
Существуют и другие факторы, которые могут влиять на проблемы времени или пространства, но которые могут находиться вне контроля программиста; к ним относятся выравнивание данных , гранулярность данных , локальность кэша , когерентность кэша , сборка мусора , параллелизм на уровне инструкций , многопоточность (на аппаратном или программном уровне), одновременная многозадачность и вызовы подпрограмм . [7]
Некоторые процессоры имеют возможности для векторной обработки , которые позволяют одной инструкции работать с несколькими операндами ; программисту или компилятору может быть легко или нелегко использовать эти возможности. Алгоритмы, разработанные для последовательной обработки, могут нуждаться в полной переработке для использования параллельной обработки , или их можно легко перенастроить. Поскольку параллельные и распределенные вычисления становятся все более важными в конце 2010-х годов, все больше инвестиций вкладывается в эффективные высокоуровневые API для параллельных и распределенных вычислительных систем, таких как CUDA , TensorFlow , Hadoop , OpenMP и MPI .
Другая проблема, которая может возникнуть при программировании, заключается в том, что процессоры, совместимые с одним и тем же набором инструкций (например, x86-64 или ARM ), могут реализовывать инструкцию по-разному, так что инструкции, которые относительно быстры на некоторых моделях, могут быть относительно медленными на других моделях. Это часто создает проблемы для оптимизирующих компиляторов , которые должны иметь обширные знания о конкретном ЦП и другом оборудовании, доступном на целевой платформе компиляции, чтобы наилучшим образом оптимизировать программу для производительности. В крайнем случае компилятор может быть вынужден эмулировать инструкции , не поддерживаемые на целевой платформе компиляции, заставляя его генерировать код или связывать внешний библиотечный вызов для получения результата, который в противном случае невычислим на этой платформе, даже если он изначально поддерживается и более эффективен на оборудовании на других платформах. Это часто имеет место во встраиваемых системах в отношении арифметики с плавающей точкой , где небольшие и маломощные микроконтроллеры часто не имеют аппаратной поддержки арифметики с плавающей точкой и, таким образом, требуют вычислительно дорогих программных процедур для выполнения вычислений с плавающей точкой.
Меры обычно выражаются как функция размера входных данных .
Две наиболее распространённые меры:
Для компьютеров, питание которых осуществляется от батареи (например, ноутбуков и смартфонов ), или для очень длительных/больших вычислений (например, суперкомпьютеров ), интерес представляют другие показатели:
По состоянию на 2018 год [update]потребление энергии растет как важный показатель для вычислительных задач всех типов и масштабов, от встроенных устройств Интернета вещей до систем-на-чипе и серверных ферм . Эту тенденцию часто называют зелеными вычислениями .
Менее распространенные показатели вычислительной эффективности также могут быть актуальны в некоторых случаях:
Анализ алгоритмов , обычно использующий такие концепции, как временная сложность , может быть использован для получения оценки времени выполнения как функции размера входных данных. Результат обычно выражается с помощью нотации Big O. Это полезно для сравнения алгоритмов, особенно когда необходимо обработать большой объем данных. Более подробные оценки необходимы для сравнения производительности алгоритмов, когда объем данных невелик, хотя это, вероятно, будет иметь меньшее значение. Параллельные алгоритмы могут быть более сложными для анализа .
Для оценки производительности алгоритма на практике можно использовать бенчмарк. Во многих языках программирования есть доступная функция, которая показывает использование процессорного времени . Для длительных алгоритмов также может быть интересно прошедшее время. Результаты обычно следует усреднять по нескольким тестам.
Профилирование на основе выполнения может быть очень чувствительным к конфигурации оборудования и возможности одновременного выполнения других программ или задач в многопроцессорной и многопрограммной среде.
Этот вид тестирования также во многом зависит от выбора конкретного языка программирования, компилятора и его параметров, поэтому все сравниваемые алгоритмы должны быть реализованы в одинаковых условиях.
В этом разделе рассматривается использование ресурсов памяти ( регистры , кэш , ОЗУ , виртуальная память , вторичная память ) во время выполнения алгоритма. Как и для анализа времени выше, проанализируйте алгоритм, обычно используя анализ сложности пространства , чтобы получить оценку памяти времени выполнения, необходимой как функцию от размера входных данных. Результат обычно выражается с помощью нотации Big O.
Необходимо учитывать до четырех аспектов использования памяти:
Ранние электронные компьютеры и ранние домашние компьютеры имели относительно небольшие объемы рабочей памяти. Например, электронный калькулятор с задержкой хранения (EDSAC) 1949 года имел максимальную рабочую память в 1024 17-битных слова, в то время как Sinclair ZX80 1980 года изначально имел 1024 8-битных байта рабочей памяти. В конце 2010-х годов для персональных компьютеров типично иметь от 4 до 32 ГБ оперативной памяти, что в 300 миллионов раз больше памяти.
Современные компьютеры могут иметь относительно большие объемы памяти (возможно, гигабайты), поэтому необходимость втиснуть алгоритм в ограниченный объем памяти уже не является проблемой, как это было раньше. Однако различные типы памяти и их относительные скорости доступа могут быть значительными:
Алгоритм, потребности в памяти которого будут помещаться в кэш-память, будет намного быстрее, чем алгоритм, который помещается в основную память, которая, в свою очередь, будет намного быстрее, чем алгоритм, которому приходится прибегать к подкачке. Из-за этого политики замены кэша чрезвычайно важны для высокопроизводительных вычислений, как и программирование с учетом кэша и выравнивание данных . Чтобы еще больше усложнить проблему, некоторые системы имеют до трех уровней кэш-памяти с различной эффективной скоростью. Разные системы будут иметь разное количество этих различных типов памяти, поэтому эффект потребностей в памяти алгоритма может сильно различаться от одной системы к другой.
На заре электронных вычислений, если алгоритм и его данные не помещались в основную память, то алгоритм не мог быть использован. В настоящее время использование виртуальной памяти, по-видимому, обеспечивает гораздо больше памяти, но за счет производительности. Гораздо более высокую скорость можно получить, если алгоритм и его данные помещаются в кэш-память; в этом случае минимизация пространства также поможет минимизировать время. Это называется принципом локальности и может быть подразделено на локальность ссылки , пространственную локальность и временную локальность . Алгоритм, который не будет полностью помещаться в кэш-память, но который демонстрирует локальность ссылки, может работать достаточно хорошо.