Резюме: Я ожидал, что std::atomic<int*>::load
с std::memory_order_relaxed
было бы близко к производительности простой загрузки указателя напрямую, по крайней мере, когда загруженное значение редко изменяется. Я видел гораздо худшую производительность для атомарной нагрузки, чем обычная загрузка в Visual Studio C ++ 2012, поэтому я решил исследовать. Оказывается, атомная нагрузка реализована в виде сравнения и замены цикл, который я подозреваю, не самая быстрая реализация.
Вопрос: Есть ли какая-то причина, по которой std::atomic<int*>::load
нужно сделать цикл сравнения и обмена?
ФонЯ полагаю, что MSVC ++ 2012 выполняет цикл сравнения и замены при атомной загрузке указателя на основе этой тестовой программы:
#include <atomic>
#include <iostream>
template<class T>
__declspec(noinline) T loadRelaxed(const std::atomic<T>& t) {
return t.load(std::memory_order_relaxed);
}
int main() {
int i = 42;
char c = 42;
std::atomic<int*> ptr(&i);
std::atomic<int> integer;
std::atomic<char> character;
std::cout
<< *loadRelaxed(ptr) << ' '
<< loadRelaxed(integer) << ' '
<< loadRelaxed(character) << std::endl;
return 0;
}
Я использую __declspec(noinline)
функция для того, чтобы изолировать инструкции по сборке, связанные с атомной нагрузкой. Я сделал новый проект MSVC ++ 2012, добавил платформу x64, выбрал конфигурацию выпуска, запустил программу в отладчике и посмотрел на разборку. Оказывается, что оба std::atomic<char>
а также std::atomic<int>
параметры в конечном итоге дают один и тот же вызов loadRelaxed<int>
— это должно быть что-то, что сделал оптимизатор. Вот разборка двух экземпляров loadRelaxed, которые вызываются:
loadRelaxed<int * __ptr64>
000000013F4B1790 prefetchw [rcx]
000000013F4B1793 mov rax,qword ptr [rcx]
000000013F4B1796 mov rdx,rax
000000013F4B1799 lock cmpxchg qword ptr [rcx],rdx
000000013F4B179E jne loadRelaxed<int * __ptr64>+6h (013F4B1796h)
loadRelaxed<int>
000000013F3F1940 prefetchw [rcx]
000000013F3F1943 mov eax,dword ptr [rcx]
000000013F3F1945 mov edx,eax
000000013F3F1947 lock cmpxchg dword ptr [rcx],edx
000000013F3F194B jne loadRelaxed<int>+5h (013F3F1945h)
Инструкция lock cmpxchg
атомно сравнения и замены и мы видим здесь, что код для атомной загрузки char
, int
или int*
это цикл сравнения и обмена Я также создал этот код для 32-битной x86, и эта реализация все еще основана на lock cmpxchg
,
Вопрос: Есть ли какая-то причина, по которой std::atomic<int*>::load
нужно сделать цикл сравнения и обмена?
Я не верю, что расслабленные атомные нагрузки требуют сравнения и обмена. В конце концов, эта реализация std :: atomic была непригодна для моих целей, но я все же хотел иметь интерфейс, поэтому я создал собственный std :: atomic, используя барьерные свойства MSVC. Это имеет лучшую производительность, чем по умолчанию std::atomic
для моего случая использования. Вы можете увидеть код Вот. Предполагается, что он будет реализован в спецификации C ++ 11 для всех заказов на загрузку и хранение. Кстати GCC 4.6 не лучше в этом отношении. Я не знаю о GCC 4.7.
Других решений пока нет …