01
Архитектура хранилища
Скорость быстрыйиз Высокая стоимость оборудования хранения данных、Небольшая емкость, медленная скорость и низкая Стоимость., большая вместительность. для компромисса между стоимостью и скоростью,Компьютерное хранилище разделено на множество уровней.,Используйте сильные стороны и избегайте слабых,иметьзарегистрироваться、L1 cache、L2 cache、L3 cache、основная память(Память)ижесткий дискждать。Рисунок 1 показал современную Архитектура хранилища。
Рисунок 1
согласно процедуреизпространственная локальностьивременная местностьпринцип,кэшжизньсередина Скорость может достигатьприезжать 70~90% . Поэтому добавление кэша может приблизить производительность всей СХД к регистровой, а стоимость байта — к памяти или даже диску.
таккэшда Архитектура хранилищаиздуша。
02
Принцип кэширования
2.1 Как работает кэширование
строка кэшадакэш Управлятьизсамая маленькая единица хранения,Также называется кэш-блоком.,каждый cache line Включать Flag、Tag и Data ,в целом Data размер 64 байта,Но разные модели CPU из Flag и Tag Может быть не то же самое. отпамять к кэшу загруженные данные и нажать всю строку кэша загрузки из строки кэшаиодин такого же размераизблок памятипереписываться。
Рисунок 2
Рисунок 2середина, кэшда расположены в матрице (M × N),ГоризонтальныйдаНабор,портретдаСпособ。каждый элементдастрока кэша(cache line)。
Затем задан виртуальный адрес addr каксуществоватькэшсередина Разместите это?первыйпоставь этосуществоватьизНомер группыпопытаться найтиприезжать,Прямо сейчас:
//Сдвиг влево на 6 бит дапотому что Block Offset занимать addr из Низкий 6 Бит, Данные для 64 байта
Set Index = (addr >> 6) % M;
Затем пройдите все дороги в группе и найдите жилье. cache line серединаиз Tag и addr середина Tag Взаимнождатьдляконец,Не все дороги сопоставлены успешно,Таккэшеще нетжизньсередина。
Вся емкость кэша = Количество групп × Количество способов × строка размер кэша
Информация о процессоре моего компьютера:
Информация о моем компьютере:
По строке размер кэшаи Количество способов Можно сделать выводвнекэшиз Количество групп,Прямо сейчас:
кэш Количество групп = Вся емкость кэша ÷ Количество способов ÷ строка размер кэша
2.2 Стратегия замены строки кэша
В настоящее время наиболее часто используетсяизкэшстратегия заменыдаПоследний использованный алгоритм(Least Recently Used ,LRU)или ВОЗдапохожий LRU из алгоритма.
LRU Алгоритм относительно прост, как показано на рисунке 3. Кэш имеет 4 Дорога и адреса доступа хешируются. Приехать в одну группу, порядок доступа да D1、D2、D3、D4 и Д5, тогда D1 будет D5 заменять。алгоритмизвыполнить Способиметьоченьмногодобрый,Самый простойизвыполнить Способдабитовая матрица。
во-первых, определите линию、Лиедуикэш Количество Способ тот же, из матрицы. При посещении нынешней дороги Перепискастрока кэшачас,Первый Волядорога Переписка Местоиметь ХОРОШОнабордля 1,а потом еще раз Волядорога Переписка Местоиметь Списокнабордля 0。
Наименее часто используемое изстрока кэша соответствует строке матрицы середина 1 из имеет наименьшее число и заменяется первым.
Рисунок 3
2.3 Кэш отсутствует
отсутствует в кеше в середине, ему необходимо загрузить данные из середины памяти, точная скорость работы будет медленнее.
просто возьми меняиз Компьютер приближаетсятест,L1d изкэшразмер 32 КБ (32768B), 8-канальный, размер строки кэша 64Б, затем
кэш Количество групп = 32 × 1024 ÷ 8 ÷ 64 = 64
Запустите следующий код
char *a = new char(64 * 64 * 8); //32768B
for(int i = 0; i < 20000000; i++)
for(int j = 0; j < 32768; j += 4096)
a[j]++;
Результат: цикл 160000000 раз, отнимает много времени 301 ms。удалять Понятно Нет.один次еще нетжизньсерединакэш,каждый раз позжечитать Писатьданные Всеспособныйжизньсерединакэш。
Настройте начальную поверхность кода и запустите его.
char *a = new char(64 * 64 * 8 * 2); //65536B
for(int i = 0; i < 10000000; i++)
for(int j = 0; j < 65536; j += 4096)
a[j]++;
Результат: цикл 160000000 раз, отнимает много времени 959 РС. Каждый раз, когда читать Писанные мертво, это занимает больше времени 2 раз.
2.4 Местоположение программы
местоположение Читайте программы, непрерывно считывая данные из памяти. в Кэше приводит к замене из накладных расходов.
Запустите следующий код на моем компьютере
int M = 10000, N = 10000;
char (*a)[N] = (char(*)[N])calloc(M * N, sizeof(char));
for(int i = 0; i < M; i++)
for(int j = 0; j < N; j++)
a[i][j]++;
Результат: цикл 100000000 раз, отнимает много времени 314 РС. Воспользуйтесь преимуществом положения программыпринцип,кэшжизньсередина Высокая ставка。
Измените начальную поверхность кода следующим образом и запустите его.
int M = 10000, N = 10000;
char (*a)[N] = (char(*)[N])calloc(M * N, sizeof(char));
for(int j = 0; j < N; j++)
for(int i = 0; i < M; i++)
a[i][j]++;
Результат: цикл 100000000 раз, отнимает много времени 1187 РС. Без использования местоположения Принцип программы, кэш-процент жизни низкий, поэтому трудоемкость увеличилась 2 раз.
2.5 Ложное распространение
Когда два нит изменяют две соседние переменные одновременно, потому что чтокэшдаВ целом организовано по строке кэша.из,Когда нить против строки При работе в среде кэша необходимо уведомить другую линию кэша Неверный,Потому что еще один нитьоткэшсерединачитать, какой бы вы ни хотели изменить, изданные не удалось.,Необходимо от Память перезагрузить,В результате снижается производительность.
Мой компьютер запускает следующий код
struct S {
long long a;
long long b;
} s;
std::thread t1([&]() {
for(int i = 0; i < 100000000; i++)
s.a++;
});
std::thread t2([&]() {
for(int i = 0; i < 100000000; i++)
s.b++;
});
Результат: отнимает много времени 512 мс, причина в том, что два начальства упоминают приезжать, просто да два нить влияют друг на друга, делая друг друга изстрока кэша Неверный,вести к прямому от Памятьчитать Выбиратьданные。
Решение состоит в том, чтобы изменить приведенный выше код следующим образом:
struct S {
long long a;
long long noop[8];
long long b;
} s;
Результат: отнимает много времени 181 мс, причина ушла long long noop[8] Поместите два данных (a и б) Разделить приезжать на два разных изстрока кэшасередина,Хватит больше говорить друг о друге изкэш Неверный,поэтому скорость меняется.
В коде в этом разделе не включена оптимизация компилятора.,то есть параметры компиляциидля-O0 。
03
протокол согласованности кэша
существуют одноядерные эпохи,Увеличение кэша может значительно увеличить скорость чтения Писать,Но даприжатез ознаменовал наступление многоядерной эры,нопредставлять Понятносогласованность кэша вопрос, есть ли в ядре модификация строки кэшасерединаопределенное значение, Так должен иметься механизм, гарантирующий, что другие ядра смогут наблюдать за этой модификацией прибытия.
3.1 Стратегия записи в кэш
откэши Памятьиз Обновите отношения, чтобы увидеть,точкадля:
из кэша записи CPU к CPU из стратегии обновления,точкадля:
из кэша зафиксированныеда загружен ли он, чтобы посмотреть,точкадля:
3.2 Протокол МЭСИ
MESIпротоколда⼀на основеНеверныйизкэш⼀Последовательностьпротокол,да⽀держатьобратная записькэшизчаще всего⽤протокол。также известный как Иллинойспротокол (Illinois protocol,потому чтодасуществовать Университет Иллинойса в Урбана-Пенанге был изобретен из).
для Понятнорешатьмежду несколькими ядрамиизданныепроблемы со связью,нестивне ПонятноСлежка за автобусомСтратегия。природаначальство Сразуда Пучок Местоиметьизчитать Писатьпросить Всепроходитьавтобус(Bus)广播给Местоиметьизосновной,Затем Пусть каждыйиндивидуальныйосновнойидтинюхатьэти запросы,Затем действуйте в соответствии с местной изоляцией.
3.2.1 Статус
Этисостояниеинформация актуальнаяначальствохранилищесуществоватьстрока кэша(cache line)из Flag внутри.
3.2.2 События
3.2.3 Конечный автомат
Рисунок 4
В таблице 1 состояние машины показано на рисунке 4 из Объяснение урока(выдержка).
Текущий статус | событие | ответ |
---|---|---|
M | PrRd | Транзакция шины не генерируетсясостояние остается прежнимчитатьдействоватьдлякэшжизньсередина |
PrWr | Транзакция шины не генерируетсясостояние остается прежним Писатьдействоватьдлякэшжизньсередина | |
BusRd | состояние Изменятьдляобщий(S)SharedОтправьте сигнал шины FlushOpt одновременно с содержимым блока.,ловитьполучать ВОЗдля Первый выпусквнеBusRdизкэшиосновная памятьконтроллер(раз Писатьосновная память) | |
BusRdX | состояние Изменятьдля⽆эффект(I)InvalidОтправьте сигнал шины FlushOpt одновременно с содержимым блока.,ловитьполучать ВОЗдля Первый выпусквнеBusRdизкэшиосновная памятьконтроллер(раз Писатьосновная память) | |
E | PrRd | Транзакция шины не генерируетсясостояние остается прежнимчитатьдействоватьдлякэшжизньсередина |
PrWr | Транзакция шины не генерируетсясостояние Изменятьдляуже Исправлять(M)ModifiedИзменено значение на кэшблоксередина Писать⼊ | |
BusRd | состояние Изменятьдляобщий(S)SharedОтправьте сигнал шины FlushOpt одновременно с содержимым блока. | |
BusRdX | состояние Изменятьдля⽆эффект Отправьте сигнал шины FlushOpt одновременно с содержимым блока. | |
S | PrRd | Транзакция шины не генерируетсясостояние остается прежнимчитатьдействоватьдлякэшжизньсередина |
PrWr | Статус выдаваемого сигнала BusUpgr транзакции шины меняется на Modified (M) Modified. Остальные кэши видят сигнал шины BusUpgr и помечают свои копии как недействительные (I) Invalid. | |
BusRd | состояние Изменятьдляобщий(S)Sharedвозможный Отправьте сигнал шины FlushOpt одновременно с содержимым блока.(дизайнчасрешить, чтоиндивидуальныйобщийизкэшволосывнеданные) | |
BusRdX | состояние Изменятьдля⽆эффект(I)Invalidвозможный Отправьте сигнал шины FlushOpt одновременно с содержимым блока.(дизайнчасрешить, чтоиндивидуальныйобщийизкэшволосывнеданные) | |
I | PrRd | Отправить сигнал BusRd на шину Для других процессоров см. приезжатьBusRd.,Проверьте, есть ли у вас действующая копия да,Уведомление отправлено вне запроса изкэшесли другой кэш имеет действительную копию,Это середина⼀кэшволосывнеданные,состояние Изменятьдля(S)Sharedесли другие кэши недействительны из копий, отосновная памятьполучатьданные,состояние Изменятьдля(E)Exclusive |
PrWr | Отправить сигнал BusRdX на шинусостояние Конвертироватьдля(M)Modifiedесли в другом кэше есть действительные копии, Это середина⼀ кэш или внеданные, в противном случае отосновная; память Получить данные, если в другом кэше есть действительные копии, После просмотра сигнала BusRdX его копия недействителен. в блок кэша середина Писать⼊ изменено значение. | |
BusRd | Состояние остается неизменным, а сигнал игнорируется. | |
BusRdX/BusUpgr | Состояние остается неизменным, а сигнал игнорируется. |
PrWr
BusRd
BusRdX
EPrRd
PrWr
BusRd
BusRdX
SPrRd
PrWr
BusRd
BusRdX
IPrRd
PrWr
BusRd
BusRdX/BusUpgr
Таблица 1
3.2.4 Демонстрация анимации
Рисунок 5
Каждая семья CPU Не все производители соблюдают MESI выполнитьпротокол согласованности кэша, причина MESI Вариантов много, например: Intel Принять из MESIF и AMD Принять из MOESI,ARM Большинствоточка Принять изда МЭСИ, редко используется изда MOESI 。
3.3 Протокол MOESI (необязательное чтение)
MOESI даодинвесьизпротокол согласованности кэша,это Включать Понятнодругойпротоколсередина Обычно используетсяиз Местоиметьвозможныйсостояние。удалять Понятно四добрыйобщийиз MESI Помимо статуса соглашения, существует пятый Owned состояние означает изменение и обмен изданными.
Это позволяет избежать необходимости делиться данными до того, как Воля изменит изданные Писать обратно в основную память. Хотя возвращение в конечном итоге должно произойти, возвращение может быть отложено.
3.4 Протокол MESIF (необязательное чтение)
MESIF даодинсогласованность кэшаиНепрерывность памятипротокол,Долженпротоколна пятьсостояниекомпозиция:Модифицированный (М),Взаимоисключающие (E),Поделиться(и),Недействительный (Я)иНападающий (Ф)。
M,E,S и I состояниеи MESI протоколпоследовательный。F Статус S состояние Особая форма,когдасистемасерединаиметьнесколько S , вам необходимо выбрать преобразование в Ф, только F состояниеиз Ответственный за ответ。в целомдабольшинствоназаддержатьиметь Долженкопироватьиз Конвертироватьдля Ф, обратите внимание F да Сухойсетьизданные。
Протоколы MOESI протокол сильно отличается от из и далек от Сравнивать MOESI Соглашение сложное. Настоящее соглашение состоит из Intel из соединение скоростных полос QPI(QuickPath Interconnect)технологияпредставлять,его основная цельиздарешать“На основании точки прибытия точка присоединения из несоответствия Память доступа (Non-uniform memory access,NUMA) процессорная система”изсогласованность кэшавопрос,Вместо того, чтобы да“Доступ на основе общей шины из согласованности Память (Uniform Memory Access, UMA) процессорная система”изсогласованность кэшавопрос.
04
Барьеры памяти
И компиляторы, и процессоры должны соблюдать правила переупорядочения. существуют один процессор в корпусе,Для поддержания правильного порядка не требуется никаких дополнительных операций. Но дадля многопроцессорности,Гарантия одна Последовательностьв целомнуждатьсяувеличиватьдобавлятьбарьер инструкция по памяти. Даже если компилятор может оптимизировать доступ к полям (например, потому что что не используется для загрузки значения приезжатьиз), компилятору все равно необходимо сгенерировать барьер память, как будто доступ к полям все еще существует (можно сделать отдельно Волябарьер оптимизирована память).
барьер памяти Толькоимодель памятьсерединаиз Расширенные концепции (например. acquire и release)косвенная корреляция。барьер памятьинструкция только прямого управления CPU и его взаимодействие с кэшизом, а также его буфер из Писать (который содержит хранилище, ожидающее обновления) и его буфер из, который используется для ожидания загрузки или предполагаемой инструкции по изучению. Эти эффекты могут привести к дальнейшему взаимодействию между хостом и другими процессорами.
几乎Местоиметьизпроцессор Все Поддержите хотя быдержатьодин Крупнозернистыйизбарьеринструкция(в целомсказатьдля Fence,также называетсяполный барьер),это Гарантированострогийизиметьпоследовательностьсекс:существовать Fence Извпередиз Местоиметьчитатьдействовать(load)и Писатьдействовать(store)предшествоватьсуществовать Fence После этого все операции чтения (загрузки) и операции записи (сохранения) изучения завершаются. Обычно это один из самых трудоемких процессоров (его накладные расходы обычно близки к таковым у Атомарных или даже превышают их). инструкция по эксплуатации). Большинство процессоров также поддерживают более детальные барьеры.
Таблица 2 да Каждый процессор поддерживает избарьер памятии Атомарные операции
Таблица 2
4.1 Писатьбуферинаписать барьер
Строго следуйте протоколу MESI, ядро 0. Прежде чем изменять локальный кеш, вам необходимо отправить его на другие ядра. Invalid сообщение, другие ядра получают сообщение «приехать» и делают своим местным коллегам изстрока. кэша Неверный и возврат Invalid acknowledgement сообщение, ядро 0 Измените строку кэша при получении. здесь ядро 0 Ожидание, пока другие ядра вернут подтверждающие сообщения, занимает для ядра много времени.
Рисунок 6
Чтобы решить эту проблему, Store Buffer , когда ядро захочет изменить кеш, напишите напрямую Store uffer , не нужно ждать, продолжайте обрабатывать другие вещи, Store Buffer Полное сопровождение работы.
Рисунок 7
Таким образом, скорость Писания быстро увеличивается.,Но да поднимает новые вопросы,Код нижеиз bar Утверждение функциисерединаиз может завершиться неудачно.
int a = 0, b = 0;
// CPU0
void foo() {
a = 1;
b = 1;
}
// CPU1
void bar() {
while (b == 0) continue;
assert(a == 1);
}
Первый случай: ЦП для Понятнонести Вознесение ХОРОШОэффект Ставкаинестивысокийкэшжизньсередина Ставка,Усыновленныйвышел из строяосуществлять;
Второй случай:Store Buffer При письме б. Соответствует изстрока кэшада E статус, а. Соответствует изстрока кэшада S статус, потому что правильно b из модификаций не требуется межядерная синхронизация, но да модификации a тогда это необходимо, т. b Будет ли Писать вводить кэш первым. и соответствующий CPU1 середина a да S статус, б да I статус из-за b Соответствующая область изкэша да I статус, он отправит в автобус BusRd запрос, тогда CPU1 поставлю это на первое место b избольшинствоновое значениечитатьприезжать локально, переменная завершения b Значение из обновляется, но даоткэш берется напрямую читать a Да 0 。
Приведите более крайний пример
// CPU0
void foo() {
a = 1;
b = a;
}
Первой ситуации не будет,Причина да В коде есть зависимости,Не будетвышел из строяосуществлять。нопотому что Store Buffer изжитьсуществовать,Второй сценарий все еще может произойти,Причина та же, что и у начальства. Это заставит людей чувствовать себя еще более невероятными.
для Понятнорешатьначальстволапшавопрос,представлять Понятнобарьер памяти,Барьер из функции перед чтением Операция не завершена в случае, если,Последующая операция прочитать Писать не может произойти.。этот Сразуда Arm начальство dmb инструкцияиз Источник,этодаданныебарьер памяти(Data Memory Barrier)изсокращать Писать。
int a = 0, b = 0;
// CPU0
void foo() {
a = 1;
smp_mb(); //барьер памяти, каждая платформа ЦП выполняет разные функции
b = 1;
}
// CPU1
void bar() {
while (b == 0) continue;
assert(a == 1);
}
добавлятьначальствобарьер памятиназад,Гарантировано a и b из Писатьвходитькэшзаказ。
В общем, магазин Buffer повышает производительность Писать,Но отказался от последовательной последовательности,этотдобрыйсейчасслонсказатьдляслабыйсогласованность кэша。в целомслучай,несколько CPU Совместное использование одной и той же переменной в случае, когда да Сравнивать меньше, так Store Buffer Это может значительно улучшить производительность программы. Но когда существование требует межъядерной синхронизации, вам также необходимо добавить барьер вручную. памятигарантироватьсогласованность кэша。
начальствоface решает проблему межядерной синхронизации из Писать,Но все еще есть узкое место в межъядерной синхронизации.,Тогда дачитать.
4.2 Неверныйочередьичитать барьер
представленный ранее Store Buffer Улучшена скорость записи, а затем invalid информацияподтверждатьскорость Взаимно Сравниватьрост Приходить Сразумедленный Понятно,приносить Приходить Понятнонесоответствие скорости,Легко сделать так, чтобы содержимое Store Buffer не сохранялось вовремя.,я полон,от и пропал эффект ускорения.
Для решения этой проблемы мы ввели Invalid Queue。получатьприезжать Invalid Сообщение из ядра возвращается немедленно Invalid acknowledgement сообщение, а затем поместите Invalid Сообщение для присоединения Invalid Queue , подожди, пока приезжать будет свободен, чтобы разобраться с этим Invalid информация.
Рисунок 8
Бег начальство увеличение лица барьер памятииз кода, нет. 11 Утверждение линии может снова потерпеть неудачу.
Ядро 0 середина a Соответствует изстрока кэшада S статус, б Соответствует изстрока кэшада E состояние;Ядро 1середина a Соответствует изстрока кэшада S статус, б Соответствует изстрока кэшада I состояние;
представлять Invalid Queue После этого для ядра 1 Посмотрим, как добраться a и b из Писатьвходитьсновавнесейчасвышел из строя Понятно。
метод решенияпродолжить добавлятьбарьер памяти,Ядро 1 Если вы хотите пересечь барьер, вы должны его преодолеть. Invalid Очередь, пара была обработана своевременно a из недействителен, тогда чтение занимает приехать новый из a значение, следующий код:
int a = 0, b = 0;
// CPU0
void foo() {
a = 1;
smp_mb();
b = 1;
}
// CPU1
void bar() {
while (b == 0) continue;
smp_mb(); //Продолжаем добавлять барьер памяти
assert(a == 1);
}
здесь используется избарьер памятидаполный барьер,включатьчитатьнаписать барьер,слишком строгий,Приведет к снижению производительности,такиметь Понятномелкозернистыйизчитать барьеринаписать барьер。
4.3 Разделение барьеров чтения и записи
разлука изнаписать барьеричитать барьеризвнесейчас,дадля Понятно Дажедобавлятьточный контроль Store Buffer и Invalid Queue из заказа.
Оптимизируйте предыдущий код следующим образом:
int a = 0, b = 0;
// CPU0
void foo() {
a = 1;
smp_wmb(); //написать барьер
b = 1;
}
// CPU1
void bar() {
while (b == 0) continue;
smp_rmb(); //читать барьер
assert(a == 1);
}
Данная модификация отличает лишь существованиечитатьнаписать. Это будет работать только в барьеризирующей архитектуре, например, alpha структура. существовать x86 и Arm серединадабезиметьэффектиз,потому что x86 Усыновленный TSO модель, которая будет подробно представлена позже, и Arm Усыновленныйодносторонний барьер。
4.4 Односторонний барьер
односторонний барьер (half-way barrier) Тоже своего рода барьер памяти, но он не отличается от да по параметру читать Писать, тогда как да похож на улицу с односторонним движением, допуская только одностороннее движение, например. ARM серединаиз stlr и ldar Инструкция Вот и все.
Рисунок 9 ARM Рисунок 13.2. Односторонние барьеры
Теория уже почти популяризирована. Далее поговорим о наиболее часто используемых работах студентов на стороне сервера: Модель памяти x86, заполнение ее 4.3 середина оставляет яму.
05
x86-TSO
x86-TSO( Total Store Order)Принять изда Рисунок 10 Модель.
Рисунок 10
x86-TSO имеет следующие возможности:
Ниже из кода да Linux существовать x86 Селектор памятиопределение
06
Контрольный показатель
6.1 о Store Buffer изтест
6.1.1 тестосновной Внутриданетжитьсуществовать Store Buffer
6.1.2 тестировать, распределять ли между ядрами Store Buffer
6.1.3 тест Store Forwarding (переадресация) да, эффективен ли он
6.2 тест CPU данетвышел из строяосуществлять
6.2.1 тест:StoreStore вышел из строя
6.2.2 тест:LoadStore вышел из строя
6.3 тест n5 / n4b: два ядра одновременно изменяют одну и ту же переменную.
6.3.1 тест:n5
6.3.2 тест:n4b
6.4 : Писать тест операции из видимости да нет передачи (если A могу видеть B изAction, Б могу видеть C изAction,Так A да, я могу видеть C издействие)
07
Принцип CAS
сравнить и поменять местами and swap, CAS),да Атомарные операцияиз вида, который можно использовать для существования нескольких программ, серединавыполнить без перерыва зданные операции подкачки, от и избегайте многократного изменения Писатьопределенный одни данные одновременно, потому что чтоосуществлятьзаказ Нет确定секса такжесерединаперерывиз Возникает непредсказуемостьизданные Нетпоследовательныйвопрос.Должендействоватьпроходить Воля Памятьсерединаизценитьиобозначениеданныеруководить Сравниватьсравнивать,когдачислоценить Такой жечас Воля Памятьсерединаизданныезаменятьдляновыйизценить。
Следующий код использует CAS из этого примера (очередь без блокировки Pop функция)
template <typename T>
bool AtomQueue<T>::Pop(T& v)
{
uint64_t tail = tail_;
if (tail == head_ || !valid_[tail])
return false;
if (!__sync_bool_compare_and_swap(&tail_, tail, (tail + 1) & mod_))
return false;
v = std::move(data_[tail]);
valid_[tail] = 0;
return true;
}
существоватьиспользоватьначальство,в целомвстречазаписыватьопределенныйкусок Памятьсерединаизстарое значение,проходитьверностарое значение руководитьодинрядиздействоватьназадпридетсяприезжатьновое значение,Затемпроходить CAS действовать Воляновое значение истарое значение Произведите обмен.
если эта часть стоимости Память существует не была изменена в течение этого периода,ностарое значение встречаи Памятьсерединаизданныетакой же,В это время CAS действовать Волявстречауспехосуществлять,делать Памятьсерединаизданные Изменятьдляновое значение。
если Памятьсерединаизсуществовать было изменено за этот период,нов целом Приходитьобъяснятьстарое значение встречаи Памятьсерединаизданныедругой,В это время CAS действовать Волявстречанеудача,новое значение Воля будет Писать входить Память.
7.1 Применение
существоватьприложениесередина CAS Может用Ввыполнитьструктура данных без блокировок,общийизиметьочередь без блокировки(Предварительная покупка Первыйвне)а такжестек без блокировок(Предварительная покупканазадвне)。для Можетсуществоватьпроизвольный Кусочекнабор ВставлятьвходитьданныеизСвязанные списки и двусвязные списки,выполнить Нет замкадействоватьизСложнее。
7.2 Проблемы ABA
проблема ABAда Нет структуры блокировки баланссередина Часто задаваемые вопросы,Может基本表описыватьдля:
для P1 Другими словами, числовые A Не изменилось, но фактически начальство A Он был изменен, и дальнейшее использование может вызвать проблемы. Если что С выравнивать имеет больше указателей да, чем из, эта проблема станет более серьезной. Рассмотрим следующую ситуацию:
Рисунок 12
Есть стек(Предварительная покупканазадвне)серединаиметь top и NodeA,NodeA В настоящее время находится на вершине стека,верхний указатель указывает на A。сейчассуществоватьиметьодиннить P1 хочу pop Узел, поэтому операция без блокировки происходит следующим образом
pop()
{
do{
ptr = top; // ptr = top = NodeA
next_ptr = top->next; // next_ptr = NodeX
} while(CAS(top, ptr, next_ptr) != true);
return ptr;
}
И нить P2 существовать P1 осуществлять CAS Прервите его перед операцией и выполните серию операций над стеком. pop и push Операция по преобразованию стека в следующую структуру:
Рисунок 13
нить P2 первый pop вне Узел А, а затем push Получил два NodeB и C,потому что ПамятьуправлятьмеханизмсерединаширокоиспользоватьизМеханизм повторного использования памяти,привести к NodeC изадресидоиз NodeA последовательный.
В это время P1 Начни снова бежать, существованиеосуществлять CAS В процессе эксплуатации из-за top Все еще указывая на изду NodeA изадрес(действительныйначальствоуже Изменятьдля NodeC ), так top изценить Исправлятьдля Понятно NodeX,В это времяструктура стекаследующее:
Рисунок 14
пройти CAS После операции сверху Указатель указывает на неправильное NodeX Вместо того, чтобы да NodeB。
Простой метод принятия решения DCAS(двойная длина CAS), а Длина CAS Сохраните исходные действительные данные, другие Длина CAS Сохраните совокупное количество изменений, первое. CAS Возможно ABA Вопрос, но да второй CAS Крайне сложно найти ABA вопрос.
7.3 Реализация
CAS Операция на основе CPU Предложение из Атомарные операцииинструкциявыполнить。для Intel X86 процессор, может быть добавлен действующими условиями перед добавлением префикса Блокировка для блокировки системной шины, чтобы системная шина существующей сборки согласно правилам существовать не могла получить доступ к соответствующему адресу из Память. И каждый компилятор имеет свои особенности, основанные на этой особенности. операциифункция。
08
Атомарные операции
степеньпоследовательностькодбольшинствоконец Всебудетпереводитьдля CPU инструкция,один条Самый простойиздобавлятьоператор вычитания Всебудетпереводитьна несколько полосокинструкцияосуществлять;для Понятноизбегатьзаявлениесуществовать CPU этотодин层级начальствоизинструкциякрестприносить Приходитьиз Нет Можетпредвидение ХОРОШОдля,Множественные программы должны быть стандартизированы в некотором роде.,чаще всего Видетьизупражняться Сразудапредставлятьблокировка мьютекса,ноблокировка операционная система мьютексада этого уровня, в конечном итоге отображаемая CPU Начальство — это еще и груда инструкций, а даинструкция неизбежно принесет дополнительные расходы.
теперь это CPU Инструкция мульти-нит не может быть далее разделена из наименьших единиц, тогда мы, если у нас есть способ, чтобы инструкции кода Воли и инструкции соответствовали, в противном случае нет необходимости в блокировке положения. мьютексаот и улучшить производительность? И эти соответствующие отношения и есть то, что называется из Атомарные. операции;существовать C++11 из atomic Есть два способа изготовления середины:
может пройти is_lock_free функцию, определить atomic Да, да lock-free тип.
Атомарные операциииметь Третья категория:
использоватьАтомарные операциимоделированиеблокировка мьютексаиз ХОРОШОдля Сразудаспин-блокировка,блокировка мьютекса Статус контролируется операционной системой из,спин-блокировкаиз Статус Программист сам это контролирует,Обычно используемые модели изспин-блокировки:
LOCK — это состояние самоопроса в спин-блокировке.,если Нетпредставлятьсерединаперерывмеханизм,Будет много пустой траты вычислительных ресурсов, чтобы приехать к самому начальству, что является обычной практикой использования выхода для переключения на другое место;,Или используйте режим сна, чтобы приостановить текущий момент.
8.2 Модель памяти C++
C++11 Атомарные операциииз Многие функции имеют одну std::memory_order Параметр, этот параметр - это то, что Да называет здесь измоделью памяти,переписыватьсясогласованность Модель кэша, роль которой заключается в одновременной сортировке операций чтения и написания, определена в целом 6 Типы следующие:
существоватьдругойиз CPU Архитектура начального уровня, эти модели в зависимости от конкретного способа выбора могут различаться, но да. C++11 Это поможет вам скрыть внутренние детали, и вам не придется об этом думать. памяти, если она соответствует правилам использования начальства, вы можете получить эффект приезжатьхочуиз. Иногда степень детализации модели может быть выше, что может привести к снижению производительности. Конечно, необходимо использовать базовую модель каждой платформы. памятидетализация Дажеточный,Эффективность также будет выше,Требования к базовым навыкам программистов также высоки.
8.3 C++ volatile
этотиндивидуальный关键字толькотолько保证данные Толькосуществовать Памятьсерединачитать Писать,прямойдействоватьэтотеперь этоОперации над атомами не гарантируются,Он также не может повсеместно достичь эффекта синхронизации приезжать Память;
потому что volatile Невозможно обеспечить множественную нитмогу в многопроцессорной среде. видетьтакой же Образецзаказизданные Изменятьизменять,существоватьсегодняиз Универсальныйприложениестепеньпоследовательностьсередина,не следует больше видеть volatile извнесейчас。
09
очередь без блокировки
Ниже я использую CAS выполнить Понятноодинмногопродюсермногопотребительочередь без блокировки,Справочник по дизайну Disruptor , до 6,6 миллионаQPS(одинпродюсеродинпотребитель)и1,6 миллионаQPS(10индивидуальныйпродюсер10индивидуальныйпотребитель)。
9.1 Идеи дизайна
1、нравиться Рисунок 15. Используйте 2 индивидуальныйкруговой массив,Ни один из элементов массива Атомарные переменные, магазин T Дженерикиданные(в целомдляуказатель),Другойодинда Может用секс检查число组(uint8_t)。Head да Местоиметьпродюсериззнак соревнования,Tail да Местоиметьпотребительиззнак соревнования。красная зонавыражатьместо для производства,зеленая зонавыражатьПозиция для потребления。
Рисунок 15
2. Продюсеры проходят CAS Приходите соревноваться и двигаться Head,Робприезжать Head изпродюсер,Первый Воля Head Добавьте 1, чтобы воспроизвести оригинал Head Местоположение изданные, а также пропуск изпотребителя; CAS Приходите соревноваться и двигаться Tail,Робприезжать Tail изпотребитель,Первый Воля Tail Добавьте 1, затем используйте оригинал Tail Кусочекнаборизданные 。
9.2 Детали реализации
Ниже приводится поиск с участием нескольких производителей и нескольких потребителей без кода блокировки для существования на платформе управления платформой x86-64 (x86-TSO). Вы пишете.
Talk is cheap. Show me the code.
9.2.1 Определение шаблона класса AtomQueue
template <typename T>
class AtomQueue
{
public:
AtomQueue(uint64_t size);
~AtomQueue();
bool Push(const T& v);
bool Pop(T& v);
private:
uint64_t P0[8]; //Частые изменения данных,избегатьпсевдосовместное использование, Использование отступов
uint64_t head_; //марка продюсера, Указывает, что место проживания указано, но место еще не указано.
uint64_t P1[8];
uint64_t tail_; //потребительотметка, Указывает, что позиция приезжать использована, но позиция еще не занята.
uint64_t P2[8];
uint64_t size_; //Максимальная емкость массива, Должно удовлетворять 2^N
int mod_; //Модуль % -> & Уменьшить 2нс
T* data_; //Массив данных кольца
uint8_t* valid_; //Доступный массив имеет форму кольца, размер и данных массива одинаковый
};
Будьте осторожны и вы увидите приезжать head_ и tail_ К переменной из середина также добавлено бессмысленное поле «из». P0、P1 и P2 ,потому что head_ и tail_ часто Изменятьизменять,глазизда Защищатьконецвнесейчас Упоминалось ранееизпсевдосовместное использованиегид Последовательностьспособный下降вопрос.
9.2.2 Функции строительства и функции разрушения
template <typename T>
AtomQueue<T>::AtomQueue(uint64_t size) : size_(size << 1), head_(0), tail_(0)
{
if ((size_ & (size_ - 1)))
{
printf("AtomQueue::size_ must be 2^N !!!\n");
exit(0);
}
mod_ = size_ - 1;
data_ = new T[size_];
valid_ = new uint8_t[size_];
std::memset(valid_, 0, sizeof(valid_));
}
template <typename T>
AtomQueue<T>::~AtomQueue()
{
delete[] data_;
delete[] valid_;
}
структурафункциясерединавынужденный проходвходитьизочередьразмер(size)должендля 2 номер мощности, я хочу его использовать & Вместо того, чтобы да % Выбиратьформа,потому что & Сравнивать % быстрый 2ns, стремясь к максимальной производительности.
9.2.3 продюсервызовиз Push функция и потребительвызовиз Pop функция
template <typename T>
bool AtomQueue<T>::Push(const T& v)
{
uint64_t head = head_, tail = tail_;
if (tail <= head ? tail + size_ <= head + 1 : tail <= head + 1)
return false;
if (valid_[head])
return false;
if (!__sync_bool_compare_and_swap(&head_, head, (head + 1) & mod_))
return false;
data_[head] = v;
valid_[head] = 1;
return true;
}
template <typename T>
bool AtomQueue<T>::Pop(T& v)
{
uint64_t tail = tail_;
if (tail == head_ || !valid_[tail])
return false;
if (!__sync_bool_compare_and_swap(&tail_, tail, (tail + 1) & mod_))
return false;
v = std::move(data_[tail]);
valid_[tail] = 0;
return true;
}
Проанализируйте описание начальства Push и Pop функция увеличит барьер чтения, если вы не хотите использовать клавиатуру Операции памяти, чтения Писать можно абстрактно описать в следующей таблице:
существоватьчитать Писатьдействоватьвышел из строяиз CPU начальство может вне настоящее начальство описанная ситуация приведет к вне линии Баг, объясни:
решатьспособдадобавлятьдобавлятьчитатьнаписать барьер(LoadStore barrier),Следующая таблица:
существовать Arm ждатьвышел из К счастью, строительство существующего начальства может решить проблему; x86-TSO платформаначальство Операции чтения не могут быть отложить, читатьнаписать нет необходимости барьер,Руководстводобавлять Понятнотакжеданулевойдействовать(no-op)。
проходитьосуществлять Разборкажизньделать(objdump -S a.out)придетсяприезжать Push середина Следующий код из ассемблерного кода.
if (!__sync_bool_compare_and_swap(&tail_, tail, (tail + 1) & mod_))
400a61: 48 8b 45 f8 mov -0x8(%rbp),%rax
400a65: 48 8d 50 01 lea 0x1(%rax),%rdx
400a69: 48 8b 45 e8 mov -0x18(%rbp),%rax
400a6d: 8b 80 d8 00 00 00 mov 0xd8(%rax),%eax
400a73: 48 98 cltq
400a75: 48 89 d1 mov %rdx,%rcx
400a78: 48 21 c1 and %rax,%rcx
400a7b: 48 8b 45 e8 mov -0x18(%rbp),%rax
400a7f: 48 8d 90 88 00 00 00 lea 0x88(%rax),%rdx
400a86: 48 8b 45 f8 mov -0x8(%rbp),%rax
400a8a: f0 48 0f b1 0a lock cmpxchg %rcx,(%rdx)
400a8f: 0f 94 c0 sete %al
400a92: 83 f0 01 xor $0x1,%eax
400a95: 84 c0 test %al,%al
400a97: 74 07 je 400aa0 <_ZN9AtomQueueIiE3PopERi+0x8c>
return false;
400a99: b8 00 00 00 00 mov $0x0,%eax
400a9e: eb 40 jmp 400ae0 <_ZN9AtomQueueIiE3PopERi+0xcc>
Обнаружить __sync_bool_compare_and_swap функция Перепискаассемблерный коддля:
400a8a: f0 48 0f b1 0a lock cmpxchg %rcx,(%rdx)
да приносить lock команда префиксиз, как говорилось ранее, существует x86-TSO начальство,приноситьиметь lock команда префиксиз обновилась Store Buffer из функции, также известной как да head_ и tail_ Модификации могут наблюдаться другими ядрами во времени, а также могут производиться и потребляться во времени.
10
Ссылки
Заключение
О боже, я впервые так много пишу! наконец-то поставил CPUкэш、барьер памяти、Атомарные операцииа такжеочередь без Блокировка была завершена за один раз. За этот период я ознакомился с большим количеством информации. Особую благодарность хотелось бы выразить автору Ссылкисерединаиз, который позволил мне получить много знаний о том, как приезжать. За этот период я также прочитал много тестовых кодов. проверяйте теорию, избегайте введения других в заблуждение и постарайтесь сделать поездку обоснованной. потому что чтоделать ВОЗуровеньиметьпредел,Ошибки и упущения в этой статье неизбежны.,Надеюсь, читатели меня раскритикуют и поправят.