Инструкции 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


Update:

Я создал простую тестовую программу на 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

Ответ 1

В Руководство разработчика для разработчиков Intel® 64 и IA-32: Vol. 3A, который в настоящее время содержит спецификации упомянутой вами белой бумаги для упорядочения памяти, говорится в разделе 8.2.3.1, как вы сами заметили, что

The Intel-64 memory ordering model guarantees that, for each of the following 
memory-access instructions, the constituent memory operation appears to execute 
as a single memory access:

• Instructions that read or write a single byte.
• Instructions that read or write a word (2 bytes) whose address is aligned on a 2
byte boundary.
• Instructions that read or write a doubleword (4 bytes) whose address is aligned
on a 4 byte boundary.
• Instructions that read or write a quadword (8 bytes) whose address is aligned on
an 8 byte boundary.

Any locked instruction (either the XCHG instruction or another read-modify-write
 instruction with a LOCK prefix) appears to execute as an indivisible and 
uninterruptible sequence of load(s) followed by store(s) regardless of alignment.

Теперь, поскольку приведенный выше список НЕ содержит один и тот же язык для double quadword (16 байт), следует, что архитектура НЕ гарантирует, что инструкции, которые обращаются к 16 байтам памяти, являются атомарными.

При этом последний абзац намекает на выход, а именно инструкцию CMPXCHG16B с префиксом LOCK. Вы можете использовать инструкцию CPUID, чтобы выяснить, поддерживает ли ваш процессор CMPXCHG16B (бит функции "CX16" ).

В соответствующем документе AMD AMD64 Technology AMD64 Architecture Programmers Manual Том 2: Системное программирование, я не могу найти такой же понятный язык.

EDIT: результаты тестовых программ

(тестовая программа изменена для увеличения #iterations в 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  Not a single memory access!
1101           0          0
1110           0          0
1111        1366  999994009

И что это значит? Ну, Opteron 2435 может или не может гарантировать, что 16-байтовые обращения к памяти являются атомарными для доступа внутри сокета, но по крайней мере протокол когерентности кэша, работающий на межсетевом соединении HyperTransport между двумя сокетами, не дает такой гарантии.

EDIT 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 могут быть более знакомы. Наконец, это с маршем = native, что делает GCC предпочтительным MOVAPS; но это не имеет значения, если я использую march = core2, он будет использовать MOVDQA для хранения в x, и я все еще могу воспроизвести ошибки.

Ответ 2

В руководстве по архитектуре Intel Vol 3A есть предупреждение. Раздел 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

Ответ 3

"Руководство по программированию архитектуры AMD Volume 1: Application Programming" в разделе 3.9.1:" CMPXCHG16B может использоваться для выполнения 16-байтовый атомный доступ в 64-битном режиме (с некоторыми ограничениями выравнивания).

Тем не менее, нет комментариев относительно инструкций SSE. На самом деле в комментарии 4.8.3 есть комментарий, что префикс LOCK "вызывает исключение недействительного кода операции при использовании с 128-битными инструкциями мультимедиа". Поэтому представляется мне совершенно убедительным, что процессоры AMD НЕ гарантируют атомные 128-битные обращения для инструкций SSE, и единственный способ сделать атомный 128-битный доступ - использовать CMPXCHG16B.

" Руководство разработчика программного обеспечения Intel 64 и IA-32 Volume 3A: Руководство по системному программированию, часть 1" говорится в 8.1.1 "Инструкция x87 или инструкции SSE, которые обращаются к данным, большим, чем квадловое слово, могут быть реализованы с использованием нескольких обращений к памяти". Это довольно убедительно, что 128-битные SSE-инструкции не гарантируются атомарным ISA. Том 2A в документах Intel говорит о CMPXCHG16B: "Эта инструкция может использоваться с префиксом LOCK, чтобы позволить инструкции выполняться атомарно."

Кроме того, производители ЦП не опубликовали письменных гарантий атомных 128-разрядных SSE-операций для конкретных моделей ЦП, если это так.

Ответ 4

x86 ISA не гарантирует атомарность для чего-либо большего, чем 8B, поэтому реализации могут свободно поддерживать SSE/AVX, как это делает Pentium III/Pentium M/Core Duo: внутренние данные обрабатываются в 64-разрядных половинах. 128-битный магазин выполнен в виде двух 64-битных магазинов. Путь данных в/из кеша имеет только 64-битную ширину в микроархитектуре Yonah (Core Duo). (источник: Agar Fog microarch doc).

В более поздних реализациях есть более широкие пути данных внутри и обрабатываются 128b-инструкциями как один op. Core 2 Duo (conroe/merom) стал первым микроархитом Intel P6 с потоком данных 128b. (IDK о P4, но, к счастью, он достаточно стар, чтобы быть абсолютно неактуальным.)

Вот почему OP обнаруживает, что 128b ops не являются атомарными в Intel Core Duo (Yonah), но другие плакаты считают, что они являются атомарными на более поздних версиях Intel, начиная с Core 2 (Merom).

Диаграммы этой записи Realworldtech о Merom vs. Yonah показывают 128-битный путь между кэшем данных ALU и L1 в Merom (и P4), в то время как маломощная Yonah имеет 64-битный путь передачи данных. Путь данных между кешами L1 и L2 составляет 256b во всех трех проектах.

Следующий переход по ширине пути данных пришел с Intel Haswell, с загрузкой/хранением AVB/AVX2 256b (32B) и 64-битным контуром между кешем L1 и L2. Я ожидаю, что 256 бит/магазины будут атомарными в Хасуэлле, Бродвелле и Скайлаке, но мне не нужно их проверять. Я забыл, что Skylake снова расширил пути подготовки AVX512 в Skylake-EP (серверная версия) или, возможно, первоначальная реализация AVX512 будет похожа на SnB/IvB AVX, а 512b загрузок/хранилищ занимают порт загрузки/хранения для 2 циклов.


Как замечает Яннеб в своем превосходном экспериментальном ответе, протокол кеширования между сокетами в многоядерной системе может быть уже, чем то, что вы получаете в CPU центрального процессора последнего уровня. Архитектурных требований к атомичности для широких нагрузок/хранилищ не существует, поэтому разработчики могут сделать их атомарными в сокете, но неатомными через сокеты, если это удобно. IDK, насколько широк путь межсотовых логических данных для семейства AMD Bulldozer или для Intel. (Я говорю "логично", потому что даже если данные передаются в меньших фрагментах, он не может изменять строку кэша до тех пор, пока он не будет полностью получен.)


Поиск похожих статей о процессорах AMD позволяет сделать разумные выводы о том, являются ли 128b ops атомарными или нет. Некоторая помощь: просто проверить таблицы команд.

K8 декодирует movaps reg, [mem] до 2 m-ops, а K10 и семейство бульдозеров декодируют его до 1 м-op. AMD с низким энергопотреблением bobcat декодирует его до 2 операций, в то время как jaguar декодирует 128b movaps до 1 m-op. (Он поддерживает AVX1, аналогичный процессорам семейства Bulldozer: 256b insns (даже ALU ops) разделены на два 128b ops. Intel SnB разделяет только 256b загрузок/хранилищ, имея ALU с полной шириной.)

janneb Opteron 2435 является 6-ядерным центральным процессором в Стамбуле, который является частью семейства K10, поэтому этот single-m-op → атомное заключение кажется точным в пределах одного сокета.

Intel Silvermont делает 128b загрузок/хранилищ с одним uop и пропускной способностью одного на такт. Это то же самое, что и для целых нагрузок/хранилищ, поэтому он вполне вероятно атомный.

Ответ 5

EDIT: За последние два дня я провел несколько тестов на трех моих компьютерах, и я не воспроизвел ошибку памяти, поэтому я не могу сказать ничего более точно. Возможно, эта ошибка памяти также зависит от ОС.

EDIT: Я программирую в 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 CPU Q6600
  • ПК с процессором Intel Core2 Duo P8400

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

Ответ 6

До сих пор было опубликовано множество ответов, и, следовательно, много информации уже доступно (как побочный эффект много путаницы). Я хотел бы представить факты из руководства Intel относительно аппаратных гарантированных атомных операций...

В новейших процессорах Intel семейства nehalem и песчаных мостов гарантируется чтение или запись в квадловое слово, согласованное с 64-битной границей.

Даже неглавные 2, 4 или 8 байтовые чтения или записи гарантированно являются атомарными, если они кэшированы и помещаются в строку кэша.

Сказав, что тест, опубликованный в этом вопросе, проходит на процессоре Intel i5 на основе песочного моста.