Оптимизация работы программы по скорости методами программирования без условных операторов | Статья в журнале «Молодой ученый»

Отправьте статью сегодня! Журнал выйдет 28 декабря, печатный экземпляр отправим 1 января.

Опубликовать статью в журнале

Автор:

Рубрика: Информационные технологии

Опубликовано в Молодой учёный №20 (362) май 2021 г.

Дата публикации: 14.05.2021

Статья просмотрена: 183 раза

Библиографическое описание:

Лобашевская, В. А. Оптимизация работы программы по скорости методами программирования без условных операторов / В. А. Лобашевская. — Текст : непосредственный // Молодой ученый. — 2021. — № 20 (362). — С. 33-36. — URL: https://moluch.ru/archive/362/80996/ (дата обращения: 16.12.2024).



В статье приводится описание техники программирования без использования условных операторов. Такая техника позволяет минимизировать эффект ошибочного предсказания ветви процессора. Приводится сравнение скорости работы функций с условными операторами и без них.

Ключевые слова: условный оператор, предсказатель переходов, branch misprediction, бенчмарк, Си, ассемблер.

Введение

Внимательно посмотрев на таблицу 1 «задержек, которые должен знать каждый программист» [1] можно заметить строчку «Branch mispredict». Ошибочное предсказание ветви (branch mispredict) — это ситуация, когда процессор не смог угадать ветвь, по которой пойдет программа в условном переходе [2]. Если процессор не смог угадать ветвь, то ему требуется дополнительное время на удаление и загрузку инструкций (команд) для другой ветви. На это тратится какое-то время, в 5–10 раз большее, чем обращение к кешу L1 процессора.

Таблица 1

Задержки, которые должен знать каждый программист (часть)

L1 cache reference

0.5 ns

Branch mispredict

5 ns

L2 cache reference

7 ns

Mutex lock/unlock

25 ns

Main memory reference

100 ns

...

...

Если понимать, как работает ошибочное предсказание, то можно уменьшить количество условных переходов в программе и сильно оптимизировать ее работу по времени. Такой подход называется безусловным программированием (branchless programming). Подробнее о предсказателе переходов процессора можно прочитать в соответствующих источниках [3].

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

Простейший пример

Рассмотрим простейший пример, функция min():

Это обычный способ написания функции нахождения минимума на языке Си. Он понятен всем разработчикам. Но в нем существует условный оператор if, который, если верить таблице выше, может замедлить выполнение нашей программы. Напишем функцию min_branchless():

В данном случае мы используем особенность языка Си, которая позволяет преобразовать значение false в 0, а значение true в 1. Мы используем сумму произведений с взаимно-исключающими условиями, чтобы получить необходимый нам результат. Например a = 5 и b = 7, тогда выражение (a < b) будет равно 1, а выражение (b <= a) будет равно 0. Тогда итоговое выражение будет 1 * 5 + 0 * 7. Таким образом мы составили функцию нахождения минимума без операций условного перехода .

Проверим, какая из этих функций будет работать быстрее дизассемблировав их:

Важное замечание. Во время компиляции здесь и далее используются флаги оптимизации (для gcc -O2 например). Также сокращены адреса для повышения читаемости.

Видно, что операций в min_branchless() несколько больше, чем в min(). А в min() вообще нет операций условных переходов (таких как jg, je, jle и т. д.). Это связано с тем, что современные компиляторы достаточно умные, и могут понять, что в этой функции мы хотим найти минимум из двух чисел и используют соответствующую ассемблерную команду cmovg (выделено жирным).

В простейшем случае применение branchless программирования не позволит ускорить работу программы.

Пример с ускорением работы

Рассмотрим функцию, которая заменяет все строчные буквы на прописные — toUpper(). Обычная реализация этой функции выглядит следующим образом:

Поясним, что мы пишем программы на языке Си в котором строка представляется в виде участка памяти, разбитого на байты — отдельные ASCII символы, оканчивающиеся символом ‘\0’. Поэтому мы используем цикл по этому участку памяти for (; *str; ++str).

Поскольку символы имеют представление ASCII, то чтобы изменить регистр символа нужно добавить или вычесть к нему 32. Латинские буквы в ASCII таблице идут подряд и ‘A’ отличается от ‘a’ на 32 единицы.

Дизассемблировав эту функцию мы обнаружим условный переход переходов ja.

Время выполнения этой функции для строки размером 1024 случайных символа (небольшой размер, чтобы минимизировать влияние промахов кэша) четыре миллиона раз составляет примерно 21,9 секунд.

Изменим функцию to_upper() с применением принципов безусловного программирования:

В этой функции отказываемся от условного оператора if, но оставляем взаимоисключающие условия. Результат вычисления условий умножается на необходимый нам результат: на (*str — 32) если буква была строчной (т. е. делаем букву заглавной), или на (*str) т. е. на сам символ (т. е. символ не меняется).

Время выполнения этой функции условиях идентичных условиям из to_upper() составило 11,2 секунды, что в 2 раза быстрее первого варианта.

Функцию to_upper() можно ускорить еще больше:

Время выполнение этой версии функции составило 7,0 секунд, что примерно в 3 раза быстрее первого варианта.

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

Именно поэтому скорость выполнения этой функции выше, чем у той, где есть условные переходы.

Выводы

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

Литература:

  1. Jonas Bonér Latency Numbers Every Programmer Should Know — Текст: электронный // Текстовый документ на гит репозитории — URL: https://gist.github.com/jboner/2841832 (дата обращения 28.04.2021)
  2. Иерархия памяти. — Текст: электронный // Википедия свободная энциклопедия. — URL: https://computer_en_ru.academic.ru/4251/branch_misprediction (дата обращения 30.04.2021)
  3. Предсказатель переходов. — Текст: электронный // Википедия свободная энциклопедия. — URL: https://ru.wikipedia.org/wiki/Предсказатель_переходов (дата обращения 30.04.2021)
Основные термины (генерируются автоматически): ASCII, функция, время выполнения, условный оператор, эта, безусловное программирование, ошибочное предсказание, переход, скорость выполнения, язык Си.


Ключевые слова

ассемблер, си, бенчмарк, условный оператор, предсказатель переходов, branch misprediction

Похожие статьи

Применение векторизации слов для нечеткого поиска

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

Линейное программирование

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

Многопоточность в языке Swift

В статье рассмотрим основной способ выполнять код асинхронно, который используется в iOS приложениях. Подробно разобран основной функционал Grand Central Dispatch (GCD) и сценарии, в которых можно реализовать многопоточность с его помощью.

Неформальный алгоритм сортировки файла с применением битового сжатия в языке программирования C++

В статье автор рассматривает сортировку файла при помощи битового массива на C++. А также сравнивает затраты оперативной памяти без использования битового массива.

Разработка алгоритма быстрого преобразования Фурье на базе модели акторов

В данной работе авторами представлен параллельный алгоритм быстрого преобразования Фурье, универсальным примитивом выполнения вычислений которого является семейство акторов.

Реализация поиска минимума функции методом градиентного спуска в среде MatLab

В данной статье описана реализация метода градиентного спуска в программе MatLab.

Сравнение эффективности использования технологий CUDA и OpenCL при реализации нейронной сети репликации

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

Крайние подходы группировки данных в распознавании образов

В работе рассматривается основные методы группировки данных при «обучении без учителя» (самообучении), т. е. в условии, когда имеется непомеченная выборка.

Нейронные сети, обучаемые на основе алгоритма обратного распространения ошибки

В статье приводится обзор алгоритма обучения на основе обратного распространение ошибки. В результате были оценены параметры нейронной сети с применением алгоритма обратного распространение ошибки. Полученные результаты являются примером работы искус...

Анализ эффективности алгоритмов сортировки и вcтроенных реализаций на примере языка программирования Java

В данной статье показана значительная роль проведения анализа работы алгоритмов сортировки на массивах данных различной размерности. Рассмотрены актуальные алгоритмы и стандартные реализации сортировки в языке программирования Java.

Похожие статьи

Применение векторизации слов для нечеткого поиска

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

Линейное программирование

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

Многопоточность в языке Swift

В статье рассмотрим основной способ выполнять код асинхронно, который используется в iOS приложениях. Подробно разобран основной функционал Grand Central Dispatch (GCD) и сценарии, в которых можно реализовать многопоточность с его помощью.

Неформальный алгоритм сортировки файла с применением битового сжатия в языке программирования C++

В статье автор рассматривает сортировку файла при помощи битового массива на C++. А также сравнивает затраты оперативной памяти без использования битового массива.

Разработка алгоритма быстрого преобразования Фурье на базе модели акторов

В данной работе авторами представлен параллельный алгоритм быстрого преобразования Фурье, универсальным примитивом выполнения вычислений которого является семейство акторов.

Реализация поиска минимума функции методом градиентного спуска в среде MatLab

В данной статье описана реализация метода градиентного спуска в программе MatLab.

Сравнение эффективности использования технологий CUDA и OpenCL при реализации нейронной сети репликации

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

Крайние подходы группировки данных в распознавании образов

В работе рассматривается основные методы группировки данных при «обучении без учителя» (самообучении), т. е. в условии, когда имеется непомеченная выборка.

Нейронные сети, обучаемые на основе алгоритма обратного распространения ошибки

В статье приводится обзор алгоритма обучения на основе обратного распространение ошибки. В результате были оценены параметры нейронной сети с применением алгоритма обратного распространение ошибки. Полученные результаты являются примером работы искус...

Анализ эффективности алгоритмов сортировки и вcтроенных реализаций на примере языка программирования Java

В данной статье показана значительная роль проведения анализа работы алгоритмов сортировки на массивах данных различной размерности. Рассмотрены актуальные алгоритмы и стандартные реализации сортировки в языке программирования Java.

Задать вопрос