Di seguito sono disponibili due versioni di spinlock. Il primo usa il default che è memory_order_cst mentre il secondo usa memory_order_acquire/memory_order_release. Dal momento che quest'ultimo è più rilassato, mi aspetto che abbia prestazioni migliori. Tuttavia non sembra essere il caso.Perché ho prestazioni peggiori per la mia implementazione di spinlock quando utilizzo un modello di memoria non cst?
class SimpleSpinLock
{
public:
inline SimpleSpinLock(): mFlag(ATOMIC_FLAG_INIT) {}
inline void lock()
{
int backoff = 0;
while (mFlag.test_and_set()) { DoWaitBackoff(backoff); }
}
inline void unlock()
{
mFlag.clear();
}
private:
std::atomic_flag mFlag = ATOMIC_FLAG_INIT;
};
class SimpleSpinLock2
{
public:
inline SimpleSpinLock2(): mFlag(ATOMIC_FLAG_INIT) {}
inline void lock()
{
int backoff = 0;
while (mFlag.test_and_set(std::memory_order_acquire)) { DoWaitBackoff(backoff); }
}
inline void unlock()
{
mFlag.clear(std::memory_order_release);
}
private:
std::atomic_flag mFlag = ATOMIC_FLAG_INIT;
};
const int NUM_THREADS = 8;
const int NUM_ITERS = 5000000;
const int EXPECTED_VAL = NUM_THREADS * NUM_ITERS;
int val = 0;
long j = 0;
SimpleSpinLock spinLock;
void ThreadBody()
{
for (int i = 0; i < NUM_ITERS; ++i)
{
spinLock.lock();
++val;
j = i * 3.5 + val;
spinLock.unlock();
}
}
int main()
{
vector<thread> threads;
for (int i = 0; i < NUM_THREADS; ++i)
{
cout << "Creating thread " << i << endl;
threads.push_back(std::move(std::thread(ThreadBody)));
}
for (thread& thr: threads)
{
thr.join();
}
cout << "Final value: " << val << "\t" << j << endl;
assert(val == EXPECTED_VAL);
return 1;
}
Sono in esecuzione su Ubuntu 12.04 con gcc 4.8.2 in esecuzione ottimizzazione O3.
- Spinlock con memory_order_cst:
Run 1:
real 0m1.588s
user 0m4.548s
sys 0m0.052s
Run 2:
real 0m1.577s
user 0m4.580s
sys 0m0.032s
Run 3:
real 0m1.560s
user 0m4.436s
sys 0m0.032s
- Spinlock con memory_order_acquire/release:
Run 1:
real 0m1.797s
user 0m4.608s
sys 0m0.100s
Run 2:
real 0m1.853s
user 0m4.692s
sys 0m0.164s
Run 3:
real 0m1.784s
user 0m4.552s
sys 0m0.124s
Run 4:
real 0m1.475s
user 0m3.596s
sys 0m0.120s
Con il modello più rilassato, vedo molto più variabilità. A volte è meglio. Spesso è peggio, qualcuno ha una spiegazione per questo?
Cosa succede se si rimuove il backoff? (Come regola generale, ti consigliamo di eseguire la lettura anziché di un'opzione atomica). – kec
Per GCC su Intel, mi aspetto che si comportino in modo identico se non generano esattamente lo stesso codice. Hai confrontato l'output di assembly di entrambe le versioni di 'ThreadBody'? – Casey
@Casey: si scopre che c'è un recinto aggiuntivo nel modello CST. Dovrei pensare molto seriamente per avere un'opinione sul fatto che sia davvero necessario. – kec