This is not the current version of the class.

Lecture 19: Multicore scalability

Notes by Thomas Lively

Front matter

Section tomorrow on multicore scalability

Multiprocessors

Shared-memory multiprocessors

MESI protocol

No state allows for having out of date data

Having Modified state allows for batching of writes

Programming consideration

Cache line bouncing is super expensive

Memory is expensive when it's contended (many cores writing)

Fairness-efficiency tradeoff

Want frequently read data to be in Shared state, so it shouldn't share cache lines with write-heavy data (i.e. false sharing)

How to debug false sharing?

Lock implementation

struct lock {
    int v_;
    
    void lock() {
        while (atomic_test_and_set(&v_, 1) == 1) {
            pause();   // “relax CPU until something happens”
        }
    }
    
    void unlock() {
        v_ = 0;
    }
}

Need special atomic instructions, not normal reads and writes.

The Chickadee spinlock uses the std::atomic_flag type, which is specialized for this purpose (although in practice on x86 it’s implemented the same way):

struct lock {
    std::atomic_flag f_;

    void lock() {
        while (f_.test_and_set()) {
            pause();
        }
    }

    void unlock() {
        f_.clear();
    }
}

Memory Models

initial conditions: x = y = 0

    TA           TB
(A1) x = 1    (B1) y = 1
(A2) t1 = y   (B2) t2 = x

t1 = t2 = 1

t1 = 0, t2 = 1

t1 = 1, t2 = 0

But what about t1 = t2 = 0?

How does this work with the MESI protocol?

processor does a store

Need hardware support for atomics because processor cannot see into other store buffers

TSO - total store order model: All processors agree on order of writes to main memory

Related: Threads cannot be implemented as a library

Lock implementation continued

Ticket lock

struct tlock {
    std::atomic<unsigned> now_;
    std::atomic<unsigned> next_;
    
    void lock() {
        unsigned my_ticket = next_++;
        while (my_ticket != now_.load(std::memory_order_relaxed)) {
            // `my_ticket != now_` works fine too
            pause();
        }
    }
    
    void unlock() {
        ++now_;
    }
}