Инструкции SSE: какие процессоры могут выполнять атомные операции памяти 16B? - PullRequest
28 голосов
/ 04 октября 2011

Рассмотрим инструкцию 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

Ответы [ 6 ]

33 голосов
/ 04 октября 2011

В Intel® 64 и IA-32 Архитектура Руководство разработчика: Том. 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.

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

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

В соответствующем документе AMD, Технология AMD64, Руководство для программиста по архитектуре 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  Not a single memory access!
1101           0          0
1110           0          0
1111        1366  999994009

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

РЕДАКТИРОВАТЬ 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, и я все еще могу воспроизвести сбои.

4 голосов
/ 07 октября 2011

«Руководство по программированию AMD Volume 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. Том 2A из документации Intel говорит о CMPXCHG16B: «Эта инструкция может использоваться с префиксом LOCK, чтобы разрешить выполнение инструкции атомарно».

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

3 голосов
/ 07 октября 2011

На самом деле есть предупреждение в Руководстве по архитектуре 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

2 голосов
/ 10 октября 2015

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

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

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

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

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


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


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

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

Opteron 2435 от janneb является 6-ядерным процессором Istanbul, который является частью семейства K10 , поэтому этот одноатомный вывод> атомный вывод выглядит точным в одном сокете.

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

0 голосов
/ 10 октября 2011

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

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

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

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

0 голосов
/ 07 октября 2011

РЕДАКТИРОВАТЬ: За последние два дня я провел несколько тестов на своих трех компьютерах, и я не воспроизвел никаких ошибок памяти, поэтому я не могу сказать что-либо более точно.Может быть, эта ошибка памяти также зависит от ОС.

РЕДАКТИРОВАТЬ: Я программирую на 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

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

...