Question

I have small but frequently used function objects. Each thread gets its own copy. Everything is allocated statically. Copies don't share any global or static data. Do I need to protect this objects from false sharing?

Thank you. EDIT: Here is a toy program which uses Boost.Threads. Can false sharing occur for the field data?

#include <boost/thread/thread.hpp>

struct Work {
    void operator()() {
        ++data;
    }

    int data;
};

int main() {
    boost::thread_group threads;
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work());
    threads.join_all();
}
Was it helpful?

Solution

False sharing between threads is when 2 or more threads use the same cache line.

E.g. :

struct Work {
    Work( int& d) : data( d ) {}
    void operator()() {
        ++data;
    }

    int& data;
};

int main() {
    int false_sharing[10] = { 0 };
    boost::thread_group threads;
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work(false_sharing[i]));
    threads.join_all();

    int no_false_sharing[10 * CACHELINE_SIZE_INTS] = { 0 };
    for (int i = 0; i < 10; ++i)
        threads.create_thread(Work(no_false_sharing[i * CACHELINE_SIZE_INTS]));
    threads.join_all();
}

The threads in the first block do suffer from false sharing. The threads in the second block do not (thanks to CACHELINE_SIZE).

Data on the stack is always 'far' away from other threads. (E.g. under windows, at least a couple of pages).

With your definition of a function object, false sharing can appear, because the instances of Work get created on the heap and this heap space is used inside the thread.

This may lead to several Work instances to be adjacent and so may incur sharing of cache lines.

But ... your sample does not make sense, because data is never touched outside and so false sharing is induced needlessly.

The easiest way, to prevent problems like this, is to copy your 'shared' data locally on tho the stack, and then work on the stack copy. When your work is finished copy it back to the output var.

E.g:

struct Work {
    Work( int& d) : data( d ) {}
    void operator()()
    {
        int tmp = data;
        for( int i = 0; i < lengthy_op; ++i )
           ++tmp;
        data = tmp;
    }

    int& data;
};

This prevents all problems with sharing.

OTHER TIPS

I did a fair bit of research and it seems there is no silver bullet solution to false sharing. Here is what I come up with (thanks to Christopher): 1) Pad your data from both sides with unused or less frequently used stuff. 2) Copy your data into stack and copy it back after all hard work is done. 3) Use cache aligned memory allocation.

I' don't feel entirely safe with the details, but here's my take:

(1) Your simplified example is broken since boost create_thread expects a reference, you pass a temporary.

(2) if you'd use vector<Work> with one item fro each thread, or othrwise have them in memory sequentially, false sharing will occur.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top