2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 Ветвления и скорость выполнения программ
Сообщение12.08.2010, 13:03 
Супермодератор
Аватара пользователя


29/07/05
8248
Москва
Решил ответвить обсуждение сабжа от темы Олимпиадные задачи
В ней возник вопрос в влиянии ветвлений на скорость. Я привел пример, демонстрирующий, что если в простейшей операции, выполняемой во внутреннем цикле, заменить условный переход на пару арифметических операций, то процедура может ускориться в несколько раз.

Продолжим разговор.

Circiter в сообщении #343385 писал(а):
Кстати о сортировке. Я так понимаю, слишком на if'ах в этом случае не поэкономишь?

Вообще-то вроде как можно. Я слышал о реализациях сортировок коротких массивов (порядка 5-6 элементов), в которых число условных переходов то ли уменьшено, то ли вообще сведено к нулю. Но сам не очень в теме и реализации не анализировал. Если кто в курсе - можете просветить.

Circiter в сообщении #343573 писал(а):
согласно PAV'у, ветвления, использующиеся функциями минимума/максимума, есть зло.

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

Приведу пример. Вот любопытная статья с хабра Пузырьки, кэши и предсказатели переходов. Суть там в следующем. Когда процессор встречает условный переход, то он на самом деле не ждет окончания вычислений, которые позволяет ему определить дальнейший ход действий. Он использует предсказатель переходов, пытается угадать этот переход и продолжает выполнение по предсказанной веточке (насколько возможно, конечно). Если оказывается, что предсказание было неверным, то происходит откат тех действий, которые были произведены, и переход по правильной ветке. Необходимость делать такой откат влечет соответствующие накладные расходы и замедление работы.

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

В статье приводится конкретный пример двух реализаций сортировки методом пузырька, алгоритмически совершенно эквивалентных, т.е. там производятся просто одинаковые действия, однако в разном порядке. Мониторинг процессорных операций показывает практически одинаковые затраты по всему, однако в одном случае предсказатель переходов "промахивается" в разы чаще, чем в другом. В статье проведен тест с сортировкой массива длины 100 000 и там получается, что различие в скорости - в 3 раза.

Я полагаю, что этот пример достаточно искусственный, поскольку массивы такого объема пузырьком, конечно же, не сортируют. Я провел более реальный эксперимент, взяв случайную числовую последовательность длиной несколько миллионов чисел, разделил ее на короткие куски, и последовательно отсортировал эти куски (т.е. имитировал сортировку короткого массива во внутреннем цикле). В таком эксперименте тоже явно наблюдалось ускорение, хотя и не такое большое. При длине сортируемого массива в 10 элементов ускорение было порядка 15-20%. При длине в 100 элементов ускорение было примерно в два раза.

Внутренний цикл данной процедуры состоит из следующей операции: даны два числа $a$ и $b$, нужно расположить их в порядке возрастания. В простейшей реализации это делается так:
Код:
if( a > b )
{
    int x = a;
    a = b;
    b = x;
}

Можно сделать ту же самую операцию без ветвлений. Я применил трюк, использующий, правда, внутреннее представление числа: а именно, что старший бит отвечает за знак. Таким образом, код
Код:
int c = a - b;
int x = ((unsigned int)d)>>31;

помещает в переменную x значение 1, если разность a-b отрицательна, и ноль в противном случае. Несложная арифметика позволяет в результате получить минимум (или максимум) этих двух чисел. У меня получилась такая процедура:
Код:
    int c = a - b;
    int x = ((unsigned)c)>>31;
    c*= x;
    int min = b + c;
    b = a - c;
    a = min;

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

Также я попробовал применить рекоментацию venco по использованию библиотечных функций min и max (http://dxdy.ru/post343593.html#p343593):
Код:
int min = std::min(a,b);
b += a - min;
a = min;

К сожалению, эта штука работала еще существенно медленнее, чем даже моя безумная реализация. Возможно, виноват компилятор (у меня Microsoft Visual Studio). Я не знаю, что он сделал из данного кода. Может быть, он не сообразил или не смог заинлайнить вызов min? Хотя я поставил в настройках инлайнить все, что только возможно.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 00:15 
Аватара пользователя


25/03/09
94
Попробуйте заменить с *= x; на c = ~c; ++c; c &= x;
Цитата:
Может быть, он не сообразил или не смог заинлайнить вызов min?
В MS VC можно настроить генерацию .asm файлов и посмотреть, что получается.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 05:13 
Заслуженный участник


04/05/09
4582
По моему, неправильно использовать "пузырёк" для анализа эффективности предсказания ветвлений. Чем эффективнее алгоритм сортировки, тем ближе к 0.5 вероятности исходов сравнений, т.к. в идеале каждое сравнение должно давать один бит информации.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 08:51 
Заслуженный участник


11/05/08
32166
PAV в сообщении #343986 писал(а):
Я не знаю, что он сделал из данного кода. Может быть, он не сообразил или не смог заинлайнить вызов min?

Может, и невпопад скажу, но вот что возможно: он мог в целях совместимости сэмулировать команду CMOVL как сравнительно позднюю. Тогда действительно должно было получиться долго.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 13:53 
Аватара пользователя


31/10/08
1244
Цитата:
Вообще-то вроде как можно. Я слышал о реализациях сортировок коротких массивов (порядка 5-6 элементов), в которых число условных переходов то ли уменьшено, то ли вообще сведено к нулю. Но сам не очень в теме и реализации не анализировал. Если кто в курсе - можете просветить.

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

Цитата:
Предсказатель переходов работает либо статически (т.е. тупо всегда делает некоторое одно предсказание), либо динамически. Второй вариант означает, что для данного конкретного ветвления (которое за короткий срок выполнялось многократно) процессор накапливает статистику - как оно срабатывало, и начинает предсказывать переход в соответствии с ней. Таким образом, если во внутреннем цикле ветвление проверяет некоторую весьма редкую ситуацию, и в большинстве случаев срабатывает одним способом, то процессор быстро научится его предсказывать, и оно практически не будет ничего замедлять.

Бла-бла-бла. Не накапливает статистику. Процессор помнит места последних переходов 1-10 и для каждого перехода хранит флаг выполнился или нет. Если в предыдущем выполнился то велика вероятность что и в этом выполнится. Если процессор и промахнется то только один раз. А в остальных будет предсказывать правильно. Но условный переход все равно тормозит конвейер, без него код выполняется быстрее и подается векторизации.

Цитата:
(хотя, я надеялся, что компилятор может быть сумеет их достаточно компактно реализовать)
Это зря. Не сумеет. А еще у вас есть умножение которое медленнее обычного and. В общем тормозит из за того что код стал громоздким. А на ассемблере это гораздо короче и проще

Код:
mov    ecx,ebx
add     ecx, eax
sub     ecx, eax
cdq     
and     edx, ecx
add     eax, edx
sub     ebx, edx

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 14:22 


24/03/07
321
PAV в сообщении #343986 писал(а):
Решил ответвить обсуждение сабжа от темы Олимпиадные задачи
В ней возник вопрос в влиянии ветвлений на скорость. Я привел пример, демонстрирующий, что если в простейшей операции, выполняемой во внутреннем цикле, заменить условный переход на пару арифметических операций, то процедура может ускориться в несколько раз.
...

А что если в вашем примере ветвление убрать самым очевидным способом? Т.е.
Код:
if( p1[0] != 0 && p2[0] == 0 ) ++sum;

заменить на
Код:
sum += h[p1[0]][p2[0]];

где h - массив, определенный до цикла
Код:
h[1][0]=1; h[0][0]=0; h[1][1]=0; h[0][1]=0;


Что будет быстрее и почему?

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение13.08.2010, 15:14 
Аватара пользователя


31/10/08
1244
Dandan
Быстрее будет ord() но это Delphi/Pascal. :D

Код:
if( p1[0] != 0 && p2[0] == 0 ) ++sum;

На самом деле тут 2 if. Т.е две проверки два перехода.
Код:
if( p1[0] != 0) {
  if (p2[0] == 0 ) ++sum;
  }


А вот додумается компилятор тут с оптимизировать или нет вопрос. Но скорее нет.
Поэтому через таблицу будет быстрее.
Если логический && заменить на битовую & то будет лучше так как будет один логический переход
Код:
if (p1[0] & !p2[0] != 0)

А тут зависит от прихоти компилятора. 50% на 50% за оба случая.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение14.08.2010, 14:07 


16/06/10
199
PAV в сообщении #343986 писал(а):
У меня получилась такая процедура:
Код:
    int c = a - b;
    int x = ((unsigned)c)>>31;
    c*= x;
    int min = b + c;
    b = a - c;
    a = min;
Поробуйте заменить
Код:
c *= x; --> c &= -x;
По поводу команды CMOVxx. Она появилась в Pentium Pro, поддерживается начиная, если не ошибусь, с Visual C++ .NET 2003. При компиляции включите оптимизацию по размеру (/O1) и использование SSE инструкций (/arch:SSE). Интересно, что при оптимизации по скорости(!) компилятор отдает предпочтение условному переходу (для макросов min/max).

Pavia в сообщении #344149 писал(а):
А на ассемблере это гораздо короче и проще
Код:
mov    ecx,ebx
add     ecx, eax
sub     ecx, eax
cdq     
and     edx, ecx
add     eax, edx
sub     ebx, edx
Вы наверное поторопились. Это не будет работать, как было задумано.

Pavia в сообщении #344161 писал(а):
На самом деле тут 2 if. Т.е две проверки два перехода.
Код:
if( p1[0] != 0) {
  if (p2[0] == 0 ) ++sum;
  }

А вот додумается компилятор тут с оптимизировать или нет вопрос. Но скорее нет.
Нормальный компилятор не будет ничего "додумывать" - если выражение ложно, будет выполнен один условный переход. В стандарте чётко и ясно определены правила сокращённого вычисления логических выражений. Именно поэтому можно не боятся GPF при выполнении следующего кода
Код:
char *p;
...
if (p != NULL && *p != '\0')
    ...

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение18.08.2010, 10:38 
Супермодератор
Аватара пользователя


29/07/05
8248
Москва
venco в сообщении #344084 писал(а):
По моему, неправильно использовать "пузырёк" для анализа эффективности предсказания ветвлений. Чем эффективнее алгоритм сортировки, тем ближе к 0.5 вероятности исходов сравнений, т.к. в идеале каждое сравнение должно давать один бит информации.


Честно говоря, не очень понял, в чем состоит Ваш point. Здесь ведь речь не о сравнении разных алгоритмов, а о технических деталях реализации одного конкретного алгоритма и влиянии на быстродействие. Вообще, ветвления в этой теме рассматриваются не столько как логические сущности, намертво привязанные к алгоритму. Было показано, что в некоторых случаях можно чисто технически реализовать ту же самую операцию и без ветвлений, причем это может оказаться быстрее, даже несмотря на то, что ветвление заменяется на арифметические операции. Также сказано, что высокоэнтропийные (= наиболее "информативные" = наименее предсказуемые) ветвления - вообще говоря, наихудшие с точки зрения быстродействия. На примере пузырька показано, что простая переорганизация тех же самых вычислений в другом порядке, при которой те же проверки оказываются более предсказуемыми, тут же позитивно отражаются на скорости.

Pavia в сообщении #344149 писал(а):
Бла-бла-бла. Не накапливает статистику. Процессор помнит места последних переходов 1-10 и для каждого перехода хранит флаг выполнился или нет. Если в предыдущем выполнился то велика вероятность что и в этом выполнится. Если процессор и промахнется то только один раз. А в остальных будет предсказывать правильно.

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

Dandan в сообщении #344151 писал(а):
заменить на
Код:
sum += h[p1[0]][p2[0]];

где h - массив, определенный до цикла
Код:
h[1][0]=1; h[0][0]=0; h[1][1]=0; h[0][1]=0;


Что будет быстрее и почему?

Это будет практически то же самое, что и в моем последующем примере
Код:
sum += p1[0] * (1 - p2[0]);

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

lim0n в сообщении #344287 писал(а):
Поробуйте заменить
Код:
c *= x; --> c &= -x;

Я пробовал что-то подобное. Практически то же самое.

 Профиль  
                  
 
 Re: Ветвления и скорость выполнения программ
Сообщение18.08.2010, 15:08 
Аватара пользователя


31/10/08
1244
Цитата:
Что-то я не уверен, что все так примитивно. Судя, например, по этой статье Обзор микроархитектур современных десктопных процессоров, а также по обсуждению на некоторых других форумах, которые у меня сходу нагуглились, предсказатель ветвлений устроен более сложно, и совершенствуется с новыми моделями процессоров.


Вы не правильно статью читали. Статья называется кэши и предсказание ветвлений. Грубо говоря мух смешали с котлетами. На самом дели вся эта статья заключается в двух строчках. Есть кэш команд причем не один. И есть предсказатель перехода. Чтобы конвейер не простаивал данные выбираются из кэша. Но это и так понятно. Дальше идет тупо набивание текста.

Лучше вот читай
http://www.intel.com/assets/pdf/manual/248966.pdf
Это руководство по оптимизации от интел. Плюс в нем рассказана архитектура их процессоров.
Про предсказание переходов там буквально пару строчек и все.

Что касается статистики. Я описал единственно возможный способ. Он дает от 60 до 80% верных переходов (точно не помню где цифру видел). Все остальное не применимо. И это логично.

Во вторых вот патент в нем рассказано что такое таблица истории и как она используется
http://www.wikipatents.com/US-Patent-53 ... sm/Page-12
В других патентах дается тоже самое только вопрос реализации.

Еще иногда делают предсказание чередования. Но это мелочи. Дальше только статическое предсказание которое основано на распознавание циклов и не циклов. Делается оно за счет анализа инструкций идущих возле инструкции прыжка и точке в которую он прыгает. Алгоритм там тоже не сложный. Предсказание ветвлений не сильно ушло с процессора 8086.

-- Ср авг 18, 2010 16:11:00 --

Код:
Поробуйте заменить
Код:
c *= x; --> c &= -x;

Я пробовал что-то подобное. Практически то же самое.

Зависит от процессора но на современных умножение по скорости занимает столько же сколько и сложение и and. Так что разница не велика.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 10 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group