Каков эффект упорядочения, если... else, если утверждения по вероятности?

В частности, если у меня есть ряд операторов if... else if, и я как-то заранее знаю относительную вероятность того, что каждый оператор будет оценивать до true, насколько разница в времени выполнения делает это сортировать их в порядке вероятности? Например, должен ли я это предпочесть:

if (highly_likely)
  //do something
else if (somewhat_likely)
  //do something
else if (unlikely)
  //do something

:

if (unlikely)
  //do something
else if (somewhat_likely)
  //do something
else if (highly_likely)
  //do something

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

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

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

Ответ 1

Как правило, большинство, если не все процессоры Intel предполагают, что ветки вперед не принимаются в первый раз, когда они видят их. См. Работа Godbolt.

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

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

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

Итак, вы должны упорядочить свои ветки в порядке убывания вероятности, чтобы получить наилучшее предсказание ветвления с "первой встречи".

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

Кроме того, векторизация и многие другие оптимизации применяются к крошечным жестким циклам.

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

Естественно, это все выходит из окна, если некоторые тесты намного дешевле других.

Ответ 2

Я составил следующий тест во время выполнения двух разных блоков if... else if, один из которых отсортирован по порядку вероятности, другой отсортирован в обратном порядке:

#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <functional>

using namespace std;

int main()
{
    long long sortedTime = 0;
    long long reverseTime = 0;

    for (int n = 0; n != 500; ++n)
    {
        //Generate a vector of 5000 random integers from 1 to 100
        random_device rnd_device;
        mt19937 rnd_engine(rnd_device());
        uniform_int_distribution<int> rnd_dist(1, 100);
        auto gen = std::bind(rnd_dist, rnd_engine);
        vector<int> rand_vec(5000);
        generate(begin(rand_vec), end(rand_vec), gen);

        volatile int nLow, nMid, nHigh;
        chrono::time_point<chrono::high_resolution_clock> start, end;

        //Sort the conditional statements in order of increasing likelyhood
        nLow = nMid = nHigh = 0;
        start = chrono::high_resolution_clock::now();
        for (int& i : rand_vec) {
            if (i >= 95) ++nHigh;               //Least likely branch
            else if (i < 20) ++nLow;
            else if (i >= 20 && i < 95) ++nMid; //Most likely branch
        }
        end = chrono::high_resolution_clock::now();
        reverseTime += chrono::duration_cast<chrono::nanoseconds>(end-start).count();

        //Sort the conditional statements in order of decreasing likelyhood
        nLow = nMid = nHigh = 0;
        start = chrono::high_resolution_clock::now();
        for (int& i : rand_vec) {
            if (i >= 20 && i < 95) ++nMid;  //Most likely branch
            else if (i < 20) ++nLow;
            else if (i >= 95) ++nHigh;      //Least likely branch
        }
        end = chrono::high_resolution_clock::now();
        sortedTime += chrono::duration_cast<chrono::nanoseconds>(end-start).count();

    }

    cout << "Percentage difference: " << 100 * (double(reverseTime) - double(sortedTime)) / double(sortedTime) << endl << endl;
}

Используя MSVC2017 с /O 2, результаты показывают, что отсортированная версия последовательно примерно на 28% быстрее, чем несортированная версия. За комментарий luk32 я также переключил порядок двух тестов, что делает заметную разницу (22% против 28%). Код был запущен под Windows 7 на Intel Xeon E5-2697 v2. Это, конечно, очень проблемно и не должно интерпретироваться как окончательный ответ.

Ответ 3

Нет, вы не должны, если только вы не уверены, что затронута целевая система. По умолчанию читайте.

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

Дело в том, что современные процессоры имеют отраслевые предсказатели. Существует много логики, посвященной предварительной выборке данных и инструкций, а современные процессоры x86 довольно умны, когда дело доходит до этого. Некоторые более тонкие архитектуры, такие как ARM или графические процессоры, могут быть уязвимы для этого. Но он действительно сильно зависит как от компилятора, так и от целевой системы.

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

код:

#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <functional>

using namespace std;

int main()
{
    //Generate a vector of random integers from 1 to 100
    random_device rnd_device;
    mt19937 rnd_engine(rnd_device());
    uniform_int_distribution<int> rnd_dist(1, 100);
    auto gen = std::bind(rnd_dist, rnd_engine);
    vector<int> rand_vec(5000);
    generate(begin(rand_vec), end(rand_vec), gen);
    volatile int nLow, nMid, nHigh;

    //Count the number of values in each of three different ranges
    //Run the test a few times
    for (int n = 0; n != 10; ++n) {

        //Run the test again, but now sort the conditional statements in reverse-order of likelyhood
        {
          nLow = nMid = nHigh = 0;
          auto start = chrono::high_resolution_clock::now();
          for (int& i : rand_vec) {
              if (i >= 95) ++nHigh;               //Least likely branch
              else if (i < 20) ++nLow;
              else if (i >= 20 && i < 95) ++nMid; //Most likely branch
          }
          auto end = chrono::high_resolution_clock::now();
          cout << "Reverse-sorted: \t" << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << "ns" << endl;
        }

        {
          //Sort the conditional statements in order of likelyhood
          nLow = nMid = nHigh = 0;
          auto start = chrono::high_resolution_clock::now();
          for (int& i : rand_vec) {
              if (i >= 20 && i < 95) ++nMid;  //Most likely branch
              else if (i < 20) ++nLow;
              else if (i >= 95) ++nHigh;      //Least likely branch
          }
          auto end = chrono::high_resolution_clock::now();
          cout << "Sorted:\t\t\t" << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << "ns" << endl;
        }
        cout << endl;
    }
}

Ответ 4

Только мои 5 центов. Кажется, эффект упорядочения, если утверждения должны зависеть от:

  • Вероятность каждого оператора if.

  • Число итераций, поэтому предиктор ветки мог бы вбить.

  • Вероятные/маловероятные подсказки компилятора, т.е. макет кода.

Чтобы изучить эти факторы, я сравнил следующие функции:

ordered_ifs()

for (i = 0; i < data_sz * 1024; i++) {
    if (data[i] < check_point) // highly likely
        s += 3;
    else if (data[i] > check_point) // samewhat likely
        s += 2;
    else if (data[i] == check_point) // very unlikely
        s += 1;
}

reversed_ifs()

for (i = 0; i < data_sz * 1024; i++) {
    if (data[i] == check_point) // very unlikely
        s += 1;
    else if (data[i] > check_point) // samewhat likely
        s += 2;
    else if (data[i] < check_point) // highly likely
        s += 3;
}

ordered_ifs_with_hints()

for (i = 0; i < data_sz * 1024; i++) {
    if (likely(data[i] < check_point)) // highly likely
        s += 3;
    else if (data[i] > check_point) // samewhat likely
        s += 2;
    else if (unlikely(data[i] == check_point)) // very unlikely
        s += 1;
}

reversed_ifs_with_hints()

for (i = 0; i < data_sz * 1024; i++) {
    if (unlikely(data[i] == check_point)) // very unlikely
        s += 1;
    else if (data[i] > check_point) // samewhat likely
        s += 2;
    else if (likely(data[i] < check_point)) // highly likely
        s += 3;
}

данные

В массиве данных содержатся случайные числа от 0 до 100:

const int RANGE_MAX = 100;
uint8_t data[DATA_MAX * 1024];

static void data_init(int data_sz)
{
    int i;
        srand(0);
    for (i = 0; i < data_sz * 1024; i++)
        data[i] = rand() % RANGE_MAX;
}

Результаты

Ниже приведены результаты для Intel i5 @3,2 ГГц и g++ 6.3.0. Первым аргументом является check_point (т.е. Вероятность в %% для оператора с высокой вероятностью if), второй аргумент - data_sz (т.е. Число итераций).

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4                    4660 ns       4658 ns     150948
ordered_ifs/50/8                   25636 ns      25635 ns      27852
ordered_ifs/75/4                    4326 ns       4325 ns     162613
ordered_ifs/75/8                   18242 ns      18242 ns      37931
ordered_ifs/100/4                   1673 ns       1673 ns     417073
ordered_ifs/100/8                   3381 ns       3381 ns     207612
reversed_ifs/50/4                   5342 ns       5341 ns     126800
reversed_ifs/50/8                  26050 ns      26050 ns      26894
reversed_ifs/75/4                   3616 ns       3616 ns     193130
reversed_ifs/75/8                  15697 ns      15696 ns      44618
reversed_ifs/100/4                  3738 ns       3738 ns     188087
reversed_ifs/100/8                  7476 ns       7476 ns      93752
ordered_ifs_with_hints/50/4         5551 ns       5551 ns     125160
ordered_ifs_with_hints/50/8        23191 ns      23190 ns      30028
ordered_ifs_with_hints/75/4         3165 ns       3165 ns     218492
ordered_ifs_with_hints/75/8        13785 ns      13785 ns      50574
ordered_ifs_with_hints/100/4        1575 ns       1575 ns     437687
ordered_ifs_with_hints/100/8        3130 ns       3130 ns     221205
reversed_ifs_with_hints/50/4        6573 ns       6572 ns     105629
reversed_ifs_with_hints/50/8       27351 ns      27351 ns      25568
reversed_ifs_with_hints/75/4        3537 ns       3537 ns     197470
reversed_ifs_with_hints/75/8       16130 ns      16130 ns      43279
reversed_ifs_with_hints/100/4       3737 ns       3737 ns     187583
reversed_ifs_with_hints/100/8       7446 ns       7446 ns      93782

Анализ

1. Заказ имеет значение

Для 4K итераций и (почти) 100% вероятности очень любимого заявления разница огромна 223%:

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/100/4                   1673 ns       1673 ns     417073
reversed_ifs/100/4                  3738 ns       3738 ns     188087

Для 4K итераций и 50% вероятности очень любимого заявления разница составляет около 14%:

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4                    4660 ns       4658 ns     150948
reversed_ifs/50/4                   5342 ns       5341 ns     126800

2. Число итераций имеет значение

Разница между итерациями 4K и 8K для (почти) 100% -ной вероятности очень любимого утверждения примерно в два раза (как и ожидалось):

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/100/4                   1673 ns       1673 ns     417073
ordered_ifs/100/8                   3381 ns       3381 ns     207612

Но разница между итерациями 4K и 8K для 50% -ной вероятности очень любимого заявления составляет 5,5 раз:

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4                    4660 ns       4658 ns     150948
ordered_ifs/50/8                   25636 ns      25635 ns      27852

Почему так? Из-за провалов ветки. Здесь промахиваются ветки для каждого упомянутого выше случая:

ordered_ifs/100/4    0.01% of branch-misses
ordered_ifs/100/8    0.01% of branch-misses
ordered_ifs/50/4     3.18% of branch-misses
ordered_ifs/50/8     15.22% of branch-misses

Таким образом, на моем i5 предиктор ветвления неэффективно возникает для не столь вероятных ветвей и больших наборов данных.

3. Советы по подсказке

Для 4K итераций результаты несколько хуже для вероятности 50% и несколько лучше для почти 100% вероятности:

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/4                    4660 ns       4658 ns     150948
ordered_ifs/100/4                   1673 ns       1673 ns     417073
ordered_ifs_with_hints/50/4         5551 ns       5551 ns     125160
ordered_ifs_with_hints/100/4        1575 ns       1575 ns     437687

Но для 8K итераций результаты всегда немного лучше:

---------------------------------------------------------------------
Benchmark                              Time           CPU Iterations
---------------------------------------------------------------------
ordered_ifs/50/8                   25636 ns      25635 ns      27852
ordered_ifs/100/8                   3381 ns       3381 ns     207612
ordered_ifs_with_hints/50/8        23191 ns      23190 ns      30028
ordered_ifs_with_hints/100/8        3130 ns       3130 ns     221205

Итак, подсказки также помогают, но всего лишь немного.

Общий вывод: всегда оценивает код, потому что результаты могут удивить.

Надеюсь, что это поможет.

Ответ 5

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

  • Относительная вероятность каждой ветки. Это оригинальный вопрос, который был задан. На основе существующих ответов, похоже, есть некоторые условия, при которых упорядочение по вероятности помогает, но, похоже, это не всегда так. Если относительные вероятности не сильно отличаются друг от друга, то вряд ли они будут иметь какое-то значение в том, в каком порядке они находятся. Однако, если первое условие имеет место 99,999% времени, а следующее - это часть того, что осталось, то я бы предположим, что наиболее вероятным первым было бы выгодно с точки зрения сроков.
  • Стоимость вычисления истинного/ложного условия для каждой ветки. Если временная стоимость тестирования условий действительно высока для одной ветки по сравнению с другой, то это, вероятно, окажет значительное влияние на времени и эффективности. Например, рассмотрим условие, которое требует вычисления 1 единицы времени (например, проверка состояния логической переменной) по сравнению с другим условием, которое вычисляет десятки, сотни, тысячи или даже миллионы единиц времени (например, проверка содержимого файл на диске или выполнение сложного SQL-запроса с большой базой данных). Предполагая, что код проверяет условия в порядке каждый раз, более быстрые условия, вероятно, должны быть первыми (если только они не зависят от других условий, которые не срабатывают в первую очередь).
  • Компилятор/интерпретатор Некоторые компиляторы (или интерпретаторы) могут включать в себя оптимизацию одного вида другого, который может повлиять на производительность (и некоторые из них присутствуют только в том случае, если во время компиляции и/или исполнения). Поэтому, если вы не сравниваете две компиляции и исполнения идентичного кода в той же системе, используя тот же самый компилятор, где единственная разница - это порядок рассматриваемых ветвей, вам придется дать некоторую свободу действий для вариантов компилятора.
  • Операционная система/аппаратное обеспечение Как упоминалось luk32 и Yakk, у различных ЦП есть свои собственные оптимизации (как и в операционных системах). Таким образом, тесты снова подвержены изменениям здесь.
  • Частота выполнения кода кода. Если к блоку, который включает ветки, редко можно получить доступ (например, только один раз во время запуска), то, вероятно, очень мало того, какой порядок вы ставите ветки. С другой стороны, если ваш код забивает этот блок кода во время критической части вашего кода, упорядочение может иметь большое значение (в зависимости от контрольных показателей).

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

Мое личное эмпирическое правило (для большинства случаев, при отсутствии эталона) заключается в том, чтобы заказать на основе:

  • Условия, которые полагаются на результат предыдущих условий,
  • Стоимость вычисления условия, затем
  • Относительная вероятность каждой ветки.

Ответ 6

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

if (likely(access_ok(VERIFY_READ, from, n))) {
    kasan_check_write(to, n);
    res = raw_copy_from_user(to, from, n);
}
if (unlikely(res))
    memset(to + (n - res), 0, res);

Здесь предполагается, что проверка доступа пройдет, и что в res не возвращается ошибка. Попытка изменить порядок этих предложений if просто смущает код, но макросы likely() и unlikely() действительно помогают читаемости, указывая, что является нормальным случаем и каково исключение.

Реализация Linux этих макросов использует специальные функции GCC. Кажется, что clang и компилятор Intel C поддерживают один и тот же синтаксис, но MSVC не имеет такой функции.

Ответ 7

Также зависит от вашего компилятора и компиляции вашей платформы.

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

Обычно наиболее вероятным условием должно быть первое:

if (most_likely) {
     // most likely instructions
} else …

Самые популярные asms основаны на условных ветвях, которые прыгают, когда условие истинно. Этот код C, скорее всего, будет переведен на такое псевдо asm:

jump to ELSE if not(most_likely)
// most likely instructions
jump to end
ELSE:
…

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

Ответ 8

Я решил перезапустить тест на своей машине, используя код Lik32. Мне пришлось изменить его из-за моих окон или компилятора, считая, что высокое разрешение составляет 1 мс, используя

mingw32-g++. exe -O3 -Wall -std = С++ 11 -fexceptions -g

vector<int> rand_vec(10000000);

GCC сделал то же самое преобразование на обоих исходных кодах.

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

Обратный

.L233:
        mov     DWORD PTR [rsp+104], 0
        mov     DWORD PTR [rsp+100], 0
        mov     DWORD PTR [rsp+96], 0
        call    std::chrono::_V2::system_clock::now()
        mov     rbp, rax
        mov     rax, QWORD PTR [rsp+8]
        jmp     .L219
.L293:
        mov     edx, DWORD PTR [rsp+104]
        add     edx, 1
        mov     DWORD PTR [rsp+104], edx
.L217:
        add     rax, 4
        cmp     r14, rax
        je      .L292
.L219:
        mov     edx, DWORD PTR [rax]
        cmp     edx, 94
        jg      .L293 // >= 95
        cmp     edx, 19
        jg      .L218 // >= 20
        mov     edx, DWORD PTR [rsp+96]
        add     rax, 4
        add     edx, 1 // < 20 Sherlock
        mov     DWORD PTR [rsp+96], edx
        cmp     r14, rax
        jne     .L219
.L292:
        call    std::chrono::_V2::system_clock::now()

.L218: // further down
        mov     edx, DWORD PTR [rsp+100]
        add     edx, 1
        mov     DWORD PTR [rsp+100], edx
        jmp     .L217

And sorted

        mov     DWORD PTR [rsp+104], 0
        mov     DWORD PTR [rsp+100], 0
        mov     DWORD PTR [rsp+96], 0
        call    std::chrono::_V2::system_clock::now()
        mov     rbp, rax
        mov     rax, QWORD PTR [rsp+8]
        jmp     .L226
.L296:
        mov     edx, DWORD PTR [rsp+100]
        add     edx, 1
        mov     DWORD PTR [rsp+100], edx
.L224:
        add     rax, 4
        cmp     r14, rax
        je      .L295
.L226:
        mov     edx, DWORD PTR [rax]
        lea     ecx, [rdx-20]
        cmp     ecx, 74
        jbe     .L296
        cmp     edx, 19
        jle     .L297
        mov     edx, DWORD PTR [rsp+104]
        add     rax, 4
        add     edx, 1
        mov     DWORD PTR [rsp+104], edx
        cmp     r14, rax
        jne     .L226
.L295:
        call    std::chrono::_V2::system_clock::now()

.L297: // further down
        mov     edx, DWORD PTR [rsp+96]
        add     edx, 1
        mov     DWORD PTR [rsp+96], edx
        jmp     .L224

Так что это не говорит нам много, за исключением того, что последний случай не нуждается в предсказании ветки.

Теперь я пробовал все 6 комбинаций if, верхние 2 - оригинальные обратные и отсортированные. высокий = > 95, низкий - < 20, середина 20-94 с 10000000 итераций каждый.

high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns

high, low, mid: 44000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 45000000ns

high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns

high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 46000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 43000000ns

high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns

high, low, mid: 43000000ns
mid, low, high: 48000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 45000000ns
low, high, mid: 45000000ns

high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns

high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns

high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns

high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns

1900020, 7498968, 601012

Process returned 0 (0x0)   execution time : 2.899 s
Press any key to continue.

Итак, почему порядок высокий, низкий, средний, а затем быстрее (незначительно)

Потому что самая непредсказуемая последняя и поэтому никогда не запускается через предсказатель ветвления.

          if (i >= 95) ++nHigh;               // most predictable with 94% taken
          else if (i < 20) ++nLow; // (94-19)/94% taken ~80% taken
          else if (i >= 20 && i < 95) ++nMid; // never taken as this is the remainder of the outfalls.

Таким образом, ветки будут предсказаны взятыми, взятыми и остальными с помощью

6% + (0,94 *) 20% ошибочных прогнозов.

"Сортировка"

          if (i >= 20 && i < 95) ++nMid;  // 75% not taken
          else if (i < 20) ++nLow;        // 19/25 76% not taken
          else if (i >= 95) ++nHigh;      //Least likely branch

Ветви будут предсказаны с не взятыми, не взятыми и Шерлоком.

25% + (0,75 *) 24% ошибочных прогнозов

Давая разницу в 18-23% (измеренная разница ~ 9%), но нам нужно вычислять циклы вместо неверного предсказания%.

Предположим, что 17 циклов неправильно предсказывают штраф на моем процессоре Nehalem и что каждая проверка требует 1 цикла для выдачи (4-5 инструкций), и цикл также принимает один цикл. Зависимости данных - это счетчики и переменные цикла, но как только неверные предсказания не влияют на время.

Итак, для "обратного" мы получаем тайминги (это должна быть формула, используемая в компьютерной архитектуре: количественный подход IIRC).

mispredict*penalty+count+loop
0.06*17+1+1+    (=3.02)
(propability)*(first check+mispredict*penalty+count+loop)
(0.19)*(1+0.20*17+1+1)+  (= 0.19*6.4=1.22)
(propability)*(first check+second check+count+loop)
(0.75)*(1+1+1+1) (=3)
= 7.24 cycles per iteration

и то же для "отсортированного"

0.25*17+1+1+ (=6.25)
(1-0.75)*(1+0.24*17+1+1)+ (=.25*7.08=1.77)
(1-0.75-0.19)*(1+1+1+1)  (= 0.06*4=0.24)
= 8.26

(8.26-7.24)/8.26 = 13.8% против ~ 9% измерено (близко к измеренному!?!).

Таким образом, очевидность OP не очевидна.

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

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

Ответ 9

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

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

Ответ 10

Если вы уже знаете относительную вероятность выражения if-else, то для целей производительности было бы лучше использовать отсортированный путь, поскольку он будет проверять только одно условие (истинное).

Несортированным способом компилятор будет проверять все условия без необходимости и займет время.