Эффективность алгоритма

Эффективность алгоритма (англ. algorithmic efficiency) — характеристика алгоритма, отражающая объём используемых им вычислительных ресурсов. Эффективность алгоритма можно сравнить с производительностью в инженерии для повторяющихся или непрерывных процессов.

Описание

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

К примеру, циклическая сортировка и Timsort — оба алгоритма сортировки списка элементов по возрастанию. Циклическая сортировка выполняет упорядочивание за время, пропорциональное квадрату числа элементов , минимизируя при этом количество записей в исходном массиве и требуя лишь незначительное количество дополнительной оперативной памяти (константное по длине списка — ). Timsort сортирует за логлинейное время (), но требует памяти, пропорциональной длине списка (). Если необходимо быстро сортировать большие списки, предпочтительнее Timsort; если важнее минимизировать количество циклов записи/стирания (например, для флеш-памяти) и памяти, более выгоден cycle sort.

История

Важность эффективности по времени отмечала ещё Ада Лавлейс в 1843 году применительно к аналитической машине Чарльза Бэббиджа:

«Практически при каждом вычислении возможны различные варианты расположения последовательности операций, и различные соображения должны влиять на выбор между ними при проектировании вычислительной машины. Одной из главных задач является выбор такой последовательности, которая позволит минимизировать время, необходимое для выполнения вычислений»[1].

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

Современные компьютеры гораздо быстрее и имеют значительно больший объём памяти. Тем не менее, Дональд Кнут подчёркивал, что эффективность всё равно остаётся важной:

«В сложившихся технических дисциплинах улучшение на 12 %, доступное без особых усилий, никогда не считается незначительным; и, по моему мнению, то же отношение должно сохраняться и в программной инженерии»[2].

В эпоху искусственного интеллекта, несмотря на то что большие языковые модели могут генерировать работающий код, он часто не соответствует требованиям по производительности, критичным для ограниченных по ресурсам или требующих высокой скорости систем[3]. Это делает эффективность кода важнейшим фактором для внедрения в реальных задачах.

Общая характеристика

Алгоритм считается эффективным, если его затраты ресурсов не превышают приемлемого уровня. Приемлемое — значит, что алгоритм завершит работу за разумное время и с допустимыми требованиями к памяти на используемом компьютере, обычно как функция от размера входных данных. С 1950-х годов вычислительная мощность и объём памяти компьютеров сильно выросли, так что современные приемлемые требования были бы неприемлемы даже 10 лет назад. Благодаря запасу роста производительности компьютеров (ежедневное удвоение примерно каждые два года), задачи, которые сегодня приемлемы на смартфонах и встраиваемых системах, ранее были бы невозможны даже для промышленных серверов.

Производители ПК и серверов регулярно выпускают новые, более производительные модели. Поскольку стоимость ПО может быть высока, иногда проще и дешевле «добавить железо» — если оно совместимо с существующими системами.

Ресурсы, расходуемые алгоритмом, могут измеряться разными способами: наиболее распространены скорость работы и объём используемой памяти; кроме того, учитываются скорость передачи данных, использование временного и постоянного дискового пространства, энергопотребление, общая стоимость владения, время отклика на внешние события и др. Многие из этих показателей зависят от объёма входных данных; некоторые алгоритмы чувствительны и к способу их организации, например, к степени отсортированности.

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

Теоретический анализ

В теоретическом анализе алгоритмов обычно оценивают их сложность в асимптотическом смысле. Наиболее распространён для этого нотация Большого O, введённая Кнутом, которая описывает поведение затрат ресурсов как функции от размера входа . Запись означает, что время работы алгоритма пропорционально , исключая члены меньшего порядка при больших . Эта оценка может быть неточной для малых , но считается точной при больших (асимптотика). Например, пузырьковая сортировка при малых объёмах данных может быть быстрее сортировки слиянием, но для больших данных предпочтительна последняя, поскольку лучше масштабируется.

Примеры использования O-нотации для асимптотической оценки временной сложности алгоритмов:

Нотация Название Примеры
константное Нахождение медианы в отсортированном списке; использование таблицы поиска фиксированного размера; эффективный хеш-функция для поиска элемента.
логарифмическое Поиск элемента в отсортированном массиве с помощью бинарного поиска или в сбалансированном дереве; все операции в биномиальной куче.
линейное Поиск в неотсортированном списке, «битом» дереве (в худшем случае) или массиве; сложение двух n-битных чисел с помощью волнопереносного сумматора.
логлинейное, квазилинейное Быстрое преобразование Фурье; сортировка кучей, быстрая сортировка (в среднем и лучшем случае), сортировка слиянием.
квадратичное Умножение двух n-разрядных чисел простым методом; пузырьковая сортировка (в худшем случае), сортировка Шелла, быстрая сортировка (в худшем случае), сортировка выбором или соритировка вставками.
экспоненциальное Поиск оптимального (неприближённого) решения задачи коммивояжёра динамическим программированием; определение эквивалентности логических высказываний методом полного перебора.

Оценка производительности

Для новых версий ПО или сравнения с конкурентами иногда используют бенчмарки, позволяющие оценить относительную производительность. Например, новый алгоритм сортировки можно сравнить с предшествующими по эффективности и учесть функциональные преимущества. Бенчмарки используются и конечными пользователями при выборе продуктов разных производителей по требуемым критериям функциональности и производительности. В индустрии больших мейнфреймов специализированные продукты, например, синхронные сортировки от независимых разработчиков, таких как Syncsort, конкурируют по скорости с решениями крупных производителей, например, IBM.

Существуют бенчмарки, сравнивающие быстродействие компилируемых и интерпретируемых языков программирования[4][5]. The Computer Language Benchmarks Game — это масштабный сравнительный проект эффективности решений типичных задач на различных языках программирования.

Даже простые «домашние» бенчмарки позволяют объективно сравнивать производительность языков по разным критериям.

Проблемы реализации

На эффективность влияет язык и стиль программирования[6], используемый компилятор, его параметры, операционная система. Часто интерпретируемые языки заметно медленнее компилируемых[4] (см. JIT-компиляция, интерпретируемый язык).

Влияние оказывают и не зависящие от программиста факторы: выравнивание данных, гранулярность данных, локальность данных в кэше, когерентность кэша, сборка мусора, параллелизм на уровне инструкций, многопоточность как на уровне оборудования, так и программном, одновременная многопоточность, вложенные вызовы процедур[7].

В ряде ситуаций доступны векторные процессоры, позволяющие выполнять одну инструкцию сразу над несколькими элементами (SIMD). Для эффективного использования этих возможностей алгоритмы иногда приходится специально переписывать (см. параллельные вычисления, распределённые вычисления). Активно разрабатываются высокоуровневые API для параллельных вычислений — CUDA, TensorFlow, Hadoop, OpenMP, MPI.

Возможно также, что совместимые на уровне архитектуры команд процессоры реализуют отдельные инструкции по-разному — например, одни команды могут быть быстрыми на одних моделях и медленными на других. Это осложняет работу оптимизирующих компиляторов, которым приходится учитывать специфику конкретного ЦПУ. Иногда компилятор вынужден эмулировать инструкции, отсутствующие в железе целевой платформы, что связано с существенными потерями производительности — особенно это актуально для микроконтроллеров без поддержки операций с плавающей точкой.

Оценка использованных ресурсов

Обычно оценки выражаются как функция от размера входных данных .

Два наиболее распространённых показателя:

  • Время: сколько времени требуется для завершения алгоритма?
  • Память: сколько рабочей памяти (обычно ОЗУ) задействовано? Сюда входит как память для кода (вспомогательное использование), так и для данных (внутреннее использование).

Для устройств на батареях (например, ноутбуков и смартфонов) и для длительных вычислений (например, суперкомпьютеров) важны:

  • Прямое энергопотребление: сколько энергии требуется для работы устройства.
  • Косвенное энергопотребление: энергия на охлаждение, освещение и т.п.

К 2018 году энергопотребление приобретает всё большее значение как метрика для разных масштабов — от IoT-девайсов до SoC и серверных ферм, что часто называют зелёные вычисления.

Реже учитываемые параметры эффективности:

  • Трафик (объём данных): может ограничивать производительность, например, при сжатии. Передача изображения (например, ) может потреблять десятки тысяч байт против 6 байт для текста «Google» — важно для задач, ограниченных вводом-выводом.
  • Внешняя память: место на внешних устройствах (жёсткий диск и др.) — как для временного хранения в процессе, так и для долговременной архивации.
  • Время отклика (задержка): критично в системах реального времени.
  • Общая стоимость владения: важно, если компьютер полностью отдан во власть одного алгоритма.

логотипа Google

Теоретические оценки

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

Практика

Практические характеристики оценивают с помощью бенчмарков. Во многих языках есть встроенные функции измерения процессорного времени выполнения. Для долгих алгоритмов важно и общее затраченное время; обычно результаты усредняются по нескольким тестам.

Тестирование производительности чувствительно к аппаратной конфигурации и действию параллельных процессов (в среде мультипрограммирование и многозадачность).

Важно сравнивать только те реализации алгоритмов, которые выполнены в одинаковых условиях (язык, компилятор, опции компиляции и т. д.).

Память

Память используется под код, входные данные, выходные данные и рабочую область. Альгоритмы типа сортировки часто перестраивают данные «на месте» и не требуют отдельного массива для результата.

Также сюда включаются рабочие переменные и стек вызова.

В ЭВМ первых поколений рабочих областей памяти было крайне мало (ЭСАК — до 1024 слов по 17 бит, ZX80 — 1024 байта ОЗУ); в 2010-х на обычных ПК обычно 4–32 Гб ОЗУ, то есть прирост — более чем в 300 миллионов раз.

Кэширование и иерархия памяти

Современные компьютеры имеют множество уровней памяти с разной скоростью доступа:

  • Регистры процессора — самый быстрый и самый ограниченный по объёму ресурс; все операции перед записью в память проходят через регистры, в которых параллельно выполняется обработка.
  • Кэш-память — обычно второй уровень скорости (и размерности); кэши современных CPU и GPU иерархичны — L1, L2, L3, — где каждый уровень медленнее, но больше предыдущего. Операции в L1-кэше примерно в 2–10 раз медленнее регистров; промах кэша и обращение к L2 могут замедлить до 10 раз, к L3 — ещё на порядок.

Если требуется обращаться к основной памяти, задержки возрастают ещё в 10–100 раз. В 2018 году ОЗУ часто размещается на том же кристалле процессора (CPU/GPU).

Алгоритмы, помещающиеся в кэш, в разы быстрее алгоритмов, рассчитанных только на ОЗУ, а те — в разы быстрее обращающихся к дисковой подкачке. Поэтому замещение кэша, локальность данных и выравнивание памяти — краеугольные аспекты высокопроизводительных вычислений. В современных платформах до трёх уровней кэша с различной производительностью и объёмом. Проблемы адаптации крайне различны на разных архитектурах.

Ранее, если алгоритм не умещался в память, он был неприменим; сегодня виртуальная память позволяет работать программам, многократно превышающим объём физической памяти, ценой резкого падения скорости. Алгоритмы, оптимизированные по памяти, одновременно выигрывают и по времени.

Примечания

  1. Green, Christopher Classics in the History of Psychology (англ.). Дата обращения: 19 мая 2013. Архивировано 3 октября 2025 года.
  2. Knuth, Donald (1974). “Structured Programming with go-to Statements” (PDF). Computing Surveys [англ.]. 6 (4): 261—301. DOI:10.1145/356635.356640. Архивировано из оригинала (PDF) 2009-08-24. Дата обращения 2013-05-19. Используется устаревший параметр |url-status= (справка)
  3. Du, Mingzhe (2025-06-03). “Afterburner: Reinforcement Learning Facilitates Self-Improving Code Efficiency Optimization” [англ.]. arXiv:2505.23387. Дата обращения 2025-06-10. |access-date= требует |url= (справка)
  4. 1 2 Floating Point Benchmark: Comparing Languages (Fourmilog: None Dare Call It Reason) (англ.). Fourmilab.ch (4 августа 2005). Дата обращения: 14 декабря 2011. Архивировано 26 ноября 2005 года.
  5. Whetstone Benchmark History (англ.). Roylongbottom.org.uk. Дата обращения: 14 декабря 2011. Архивировано 18 декабря 2025 года.
  6. Kriegel, Hans-Peter; Schubert, Erich; Zimek, Arthur (2016). “The (black) art of runtime evaluation: Are we comparing algorithms or implementations?”. Knowledge and Information Systems [англ.]. 52 (2): 341—378. DOI:10.1007/s10115-016-1004-2. S2CID 40772241.
  7. Guy Lewis Steele Jr. "Debunking the 'Expensive Procedure Call' Myth, or, Procedure Call Implementations Considered Harmful, or, Lambda: The Ultimate GOTO". MIT AI Lab. AI Lab Memo AIM-443. October 1977. [1]