C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
Закладка:
Векторизация — это свойство, которое должны поддерживать как процессор, так и компилятор. Кратко рассмотрим простой пример, чтобы понять суть векторизации и как она работает. Допустим, нужно сложить числа, находящиеся в очень большом векторе. Простая реализация данной задачи может выглядеть так:
std::vector<int> v {1, 2, 3, 4, 5, 6, 7 /*...*/};
int sum {std::accumulate(v.begin(), v.end(), 0)};
Компилятор в конечном счете сгенерирует цикл из вызова accumulate, который может выглядеть следующим образом:
int sum {0};
for (size_t i {0}; i < v.size(); ++i) {
sum += v[i];
}
С этого момента при разрешенной и включенной векторизации компилятор может создать следующий код. Цикл выполняет четыре шага сложения в одной итерации цикла, что сокращает количество итераций в четыре раза. Для простоты пример не работает с остатком, если вектор не содержит N*4 элементов:
int sum {0};
for (size_t i {0}; i < v.size() / 4; i += 4) {
sum += v[i] + v[i+1] + v[i + 2] + v[i + 3];
}
// если операция v.size()/4 имеет остаток,
// в реальном коде также нужно это обработать.
Зачем это делать? Многие процессоры предоставляют инструкции, которые могут выполнять математические операции наподобие sum += v[i]+v[i+1]+v[i+2]+v[i+3]; всего за один шаг. Сжатие большого количества математических операций в минимальное количество инструкций — наша цель, поскольку это ускоряет программу.
Автоматическую векторизацию выполнять сложно, поскольку компилятору нужно в некоторой степени понимать нашу программу, чтобы ускорить ее, не нарушая правильности. По крайней мере помочь компилятору можно, используя стандартные алгоритмы максимально часто, поскольку компилятору проще понять их, чем запутанные циклы со сложными зависимостями.
Приостанавливаем программу на конкретный промежуток времени
Простая и удобная возможность управления потоками добавлена в С++11. В данной версии появилось пространство имен this_thread, содержащее функции, которые влияют только на вызывающий поток. Оно включает две разные функции, позволяющие приостановить поток на определенный промежуток времени, это позволяет перестать использовать внешние библиотеки или библиотеки, зависящие от операционной системы.
В этом примере мы сконцентрируемся на том, как приостанавливать потоки на определенный промежуток времени.
Как это делается
В этом примере мы напишем короткую программу, которая приостанавливает основной поток на определенные промежутки времени.
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространств имен std и chrono_literals. Второе пространство содержит удобные аббревиатуры для описания промежутков времени:
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
using namespace chrono_literals;
2. Сразу же приостановим основной поток на 5 секунд 300 миллисекунд. Благодаря пространству имен chrono_literals можем выразить эти промежутки времени в читабельном формате:
int main()
{
cout << "Going to sleep for 5 seconds"
" and 300 milli seconds.n";
this_thread::sleep_for(5s + 300ms);
3. Последним выражением приостановки являлось relative. Кроме того, можно выразить запросы absolute на приостановку. Приостановим поток на 3 секунды, начиная с текущего момента:
cout << "Going to sleep for another 3 seconds.n";
this_thread::sleep_until(
chrono::high_resolution_clock::now() + 3s);
4. Перед завершением программы выведем на экран какое-нибудь сообщение, что укажет на окончание второго периода приостановки.
cout << "That's it.n";
}
5. Компиляция и запуск программы дадут следующие результаты. В Linux, Mac и других UNIX-подобных операционных системах имеется команда time, принимающая другую команду, чтобы выполнить ее и определить время, которое требуется на ее выполнение. Запуск нашей программы с помощью команды time показывает: она работала 8,32 секунды, это значение примерно равно 5,3 и 3 секундам, на которые мы приостанавливали программу. При запуске программы можно определить промежуток времени между появлением строк, выводимых на консоль:
$ time ./sleep
Going to sleep for 5 seconds and 300 milli seconds.
Going to sleep for another 3 seconds.
That's it.
real 0m8.320s
user 0m0.005s
sys 0m0.003s
Как это работает
Функции sleep_for и sleep_until появились в версии C++11 и находятся в пространстве имен std::this_thread. Они блокируют выполнение текущего потока (но не процесса или программы) на конкретный промежуток времени. Поток не потребляет время процессора на протяжении блокировки. Он просто помещается операционной системой в неактивное состояние. ОС, конечно же, напоминает себе о необходимости возобновить поток. Самое лучшее заключается в том, что нам не придется волноваться, в какой операционной системе запущена программа, поскольку эту информацию от нас абстрагирует STL.
Функция this_thread::sleep_for принимает значение типа chrono::duration. В простейшем случае это просто 1s или 5s+300ms, как это было показано в нашем примере кода. Чтобы получить возможность применять такие удобные литералы, нужно объявить об использовании пространства имен std::chrono_literals;.
Функция this_thread::sleep_until принимает значение типа chrono::time_point вместо промежутка времени. Это удобно в том случае, если нужно приостановить поток до наступления конкретного момента времени.
Точность момента пробуждения зависит от операционной системы. Она будет довольно высокой для большинства ОС, но могут возникнуть проблемы, если требуется точность вплоть до наносекунд.
Приостановить выполнение потока на короткий промежуток времени также можно с помощью функции this_thread::yield. Она не принимает аргументы; это значит, что неизвестно, как надолго будет отложено выполнение потока. Причина заключается в следующем: данная функция не знает о том, как приостанавливать потоки на какое-то время. Она просто говорит ОС, что может перепланировать выполнение других потоков любых процессов. Если таких потоков нет, то поток возобновится мгновенно. По этой причине функция yield зачастую менее полезна, чем приостановка на короткий промежуток времени, установленный заранее.
Запускаем и приостанавливаем потоки
Еще одним дополнением, появившимся в C++11, является класс std::thread. Он предоставляет простой способ запускать и приостанавливать потоки, не прибегая к использованию внешних библиотек и знаний о том, в какой операционной системе запущен процесс. Все это включено в STL.
В данном примере мы реализуем программу, которая запускает и останавливает потоки. Далее рассмотрим информацию о том, что с ними делать после запуска.
Как это делается
В этом примере мы запустим несколько потоков и увидим, как ведет себя программа, когда мы задействуем несколько ядер процессора, чтобы выполнить разные части ее кода одновременно.
1. Сначала включим