После прочтения статьи решил проверить как справляются с оптимизацией различные компиляторы на примере алгоритма STL std::count_if. Для этого был написан следующий код:

#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>

int
main(int argc, char **argv)
{
    std::mt19937 gen;
    std::uniform_int_distribution<int> dist(0, 10);
    std::vector<int> arr(1000000);
    for (auto& item : arr) {
        item = dist(gen);
    }
    
    size_t total_time = 0;
    size_t count_times = 0;
    for (decltype(arr)::value_type num = 0; num <= 10; ++num) {
        const auto start_time = std::chrono::high_resolution_clock::now();
        count_times = 0;
        for (size_t iter = 0; iter < 100; ++iter) {
            count_times += std::count_if(std::begin(arr), std::end(arr), [&num](int elem){ return (elem < num);});
        }
        const auto end_time = std::chrono::high_resolution_clock::now();
        const auto t = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
        std::cout << num << " - " << t << "\n";
        total_time += t;
    }
    std::cout << "total_time  = " << total_time << "\n";
    std::cout << "count_times = " << count_times << "\n";

    return 0;
}


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

for (size_t iter = 0; iter < 100; ++iter) {
    count_times += std::count_if(std::begin(arr), std::end(arr), [&num](int elem){ return (elem < num);});
}
после оптимизации. Версия компилятора, ключи компиляции и результаты работы можно увидеть на следующем скриншоте:



Т.е. время, затраченное на подсчет элементов, значение которых меньше 0 — это время работы цикла, без выполнения команды инкремента (поскольку у нас в векторе нет элементов, меньших 0). Также можно заметить, что время, затраченное на подсчет элементов, значение которых меньше 5 в 6 раз превосходит время работы цикла, без выполнения команды инкремента. Неужели инкремент занимает столько много времени? Для этого данный код был откомпилирован на g++ (вресия компилятор, ключи компиляции и результаты работы представлены на скриншоте):



Т.е. видно, что g++ справился с задачей на отлично: время выполнения практически в 3 раза быстрее, чем у cl.exe. Если посмотреть на дизассемблерный фрагмент кода:
count_times += std::count_if(std::begin(arr), std::end(arr), [&num](int elem){ return (elem < num);});
, то станет ясно, что причиной всему — предсказатель переходов (branch predictor). Вот ассемблерный код от компилятора cl.exe:



А вот как с этим же заданием справился g++:



Видно, что cl.exe использовал условный переход, в то время как g++ использовал ассемблерную команду cmovl (conditional move if less), поэтому программа, откомпилированная с помощью g++ работает быстрее, поскольку процессору не пришлось предсказывать переход и очищать кеш-линии в случае неудачно предсказанного перехода.