Chromium Code Reviews| Index: content/common/gamepad_seqlock.h |
| =================================================================== |
| --- content/common/gamepad_seqlock.h (revision 112429) |
| +++ content/common/gamepad_seqlock.h (working copy) |
| @@ -6,41 +6,92 @@ |
| #define CONTENT_COMMON_GAMEPAD_SEQLOCK_H_ |
| #include "base/atomicops.h" |
| -#include "base/threading/platform_thread.h" |
| #include "content/common/content_export.h" |
| namespace content { |
| // This SeqLock handles only *one* writer and multiple readers. It may be |
| -// suitable for low-contention with relatively infrequent writes, and many |
| -// readers. See: |
| +// suitable for high read frequency scenarios and is especially useful in shared |
| +// memory environment. See for the basic idea: |
| // http://en.wikipedia.org/wiki/Seqlock |
| -// http://www.concurrencykit.org/doc/ck_sequence.html |
| -// This implementation is based on ck_sequence.h from http://concurrencykit.org. |
| -// |
| -// Currently, this is used in only one location. It may make sense to |
| -// generalize with a higher-level construct that owns both the lock and the |
| -// data buffer, if it is to be used more widely. |
| -// |
| -// You must be very careful not to operate on potentially inconsistent read |
| -// buffers. If the read must be retry'd, the data in the read buffer could |
| -// contain any random garbage. e.g., contained pointers might be |
| -// garbage, or indices could be out of range. Probably the only suitable thing |
| -// to do during the read loop is to make a copy of the data, and operate on it |
| -// only after the read was found to be consistent. |
| +// However, this implementation is an improved lock-free variant. |
| +// The SeqLock can hold only POD fully-embed data (no pointers |
| +// to satellite data), copies are done with memcpy. |
| +template<typename T> |
| class CONTENT_EXPORT GamepadSeqLock { |
|
darin (slow to review)
2011/12/01 17:45:16
nit: since this is all implemented inline, there i
dvyukov
2011/12/01 21:27:28
Will do.
|
| public: |
| GamepadSeqLock(); |
| - base::subtle::Atomic32 ReadBegin(); |
| - bool ReadRetry(base::subtle::Atomic32 version); |
| - void WriteBegin(); |
| - void WriteEnd(); |
| + void ReadTo(T* obj); |
| + void Write(T const* obj); |
| private: |
| - base::subtle::Atomic32 sequence_; |
| + typedef base::subtle::Atomic32 Atomic32; |
| + static size_t const kSize = sizeof(T) / sizeof(Atomic32) + 1; |
| + static size_t const kEntries = 2; |
| + struct Entry { |
| + Atomic32 sequence_; |
| + Atomic32 data_[kSize]; |
| + }; |
| + Atomic32 current_; |
| + Entry entries_[kEntries]; |
| DISALLOW_COPY_AND_ASSIGN(GamepadSeqLock); |
| }; |
| +template<typename T> |
| +GamepadSeqLock<T>::GamepadSeqLock() |
| + : current_(0) { |
| + memset(entries_, 0, sizeof(entries_)); |
| +} |
| + |
| +template<typename T> |
| +void GamepadSeqLock<T>::ReadTo(T* obj) { |
| + using base::subtle::NoBarrier_Load; |
| + using base::subtle::Acquire_Load; |
| + using base::subtle::MemoryBarrier; |
| + for (;;) { |
|
scottmg
2011/12/01 22:36:39
Could you explain (perhaps in a comment here) why
|
| + Atomic32 cur = Acquire_Load(¤t_); |
| + Entry* ent = &entries_[cur % 2]; |
|
jbates
2011/12/01 19:31:12
I'm not sure this additional complexity is necessa
dvyukov
2011/12/01 21:27:28
Well, the current algo already has quite substanti
scottmg
2011/12/01 22:36:39
2 should be kEntries? (and below)
|
| + Atomic32 seq = Acquire_Load(&ent->sequence_); // membar #LoadLoad |
| + // If the counter is even, then the entry is not already current -> retry. |
| + if (seq % 2) |
| + continue; |
|
jbates
2011/12/01 19:31:12
Without some kind of yield or sleep, this can caus
dvyukov
2011/12/01 21:27:28
This algorithm does not require yield, it is compl
jbates
2011/12/02 21:40:14
After rereading the code 10 times or so :), I thin
|
| + // Copy out the entry. |
| + Atomic32 tmp[kSize]; |
| + for (size_t i = 0; i < kSize; ++i) |
|
scottmg
2011/12/01 22:36:39
This seems very subtle. Could you explain (again i
|
| + tmp[i] = NoBarrier_Load(&ent->data_[i]); |
|
jbates
2011/12/01 19:31:12
Why not store a volatile T in ent, and change this
dvyukov
2011/12/01 21:27:28
Because it leads to undefined behavior.
jbates
2011/12/02 21:40:14
I'd be interested in learning how you came to that
|
| + MemoryBarrier(); // membar #LoadLoad |
| + Atomic32 seq2 = NoBarrier_Load(&ent->sequence_); |
| + // If the counter is changed, then we've read inconsistend data -> retry. |
|
scottmg
2011/12/01 22:36:39
nit: 'inconsistent'
|
| + if (seq2 != seq) |
| + continue; |
|
jbates
2011/12/01 19:31:12
Less likely to cause problems assuming the writer
dvyukov
2011/12/01 21:27:28
yield is not required here.
jbates
2011/12/02 21:40:14
Is that because it can only happen once per call t
|
| + // Kickback to strict-aliasing. |
| + memcpy(reinterpret_cast<char*>(obj), tmp, sizeof(*obj)); |
| + break; |
| + } |
| +} |
| + |
| +template<typename T> |
| +void GamepadSeqLock<T>::Write(T const* obj) { |
|
jbates
2011/12/01 19:31:12
nit: consider changing this to const T&?
dvyukov
2011/12/01 21:27:28
Will do.
|
| + using base::subtle::NoBarrier_Store; |
| + using base::subtle::Release_Store; |
| + using base::subtle::MemoryBarrier; |
| + Atomic32 tmp[kSize]; |
| + // Kickback to strict-aliasing. |
| + memcpy(tmp, reinterpret_cast<char const*>(obj), sizeof(*obj)); |
|
jbates
2011/12/01 19:31:12
Why the extra memcpy?
dvyukov
2011/12/01 21:27:28
I don't know how to implement w/o it.
jbates
2011/12/02 21:40:14
It appears this is to prevent the compiler from re
|
| + // Get the non current entry... |
| + Entry* ent = &entries_[(current_ + 1) % 2]; |
| + // ... and mark it as inconsistent. |
| + NoBarrier_Store(&ent->sequence_, ent->sequence_ + 1); |
|
jbates
2011/12/01 19:31:12
If the writer thread calls Write multiple times wh
dvyukov
2011/12/01 21:27:28
Yes.
|
| + MemoryBarrier(); // membar #StoreStore |
| + // Copy in the entry. |
| + for (size_t i = 0; i < kSize; ++i) |
| + NoBarrier_Store(&ent->data_[i], tmp[i]); |
|
jbates
2011/12/01 19:31:12
Why not store a volatile T in ent, and change this
dvyukov
2011/12/01 21:27:28
It leads to undefined behavior, complicates manual
|
| + // Mark the entry as consistent again. |
| + Release_Store(&ent->sequence_, ent->sequence_ + 1); // membar #StoreStore |
| + // Switch the current entry. |
| + Release_Store(¤t_, current_ + 1); |
| +} |
| + |
| } // namespace content |
| #endif // CONTENT_COMMON_GAMEPAD_SEQLOCK_H_ |