Инструкции 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. , которая в настоящее время содержит спецификации упомянутой вами белой книги по упорядочению памяти, в разделе 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;

Результат: нет ошибки на моем четырехъядерном ПК и нет ошибки на моем двухъядерном ПК, как ожидалось!

  1. ПК с процессором Intel Pentium4
  2. ПК с процессором Intel Core2 Quad Q6600
  3. ПК с процессором Intel Core2 Duo P8400

Можете ли вы показать, как отладчик видит ваш код процедуры потока? Пожалуйста...

Другие вопросы по тегам