Инструкции SSE: какие процессоры могут выполнять атомные операции памяти 16B?
Рассмотрим инструкцию SSE с одним доступом к памяти (одно чтение или одна запись, а не чтение + запись) на процессоре x86. Эта команда обращается к 16 байтам (128 битам) памяти, а доступ к ячейке памяти выравнивается до 16 байтов.
В документе "Технический документ по упорядочению памяти архитектуры Intel® 64" говорится, что для "Инструкций, которые читают или пишут четырехслойное слово (8 байтов), чей адрес выровнен по 8-байтовой границе", операция памяти, по-видимому, выполняется как один доступ к памяти независимо от тип памяти.
Вопрос: существуют ли процессоры Intel/AMD/etc x86, которые гарантируют, что чтение или запись 16 байтов (128 бит), выровненных по границе 16 байтов, выполняются как один доступ к памяти? Так, какой конкретно тип процессора (Core2/Atom/K8/Phenom/...)? Если вы предоставляете ответ (да / нет) на этот вопрос, укажите также метод, который использовался для определения ответа - поиск документа в формате PDF, тестирование методом грубой силы, математическое подтверждение или любой другой метод, который вы использовали для определения ответа.
Этот вопрос относится к таким проблемам, как http://research.swtch.com/2010/02/off-to-races.html
Обновить:
Я создал простую тестовую программу на C, которую вы можете запускать на своих компьютерах. Пожалуйста, скомпилируйте и запустите его на вашем Phenom, Athlon, Bobcat, Core2, Atom, Sandy Bridge или любом другом процессоре с поддержкой SSE2. Благодарю.
// Compile with:
// gcc -o a a.c -pthread -msse2 -std=c99 -Wall -O2
//
// Make sure you have at least two physical CPU cores or hyper-threading.
#include <pthread.h>
#include <emmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef int v4si __attribute__ ((vector_size (16)));
volatile v4si x;
unsigned n1[16] __attribute__((aligned(64)));
unsigned n2[16] __attribute__((aligned(64)));
void* thread1(void *arg) {
for (int i=0; i<100*1000*1000; i++) {
int mask = _mm_movemask_ps((__m128)x);
n1[mask]++;
x = (v4si){0,0,0,0};
}
return NULL;
}
void* thread2(void *arg) {
for (int i=0; i<100*1000*1000; i++) {
int mask = _mm_movemask_ps((__m128)x);
n2[mask]++;
x = (v4si){-1,-1,-1,-1};
}
return NULL;
}
int main() {
// Check memory alignment
if ( (((uintptr_t)&x) & 0x0f) != 0 )
abort();
memset(n1, 0, sizeof(n1));
memset(n2, 0, sizeof(n2));
pthread_t t1, t2;
pthread_create(&t1, NULL, thread1, NULL);
pthread_create(&t2, NULL, thread2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
for (unsigned i=0; i<16; i++) {
for (int j=3; j>=0; j--)
printf("%d", (i>>j)&1);
printf(" %10u %10u", n1[i], n2[i]);
if(i>0 && i<0x0f) {
if(n1[i] || n2[i])
printf(" Not a single memory access!");
}
printf("\n");
}
return 0;
}
Процессор у меня в ноутбуке - Core Duo (не Core2). Этот конкретный процессор не проходит тест, он реализует чтение / запись 16-байтовой памяти со степенью детализации 8 байт. Выход:
0000 96905702 10512
0001 0 0
0010 0 0
0011 22 12924 Not a single memory access!
0100 0 0
0101 0 0
0110 0 0
0111 0 0
1000 0 0
1001 0 0
1010 0 0
1011 0 0
1100 3092557 1175 Not a single memory access!
1101 0 0
1110 0 0
1111 1719 99975389
7 ответов
В Руководстве разработчика по архитектуре Intel® 64 и IA-32: Vol. 3А, которая в настоящее время содержит спецификации упомянутой вами белой книги по упорядочению памяти, в разделе 8.2.3.1, как вы сами отметили, сказано, что
Модель упорядочения памяти Intel-64 гарантирует, что для каждого из следующих инструкции доступа к памяти, кажется, что выполняется составляющая операция памяти как единый доступ к памяти: • Инструкции, которые читают или записывают один байт. • Инструкции, которые читают или пишут слово (2 байта), адрес которого выровнен на 2 граница байта. • Инструкции, которые читают или пишут двойное слово (4 байта), адрес которого выровнен на границе 4 байта. • Инструкции, которые читают или пишут четырехзначное слово (8 байт), адрес которого выровнен по 8-байтовая граница. Любая заблокированная инструкция (либо инструкция XCHG, либо другая операция чтения-изменения-записи). инструкция с префиксом LOCK) выполняется как неделимая и Непрерывная последовательность нагрузок (ий) с последующим хранением (ями) независимо от выравнивания.
Теперь, поскольку вышеприведенный список НЕ содержит один и тот же язык для двойного четырехсловного слова (16 байт), из этого следует, что архитектура НЕ гарантирует, что инструкции, которые обращаются к 16 байтам памяти, являются атомарными.
При этом, последний абзац намекает на выход, а именно инструкцию CMPXCHG16B с префиксом LOCK. Вы можете использовать инструкцию CPUID, чтобы выяснить, поддерживает ли ваш процессор CMPXCHG16B (бит функции "CX16").
В соответствующем документе AMD, AMD64 Technology, Руководство по программированию архитектуры AMD64, том 2: Системное программирование, я не могу найти аналогичного ясного языка.
РЕДАКТИРОВАТЬ: Результаты тестовой программы
(Тестовая программа изменена для увеличения количества итераций в 10 раз)
На Xeon X3450 (x86-64):
0000 999998139 1572 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 1861 999998428
На Xeon 5150 (32-разрядная версия):
0000 999243100 283087 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 756900 999716913
На Opteron 2435 (x86-64):
0000 999995893 1901 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 4107 999998099
Означает ли это, что Intel и / или AMD гарантируют, что 16-байтовый доступ к памяти является атомарным на этих машинах? ИМХО, это не так. Это не указано в документации как гарантированное архитектурное поведение, и, таким образом, никто не может знать, действительно ли на этих конкретных процессорах 16-байтовые обращения к памяти являются атомарными или тестовая программа просто не может вызвать их по той или иной причине. И поэтому полагаться на это опасно.
РЕДАКТИРОВАТЬ 2: Как сделать тестовую программу неудачной
Ха! Мне удалось заставить тестовую программу провалиться. На том же Opteron 2435, что и выше, с тем же двоичным файлом, но теперь запускающим его через инструмент "numactl", указывающий, что каждый поток работает на отдельном сокете, я получил:
0000 999998634 5990 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 1 Ни одного доступа к памяти! 1101 0 0 1110 0 0 1111 1366 999994009
Так что это значит? Что ж, Opteron 2435 может или не может гарантировать, что 16-байтовые обращения к памяти являются атомарными для доступа внутри сокетов, но по крайней мере протокол когерентности кэша, работающий на межсоединении HyperTransport между двумя сокетами, не дает такой гарантии.
РЕДАКТИРОВАТЬ 3: ASM для функций потока, по запросу "GJ."
Вот сгенерированный asm для потоковых функций для версии GCC 4.4 x86-64, используемой в системе Opteron 2435:
.globl thread2
.type thread2, @function
thread2:
.LFB537:
.cfi_startproc
movdqa .LC3(%rip), %xmm1
xorl %eax, %eax
.p2align 5,,24
.p2align 3
.L11:
movaps x(%rip), %xmm0
incl %eax
movaps %xmm1, x(%rip)
movmskps %xmm0, %edx
movslq %edx, %rdx
incl n2(,%rdx,4)
cmpl $1000000000, %eax
jne .L11
xorl %eax, %eax
ret
.cfi_endproc
.LFE537:
.size thread2, .-thread2
.p2align 5,,31
.globl thread1
.type thread1, @function
thread1:
.LFB536:
.cfi_startproc
pxor %xmm1, %xmm1
xorl %eax, %eax
.p2align 5,,24
.p2align 3
.L15:
movaps x(%rip), %xmm0
incl %eax
movaps %xmm1, x(%rip)
movmskps %xmm0, %edx
movslq %edx, %rdx
incl n1(,%rdx,4)
cmpl $1000000000, %eax
jne .L15
xorl %eax, %eax
ret
.cfi_endproc
и для полноты.LC3, который представляет собой статические данные, содержащие вектор (-1, -1, -1, -1), используемый thread2:
.LC3:
.long -1
.long -1
.long -1
.long -1
.ident "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"
.section .note.GNU-stack,"",@progbits
Также обратите внимание, что это синтаксис AT&T ASM, а не синтаксис Intel. Программисты Windows могут быть более знакомы с ним. Наконец, это с march=native, что делает GCC предпочтительным MOVAPS; но это не имеет значения, если я использую march=core2, он будет использовать MOVDQA для хранения в x, и я все еще могу воспроизвести сбои.
ISA x86 не гарантирует атомарности для чего-либо большего, чем 8B, поэтому реализации могут свободно реализовывать поддержку SSE / AVX, как это делают Pentium III / Pentium M / Core Duo: внутренняя обработка данных осуществляется в 64-битных половинах. 128-битный магазин сделан как два 64-битных магазина. В микроархитектуре Yonah (Core Duo) путь данных в / из кэша составляет всего 64 б. (источник: документ микроархиста Агнера Фога).
Более поздние реализации имеют более широкие пути данных внутри и обрабатывают 128-битные инструкции как одну операцию. Core 2 Duo (conroe/merom) был первым микроархивом Intel P6 со 128-битной передачей данных. (ИДК о P4, но, к счастью, он достаточно взрослый, чтобы быть совершенно неактуальным.)
Вот почему OP считает, что 128b операций не являются атомарными на Intel Core Duo (Yonah), но другие авторы считают, что они атомарны на более поздних разработках Intel, начиная с Core 2 (Merom).
Диаграммы в этой статье Realworldtech о Merom против Yonah показывают 128-битный путь между ALU и L1-кешем данных в Merom (и P4), в то время как Yonah с низким энергопотреблением имеет 64-битный путь данных. Путь данных между кешем L1 и L2 составляет 256b во всех 3 проектах.
Следующий скачок в ширине пути данных произошел с процессором Intel Haswell с загрузкой / хранением AVX/AVX2 256b (32B) и 64-байтовым путем между кэш-памятью L1 и L2. Я ожидаю, что 256b загрузки / хранилища являются атомарными в Haswell, Broadwell и Skylake, но у меня нет ни одного, чтобы проверить. Я забыл, если Skylake снова расширил пути при подготовке к AVX512 в Skylake-EP (серверная версия), или, возможно, первоначальная реализация AVX512 будет похожа на AVX SnB/IvB и будет иметь 512b загрузок / хранилищ, занимающих порт загрузки / хранения за 2 цикла.
Как указывает Яннеб в своем превосходном экспериментальном ответе, протокол когерентности кеша между сокетами в многоядерной системе может быть уже, чем то, что вы получаете в процессоре общего кэш-памяти последнего уровня. Для широких загрузок и хранилищ архитектурное требование в отношении атомарности отсутствует, поэтому разработчики могут делать их атомарными внутри сокета, но не атомарными через сокеты, если это удобно. IDK, насколько широк логический путь данных между сокетами для семейства AMD Bulldozer или для Intel. (Я говорю "логично", потому что даже если данные передаются небольшими порциями, они могут не изменить строку кэша, пока не будут полностью получены.)
Поиск похожих статей о процессорах AMD должен сделать разумные выводы о том, являются ли 128b операций атомарными или нет. Просто проверка таблиц инструкций является некоторой помощью:
К8 декодирует movaps reg, [mem]
до 2-х мегапикселей, в то время как K10 и семейство бульдозеров расшифровывают его до 1 мегапикселя AMD Bobcat с низким энергопотреблением декодирует его до 2 операций, а Jaguar - 128 Мовапа до 1 операции. (Он поддерживает AVX1, аналогичный процессорам семейства бульдозеров: 256-битные insns (даже операции ALU) разделены на две 128-битные операции. Intel SnB разделяет только 256-битные загрузки / хранилища, при этом имея ALU полной ширины.)
Opteron 2435 от janneb - это 6-ядерный процессор Istanbul, являющийся частью семейства K10, поэтому этот вывод с одним атомом m> op> выглядит точно в одном сокете.
Intel Silvermont выполняет 128b загрузок / хранилищ с одним мопом и пропускной способностью один за такт. Это то же самое, что и для целочисленных загрузок / хранилищ, так что вполне вероятно, что они атомарные.
В "Руководстве по программированию AMD, том 1: Программирование приложений" говорится в разделе 3.9.1: "CMPXCHG16B
может использоваться для выполнения 16-байтового атомарного доступа в 64-битном режиме (с некоторыми ограничениями выравнивания)."
Тем не менее, нет такого комментария о инструкциях SSE. На самом деле, в 4.8.3 есть комментарий, что префикс LOCK "вызывает исключение недопустимого кода операции при использовании с 128-битными инструкциями носителя". Поэтому мне кажется довольно убедительным, что процессоры AMD НЕ гарантируют атомарный 128-битный доступ для инструкций SSE, и единственный способ сделать атомарный 128-битный доступ - это использовать CMPXCHG16B
,
В " Руководстве по программному обеспечению для архитектуры Intel 64 и IA-32", том 3А: Руководство по системному программированию, часть 1"в 8.1.1 говорится:" Инструкция x87 или инструкция SSE, которая обращается к данным больше четырехугольного слова, может быть реализована с использованием нескольких обращений к памяти. " Это довольно убедительно, что 128-битные инструкции SSE не гарантированы атомарными ISA. Том 2А в документации Intel говорит о CMPXCHG16B
: "Эту инструкцию можно использовать с префиксом LOCK, чтобы разрешить выполнение инструкции атомарно".
Кроме того, производители ЦП не опубликовали письменные гарантии атомарных операций 128b SSE для конкретных моделей ЦП, где это имеет место.
На самом деле есть предупреждение в Руководстве по архитектуре Intel, том 3А. Раздел 8.1.1 (май 2011 года) в разделе гарантированных атомных операций:
Инструкция x87 или инструкция SSE, которая осуществляет доступ к данным, большим, чем четырехугольное слово, может быть реализована с использованием множественного доступа к памяти. Если такая инструкция сохраняется в памяти, некоторые из доступов могут завершиться (запись в память), в то время как другая приводит к сбою операции по архитектурным причинам (например, из-за записи в таблице страниц, которая помечена как "отсутствует"). В этом случае результаты завершенных обращений могут быть видны программному обеспечению, даже если общая инструкция вызвала ошибку. Если аннулирование TLB было отложено (см. Раздел 4.10.4.4), такие сбои страниц могут возникнуть, даже если все обращения к одной и той же странице.
таким образом, инструкции SSE не гарантированно будут атомарными, даже если базовая архитектура использует единый доступ к памяти (это одна из причин, по которой было введено ограждение памяти).
Объедините это с этим утверждением из Руководства по оптимизации Intel, раздел 13.3 (апрель 2011 г.)
Инструкции AVX и FMA не вводят никаких новых гарантированных атомарных операций с памятью.
и тот факт, что ни одна из операций загрузки или сохранения для SIMD не гарантирует атомарность, мы можем прийти к выводу, что Intel не поддерживает никакую форму атомарного SIMD (пока).
В качестве дополнительного бита, если память разделена по строкам кэша или границам страниц (при использовании таких вещей, как movdqu
которые разрешают доступ без выравнивания), следующие процессоры не будут выполнять атомарный доступ, независимо от выравнивания, но более поздние процессоры будут (снова из Руководства по архитектуре Intel):
Процессоры Intel Core 2 Duo, Intel® Atom™, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, семейства P6, Pentium и Intel486. Процессоры Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon и P6.
Похоже, AMD также укажет в следующей редакции своего руководства, что выровненные 16-битные загрузки и хранилища являются атомарными на их процессорах x86, поддерживающих AVX. (Источник)
Приносим извинения за поздний ответ!
Мы обновим руководства по AMD APM в следующей версии.
Для всех архитектур AMD
Процессоры, поддерживающие AVX, расширяют атомарность кэшируемых, естественно выровненных одиночных загрузок или сохранений от квадрослова до двойного квадрослова.
что означает, что все инструкции 128b, даже инструкции *MOVDQU, являются атомарными, если они в конечном итоге выровнены естественным образом.
Можем ли мы распространить этот патч и на процессоры AMD? Если нет, я планирую представить патч для этапа-1!
При этом патч, использующий libatomic,vmovdqa
в их осуществлении__atomic_load_16
и__atomic_store_16
не только на процессорах Intel с AVX, но и на процессорах AMD с AVX попал в главную ветвь .
На данный момент опубликовано множество ответов, и, следовательно, много информации уже доступно (в качестве побочного эффекта также много путаницы). Я хотел бы разместить факты из руководства Intel, касающиеся аппаратных гарантированных атомарных операций...
В новейших процессорах Intel семейства Nehalem и Sandy Bridge чтение или запись в четырехслойное слово с 64-битной границей гарантированы.
Даже невыровненные 2, 4 или 8-байтовые операции чтения или записи гарантированно будут атомарными, если они являются кэшированной памятью и помещаются в строку кэша.
Сказав, что тест, размещенный в этом вопросе, проходит на процессоре Intel i5 Sandy Bridge.
РЕДАКТИРОВАТЬ: За последние два дня я сделал несколько тестов на моих трех компьютерах, и я не воспроизвел никаких ошибок памяти, поэтому я не могу сказать что-либо более точно. Может быть, эта ошибка памяти также зависит от ОС.
РЕДАКТИРОВАТЬ: я программирую на Delphi, а не на C, но я должен понимать C. Итак, я перевел код, вот у вас есть процедуры потоков, где основная часть сделана на ассемблере:
procedure TThread1.Execute;
var
n :cardinal;
const
ConstAll0 :array[0..3] of integer =(0,0,0,0);
begin
for n := 0 to 100000000 do
asm
movdqa xmm0, dqword [x]
movmskps eax, xmm0
inc dword ptr[n1 + eax *4]
movdqu xmm0, dqword [ConstAll0]
movdqa dqword [x], xmm0
end;
end;
{ TThread2 }
procedure TThread2.Execute;
var
n :cardinal;
const
ConstAll1 :array[0..3] of integer =(-1,-1,-1,-1);
begin
for n := 0 to 100000000 do
asm
movdqa xmm0, dqword [x]
movmskps eax, xmm0
inc dword ptr[n2 + eax *4]
movdqu xmm0, dqword [ConstAll1]
movdqa dqword [x], xmm0
end;
end;
Результат: нет ошибки на моем четырехъядерном ПК и нет ошибки на моем двухъядерном ПК, как ожидалось!
- ПК с процессором Intel Pentium4
- ПК с процессором Intel Core2 Quad Q6600
- ПК с процессором Intel Core2 Duo P8400
Можете ли вы показать, как отладчик видит ваш код процедуры потока? Пожалуйста...