Я пытаюсь измерить издержки различных параметров синхронизации, когда нет конфликтов. Я использую следующую программу:
#include <atomic>
#include <chrono>
#include <iostream>
#include <mutex>
void function() {
static volatile uint64_t counter = 0;
counter++;
}
void function2() {
std::atomic<uint64_t> counter2 = 0;
counter2++;
}
int main() {
// warm up the cache
std::mutex lock;
for( int i=0; i<1'000'000; ++i ) {
std::lock_guard<std::mutex> locker(lock);
function();
function2();
}
std::cout<<"Starting test\n";
auto start = std::chrono::high_resolution_clock::now();
for( int i=0; i<1'000'000; ++i ) {
std::lock_guard<std::mutex> locker(lock);
function();
}
auto end = std::chrono::high_resolution_clock::now();
std::cout<<" With lock took "<<std::chrono::ceil<std::chrono::nanoseconds>(end-start).count()<<"ns\n";
start = std::chrono::high_resolution_clock::now();
for( int i=0; i<1'000'000; ++i ) {
function();
}
end = std::chrono::high_resolution_clock::now();
std::cout<<" No lock took "<<std::chrono::ceil<std::chrono::nanoseconds>(end-start).count()<<"ns\n";
start = std::chrono::high_resolution_clock::now();
for( int i=0; i<1'000'000; ++i ) {
function2();
}
end = std::chrono::high_resolution_clock::now();
std::cout<<"Atomic lock took "<<std::chrono::ceil<std::chrono::nanoseconds>(end-start).count()<<"ns\n";
}
При компиляции с помощью gcc или clang я получаю похожие результаты:
$ clang++-7 -g -O3 -std=c++2a locking.cpp -o locking && ./locking
Starting test
With lock took 2099204ns
No lock took 2126724ns
Atomic lock took 12922543ns
Таким образом, с блокировкой или без нее результаты довольно схожи (обычно без блокировки немного быстрее), но с атомарными операциями я получаю снижение производительности в 6 раз.
Логика говорит, что без конфликта все соответствующие переменные будут локальными по отношению к кэшу работающего ЦП, и все параметры синхронизации приведут к более или менее одинаковой производительности.
Чего мне не хватает?