C ++ 11 представил стандартизованную модель памяти. Что это значит? И как это повлияет на программирование на С ++? [c++]


Answers

Я просто даю аналогию, с которой я понимаю модели согласованности памяти (или модели памяти, для краткости). Это вдохновляет оригинальная статья Лесли Лампорта «Время, часы и порядок событий в распределенной системе» . Аналогия уместна и имеет фундаментальное значение, но может быть излишним для многих людей. Однако я надеюсь, что это дает мысленный образ (графическое представление), что облегчает рассуждение о моделях согласованности памяти.

Давайте рассмотрим истории всех местоположений памяти в пространственно-временной диаграмме, в которой горизонтальная ось представляет адресное пространство (т. Е. Каждая ячейка памяти представлена ​​точкой на этой оси), а вертикальная ось представляет время (мы увидим, что, В общем, нет универсального понятия времени). Таким образом, история значений, хранящихся в каждой ячейке памяти, представляет собой вертикальный столбец по этому адресу памяти. Каждое изменение значения происходит из-за того, что один из потоков записывает новое значение в это место. Под изображением памяти мы будем понимать совокупность / комбинацию значений всех мест памяти, наблюдаемых в определенное время конкретным потоком .

Цитата из «Праймера по согласованности памяти и согласованности кеша»

Интуитивная (и наиболее ограничительная) модель памяти представляет собой последовательную согласованность (SC), в которой многопоточное выполнение должно выглядеть как чередование последовательных исполнений каждого составного потока, как если бы потоки были мультиплексированы по времени на одноядерном процессоре.

Этот глобальный порядок памяти может варьироваться от одного запуска программы к другому и может быть не известен заранее. Характерной особенностью SC является набор горизонтальных срезов в диаграмме адрес-пространство-время, представляющий плоскости одновременности (т. Е. Изображения памяти). На данной плоскости все его события (или значения памяти) являются одновременными. Существует понятие Абсолютного времени , в котором все нити согласуются с тем, какие значения памяти являются одновременными. В SC в каждый момент времени есть только один образ памяти, общий для всех потоков. То есть, в каждый момент времени все процессоры согласуют изображение с памятью (т. Е. Совокупное содержимое памяти). Это не только означает, что все потоки рассматривают одну и ту же последовательность значений для всех мест памяти, но также и то, что все процессоры выполняют одни и те же комбинации значений всех переменных. Это то же самое, что сказать, что все операции с памятью (по всем ячейкам памяти) наблюдаются в том же полном порядке всеми потоками.

В моделях с ослабленной памятью каждый поток будет разбивать адрес-пространство-время по-своему, единственным ограничением является то, что срезы каждого потока не должны пересекаться друг с другом, потому что все потоки должны согласовывать историю каждой отдельной ячейки памяти (конечно , Срезы разных потоков могут и будут пересекаться друг с другом). Нет универсального способа разрезать его (без привилегированного слоения адресного пространства-времени). Ломтики не должны быть плоскими (или линейными). Они могут быть изогнутыми, и это то, что может сделать значения чтения потока, написанные другим потоком, из того порядка, в котором они были написаны. Истории разных мест памяти могут скользить (или растягиваться) произвольно относительно друг друга при просмотре каким-либо конкретным потоком , Каждый поток будет иметь другое представление о том, какие события (или, что то же самое, значения памяти) являются одновременными. Набор событий (или значений памяти), которые одновременно связаны с одним потоком, не являются синхронными с другим. Таким образом, в модели с ослабленной памятью все потоки по-прежнему сохраняют одну и ту же историю (то есть последовательность значений) для каждой ячейки памяти. Но они могут наблюдать различные образы памяти (т. Е. Комбинации значений всех мест памяти). Даже если два разных места памяти записаны одним и тем же потоком в последовательности, два новых записанных значения могут наблюдаться в другом порядке другими потоками.

[Иллюстрация из Википедии]

Читатели, знакомые с специальной теорией относительности Эйнштейна , заметят, о чем я говорю. Перевод слов Минковского в область моделей памяти: адресное пространство и время - это тени адресного пространства-времени. В этом случае каждый наблюдатель (т. Е. Поток) проецирует тени событий (т. Е. Запоминает / загружает память) на свою собственную линию мира (т. Е. Свою временную ось) и свою собственную плоскость одновременности (его ось адресного пространства) , Темы в модели памяти C ++ 11 соответствуют наблюдателям , которые движутся относительно друг друга в специальной теории относительности. Последовательная согласованность соответствует галилеевскому пространству-времени (т. Е. Все наблюдатели соглашаются на один абсолютный порядок событий и глобальное чувство одновременности).

Сходство между моделями памяти и специальной теорией относительности связано с тем, что оба определяют частично упорядоченный набор событий, часто называемый причинным множеством. Некоторые события (т. Е. Хранилища памяти) могут влиять (но не влиять) на другие события. Поток C ++ 11 (или наблюдатель в физике) - это не более чем цепочка (т. Е. Полностью упорядоченный набор) событий (например, загрузка и сохранение памяти на разные адреса).

В теории относительности некоторый порядок восстанавливается на кажущуюся хаотическую картину частично упорядоченных событий, поскольку единственное временное упорядочение, с которым согласны все наблюдатели, - это упорядочение между «временными» событиями (т. Е. Те события, которые в принципе соединяются любой частицей, которая идет медленнее Чем скорость света в вакууме). Только упорядоченные по времени события инвариантно упорядочены. Время в физике, Крейг Каллендер .

В модели памяти C ++ 11 аналогичный механизм (модель согласованности-освобождения-выпуска) используется для установления этих локальных отношений причинности .

Чтобы дать определение последовательности памяти и мотивации отказа от SC, я приведу цитату из «Основы согласованности памяти и согласованности кэш-памяти»,

Для компьютера с общей памятью модель согласованности памяти определяет архитектурно видимое поведение своей системы памяти. Критерий правильности для одного ядра процессора разбивает поведение между « одним правильным результатом » и « множеством неправильных альтернатив ». Это связано с тем, что архитектура процессора предусматривает, что выполнение потока преобразует заданное входное состояние в одно четко определенное состояние вывода даже на ядре вне порядка. Однако модели согласованности с общей памятью относятся к нагрузкам и хранилищам нескольких потоков и, как правило, позволяют много правильных исполнений при одновременном запрете многих (более) неправильных. Возможность множественных правильных исполнений связана с ISA, позволяющей одновременным выполнением нескольких потоков, часто со многими возможными законными перемежениями команд из разных потоков.

Расслабленные или слабые модели согласованности памяти мотивированы тем, что большинство порядков памяти в сильных моделях не нужно. Если поток обновляет десять элементов данных, а затем флаг синхронизации, обычно программистам не важно, обновлены ли элементы данных по порядку относительно друг друга, а только обновлены все элементы данных до обновления флага (обычно они реализуются с использованием команды FENCE ). Расслабленные модели стремятся уловить эту повышенную гибкость заказа и сохранить только заказы, которые программисты « требуют », чтобы получить как более высокую производительность, так и правильность SC. Например, в некоторых архитектурах буферы записи FIFO используются каждым ядром для хранения результатов фиксированных (отставных) хранилищ перед записью результатов в кеши. Эта оптимизация повышает производительность, но нарушает SC. Буфер записи скрывает задержку обслуживания пропусков в магазине. Поскольку магазины являются общими, возможность избежать остановки большинства из них является важным преимуществом. Для одноядерного процессора буфер записи может быть сделан архитектурно невидимым, гарантируя, что загрузка на адрес A возвращает значение самого последнего хранилища в A, даже если один или несколько хранилищ для A находятся в буфере записи. Обычно это делается путем обхода значения самого последнего хранилища A до нагрузки от A, где «последнее» определяется по заказу программы или путем остановки загрузки A, если хранилище A находится в буфере записи , Когда используются несколько ядер, каждый из них будет иметь свой собственный байпас записи. Без буферов записи аппаратное обеспечение является SC, но с буферами записи это не так, что делает буферы записи архитектурно видимыми в многоядерном процессоре.

Переупорядочение магазина-хранилища может произойти, если в ядре есть буфер записи, отличный от FIFO, который позволяет магазинам уходить в другом порядке, чем в том порядке, в котором они были введены. Это может произойти, если первый магазин промахивается в кеше, а второй - или если второй магазин может объединиться с более ранним хранилищем (т. Е. Перед первым хранилищем). Переупорядочение нагрузки может также происходить в динамически запланированных ядрах, которые выполняют инструкции из программы. Это может вести себя так же, как переупорядочение магазинов на другом ядре (можете ли вы придумать пример чередования между двумя потоками?). Переупорядочение ранней загрузки с последующим хранилищем (переупорядочение хранилища-загрузки) может привести ко многим неправильным поведением, например, загрузить значение после отпускания блокировки, которая его защищает (если хранилище является операцией разблокировки). Обратите внимание, что переупорядочивание в хранилище может также возникать из-за локального обхода в обычно реализованном буфере записи FIFO даже с ядром, которое выполняет все инструкции в порядке выполнения программы.

Поскольку согласованность кэша и согласованность памяти иногда путают, поучительно также иметь эту цитату:

В отличие от последовательности, когерентность кэша не является видимой для программного обеспечения и не требуется. Когерентность направлена ​​на то, чтобы кэши системы с разделяемой памятью были функционально невидимы как кеши в одноядерной системе. Правильная согласованность гарантирует, что программист не может определить, имеет ли и где система кэширует, анализируя результаты нагрузок и хранилищ. Это связано с тем, что правильная согласованность гарантирует, что кеши никогда не будут включать новое или другое функциональное поведение (программисты могут все еще иметь возможность вывести вероятную структуру кэша с использованием информации о времени ). Основная цель протоколов когерентности кеша - поддерживать инвариант одиночного писателя-множественного считывателя (SWMR) для каждой ячейки памяти. Важное различие между согласованностью и согласованностью заключается в том, что согласованность указана для каждой ячейки памяти , тогда как согласованность указана для всех мест памяти.

Продолжая нашу ментальную картину, инвариант SWMR соответствует физическому требованию, чтобы в одной точке находилось не более одной частицы, но может быть неограниченное количество наблюдателей любого местоположения.

Question

C ++ 11 представил стандартизованную модель памяти, но что именно это означает? И как это повлияет на программирование на С ++?

Херб Саттер говорит здесь, что,

Модель памяти означает, что код C ++ теперь имеет стандартизованную библиотеку для вызова независимо от того, кто создал компилятор и на какой платформе он работает. Существует стандартный способ управления тем, как разные потоки разговаривают с памятью процессора.

«Когда вы говорите о разделении [кода] на разные ядра, которые находятся в стандарте, мы говорим о модели памяти. Мы будем оптимизировать ее, не нарушая следующих предположений, которые люди собираются сделать в коде», - сказал Саттер.

Хорошо, я могу запомнить этот и аналогичные абзацы, доступные в Интернете (так как у меня есть собственная модель памяти с рождения: P), и я могу даже написать ответ на вопросы, заданные другими, но, честно говоря, я не совсем понимаю это.

Итак, что я в основном хочу знать, программисты на С ++ раньше разрабатывали многопоточные приложения, поэтому как это важно, если их потоки POSIX или потоки Windows или потоки C ++ 11? Каковы преимущества? Я хочу понять детали низкого уровня.

Я также чувствую, что модель памяти C ++ 11 каким-то образом связана с поддержкой многопоточности C ++ 11, поскольку я часто вижу их вместе. Если да, то как именно? Почему они должны быть связаны?

Поскольку я не знаю, как работают многопоточные работы и какая модель памяти в целом, пожалуйста, помогите мне понять эти понятия. :-)




Это уже 2-летний вопрос, но, будучи очень популярным, стоит упомянуть фантастический ресурс для изучения модели памяти C ++ 11. Я не вижу смысла подытоживать его разговор, чтобы сделать этот еще один полный ответ, но учитывая, что это тот парень, который на самом деле написал стандарт, я думаю, что стоит посмотреть разговор.

Herb Sutter имеет 3-часовую беседу о модели памяти C ++ 11 под названием «атомное оружие», доступное на сайте Channel9 - часть 1 и часть 2 . Разговор довольно технический и охватывает следующие темы:

  1. Оптимизации, расы и модель памяти
  2. Заказ - Что: Приобретение и выпуск
  3. Заказ - Как: Мьютекс, Атомная техника и / или Заборы
  4. Другие ограничения на компиляторы и аппаратные средства
  5. Генерация кода и производительность: x86 / x64, IA64, POWER, ARM
  6. Расслабленная атомная энергия

В разговоре не говорится об API, а скорее о рассуждениях, предпосылках под капотом и за кулисами (знаете ли вы, что смягченная семантика была добавлена ​​к стандарту только потому, что Power and Arm не поддерживают синхронизированную нагрузку эффективно?).




Если вы используете мьютексы для защиты всех ваших данных, вам действительно не нужно беспокоиться. Мьютексы всегда обеспечивали достаточные гарантии порядка и видимости.

Теперь, если вы использовали атомы или алгоритмы блокировки, вам нужно подумать о модели памяти. Модель памяти точно описывает, когда атомистика обеспечивает гарантии порядка и видимости, а также предоставляет переносные ограждения для ручных гарантий.

Раньше атомизация выполнялась с использованием встроенных средств компилятора или некоторой библиотеки более высокого уровня. Заборы были бы выполнены с использованием инструкций, специфичных для процессора (барьеров памяти).