mardi 18 janvier 2022

Atomic variable vs Normal variable with locks in C++

Recently I gave an interview to a tech company and the interviewer asked me to tell which of the operation is faster among a normal increment operation on atomic variable and increment operation on a normal variable with locks. (This came as a sub question of an original question which goes out of context)

As I don't know what's going under the hood, I gave the reason as one hardware instruction for one increment instead of three and I claimed atomic to be faster.

Now after the interview, while I though of extracting the solution, I found this happening.

Code I've written to test:

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <atomic>
#include <cmath>
using namespace std;

int iters = 1;

class Timer{
private:
    std::chrono::time_point<std::chrono::high_resolution_clock> startTime,stopTime;
    string method_name;
    // stop function
    void stop(){
        stopTime = std::chrono::high_resolution_clock::now();
        auto start = std::chrono::time_point_cast<std::chrono::microseconds>(startTime).time_since_epoch().count();
        auto stop = std::chrono::time_point_cast<std::chrono::microseconds>(stopTime).time_since_epoch().count();
        auto duration = stop-start;
        cout<<"Time taken : "<<duration<<" μs"<<endl;
    }
public:
    // constructor
    Timer(){
        startTime = std::chrono::high_resolution_clock::now();
    }
    // destructor
    ~Timer(){
        stop();
    }
};

std::mutex Lock;
long n_variable = 0;
std::atomic<long> a_variable(0);

void updateAtomicVariable(){
    for(int i = 0 ; i < iters ; i++){
        a_variable++;
    }
}

void updateNormalVariableWithLocks(){
    for(int i = 0 ; i < iters ; i++){
        Lock.lock();
        n_variable++;
        Lock.unlock();
    }
}

int main(){

    int no_th = 1;
    std::thread atomic_threads[10];
    std::thread normal_threads[10];

    // updating once
    cout<<"Updating atomic variable once"<<endl;
    {
        Timer timer;
        for(int i = 0 ; i < no_th ; i++){
            atomic_threads[i] = std::thread(updateAtomicVariable);
        }

        for(int i = 0 ; i < no_th ; i++){
            atomic_threads[i].join();
        }
    }
    cout<<"Updating normal variable once with locks"<<endl;
    {
        Timer timer;
        for(int i = 0 ; i < no_th ; i++){
            normal_threads[i] = std::thread(updateNormalVariableWithLocks);
        }

        for(int i = 0 ; i < no_th ; i++){
            normal_threads[i].join();
        }
    }

    no_th = 10;
    iters = 1e7;
    //updating multiple times
    cout<<"Updating atomic variable 1e8 times with 10 threads"<<endl;
    {
        Timer timer;
        for(int i = 0 ; i < no_th ; i++){
            atomic_threads[i] = std::thread(updateAtomicVariable);
        }

        for(int i = 0 ; i < no_th ; i++){
            atomic_threads[i].join();
        }
    }
    cout<<"Updating normal variable 1e8 times with 10 threads and with locks"<<endl;
    {
        Timer timer;
        for(int i = 0 ; i < no_th ; i++){
            normal_threads[i] = std::thread(updateNormalVariableWithLocks);
        }

        for(int i = 0 ; i < no_th ; i++){
            normal_threads[i].join();
        }
    }
}

Interestingly I got the output as :

Updating atomic variable once
Time taken : 747 μs
Updating normal variable once with locks
Time taken : 255 μs
Updating atomic variable 1e8 times with 10 threads
Time taken : 1806461 μs
Updating normal variable 1e8 times with 10 threads and with locks
Time taken : 12974378 μs

Although the numbers are varying each and every time, the relative magnitude remained same.

This is showing that for one increment operation atomic increment is slower and on the other hand while incrementing the same for multiple times it is showing atomic increment is much faster which is the contrary to first recorded observation of one increment.

I ran this on multiple machines and multiple times and found the same.

Could some one help me to understand what's going on here. Thanks in advance.

Aucun commentaire:

Enregistrer un commentaire