атомарные операции занимают больше времени, чем блокировка (без конфликтов) - PullRequest
2 голосов
/ 12 июня 2019

Я пытаюсь измерить издержки различных параметров синхронизации, когда нет конфликтов. Я использую следующую программу:

#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 раз.

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

Чего мне не хватает?

1 Ответ

1 голос
/ 12 июня 2019

Проблемы с эталонной программой.

Во-первых, std::atomic не был объявлен static, что привело к использованию нового на каждой итерации. Вторая проблема заключается в том, что программа не была скомпилирована с -pthread, в результате чего код блокировки стал недоступным (что объясняет, почему код с блокировкой работает с той же скоростью, что и код без).

Исправление обеих этих проблем приводит к:

Starting test
  With lock took 21013047ns
    No lock took 2125868ns
Atomic lock took 6744567ns

Таким образом, атомарный подход примерно в 3 раза медленнее, чем без блокировки, а фактическая блокировка примерно в 10 раз медленнее.

...