TL: DR: на каждом современном ISA, имеющем инструкции по хранению байтов (включая x86), они атомарны и не мешают окружающим байтам. (мне неизвестны более старые ISA, где байтовые Инструкции -store также могут "изобретать записи" в соседние байты.)
Фактический механизм реализации (в процессорах, отличных от x86) иногда является внутренним циклом RMW для изменения всего слова в строке кэша, но это делается« незаметно »внутри ядра, в то время как оно имеет исключительное право владения строкой кеша, поэтому это всегда проблема производительности, а не правильности. (А слияние в буфере хранилища может иногда превращать инструкции байтового хранилища в эффективную фиксацию полного слова в кеш L1d.)
О фразировке Страуструпа
Я не думаю, что это очень точное, ясное или полезное утверждение. Было бы точнее сказать, что современные процессоры не могут загружать или хранить что-либо меньшее, чем строка кэша. (Хотя это неверно для некэшируемых областей памяти, например, для MMIO.)
Вероятно, было бы лучше просто привести гипотетический пример, чтобы поговорить о моделях памяти, чем предполагать, что реальное оборудование такое. Но если мы попытаемся, возможно, мы сможем найти интерпретацию, которая не так очевидна или совершенно неверна, что могло быть тем, о чем, возможно, думал Страуструп, когда писал это, чтобы представить тему моделей памяти. (Извините, этот ответ такой длинный; в итоге я много писал, догадываясь, что он имел в виду и о связанных темах ...)
Или, может быть, это еще один случай, когда разработчики языков высокого уровня не являются экспертами по оборудованию или, по крайней мере, иногда делают неверные заявления.
Я думаю, что Страуструп говорит о том, как процессоры работают внутренне для реализации инструкций по хранению байтов. Он предполагает, что ЦП без четко определенной и разумной модели памяти мог бы реализовать байтовое хранилище с неатомарным RMW содержащего слова в строке кэша или в памяти для ЦП без кеша. .
Даже это более слабое утверждение о внутреннем (не видимом извне) поведении неверно для высокопроизводительных процессоров x86. Современные процессоры Intel не имеют потери пропускной способности для хранилищ байтов или даже невыровненных хранилищ слов или векторов, которые не пересекают границу строки кэша. AMD похожа.
Если бы байтовые или невыровненные хранилища должны были выполнить цикл RMW, когда хранилище зафиксировано в кэше L1D, это повлияло бы на пропускную способность хранилища и / или загрузки инструкций / uop таким образом, который мы могли бы измерить с помощью счетчиков производительности. (В тщательно спланированном эксперименте, исключающем возможность объединения хранилищ в буфере хранилища перед фиксацией кеш-памяти L1d, скрывая стоимость, потому что исполнительные блоки хранилища могут запускать только 1 хранилище за такт на текущих процессорах.)
Однако некоторые высокопроизводительные проекты для ISA, отличных от x86, действительно используют атомарный цикл RMW для внутренней фиксации хранилищ в кэше L1d. Существуют ли какие-либо современные процессоры, в которых кешируемое хранилище байтов на самом деле медленнее, чем хранилище слов? Строка кеша все время остается в состоянии MESI Exclusive / Modified, поэтому не может создавать никаких проблем с правильностью, только небольшое снижение производительности. Это сильно отличается от того, что может наступить на магазины других процессоров. (Приведенные ниже аргументы о том, что это не происходит, все еще применимы, но в моем обновлении, возможно, пропущены некоторые вещи, которые все еще утверждают, что атомарный кеш-RMW маловероятен.)
(На многих ISA, отличных от x86, невыровненные хранилища вообще не поддерживаются или используются реже, чем в программном обеспечении x86. А слабоупорядоченные ISA позволяют больше объединяться в буферах хранилища, поэтому меньшее количество инструкций хранилища байтов на самом деле приводит к одно- фиксация байта в L1d. Без этих мотивов для причудливого (энергоемкого) оборудования для доступа к кеш-памяти слово RMW для разрозненных хранилищ байтов является приемлемым компромиссом в некоторых проектах.)
Alpha AXP, высокопроизводительный RISC-проект 1992 года, известный (и уникальный среди современных ISA, не использующий DSP) опускает инструкции загрузки / сохранения байтов до Alpha 21164A (EV56) в 1996 году. Очевидно, они не считали word-RMW жизнеспособным вариантом для реализации байтовых хранилищ, потому что одним из упомянутых преимуществ реализации только 32-битных и 64-битных выровненных хранилищ было более эффективное ECC для кэша L1D. «Традиционный SECDED ECC потребует 7 дополнительных битов вместо 32- битовые гранулы (22% накладных расходов) по сравнению с 4 дополнительными битами по сравнению с 8-битными гранулами (50% накладных расходов) ». (Ответ @Paul A. Clayton о словарной и байтовой адресации содержит некоторые другие интересные вещи, связанные с компьютерной архитектурой.) Если бы байтовые хранилища были реализованы с помощью word-RMW, вы все равно могли бы выполнять обнаружение / исправление ошибок с детализацией по словам.
По этой причине текущие процессоры Intel используют только четность (не ECC) в L1D. См. это Вопросы и ответы об аппаратном (не) устранении «тихих хранилищ»: проверка старого содержимого кеша перед записью, чтобы не пометить строку как грязную, если она совпадает, потребует RMW вместо простого хранилища, и это серьезное препятствие.
Оказывается, некоторые высокопроизводительные конвейерные проекты действительно используют атомарное слово-RMW для фиксации в L1d, несмотря на то, что это останавливает конвейер памяти, но (как я утверждаю ниже) это намного менее вероятно, что кто-либо сделает внешнее -видимый RMW в RAM.
Word-RMW не подходит для MMIO также хранит байты, поэтому, если у вас нет архитектуры, которая не требует хранилищ вложенных слов для ввода-вывода, вы d требуется особая обработка ввода-вывода (например, разреженное пространство ввода-вывода Alpha где загрузка / сохранение слов были сопоставлены с байтовой загрузкой / хранением, чтобы можно было использовать стандартные карты PCI вместо необходимости специального оборудования без байтовых регистров ввода-вывода).
Как указывает @Margaret, контроллеры памяти DDR3 могут хранить байты, задавая управляющие сигналы, которые маскируют другие байты пакета. Те же механизмы, которые передают эту информацию контроллеру памяти (для некэшированных хранилищ), также могут передавать эту информацию вместе с загрузкой или сохранением в пространство MMIO. Таким образом, существуют аппаратные механизмы для реального хранения байтов даже в системах с пакетной памятью, и весьма вероятно, что современные процессоры будут использовать это вместо реализации RMW, потому что это, вероятно, проще и намного лучше. для корректности MMIO.
Как сколько циклов и какой размер потребуется для выполнения передачи длинного слова в ЦП, показывает, как микроконтроллер ColdFire сигнализирует о размере передачи (байт / слово / длинное слово / 16-байтовая строка) с внешними сигнальными линиями, позволяя он выполняет загрузку / сохранение байтов, даже если 32-разрядная память была подключена к его 32-разрядной шине данных. Нечто подобное предположительно типично для большинства настроек шины памяти (но я не знаю). Пример ColdFire усложняется тем, что его можно настроить на использование 16- или 8-битной памяти, что требует дополнительных циклов для более широких передач. Но не важно, что важно то, что он имеет внешнюю сигнализацию для размера передачи, чтобы сообщить аппаратному обеспечению памяти, какой байт он на самом деле записывает.
Страуструпа следующий абзац -
"Модель памяти C ++ гарантирует, что два потока выполнение может обновлять и получать доступ к отдельным участкам памяти, не мешая друг другу. Это именно то, чего мы наивно ожидали. Задача компилятора - защитить нас от иногда очень странного и тонкого поведения современного оборудования. и комбинация аппаратных средств позволяет добиться этого от компилятора. ... "
Очевидно, он думает, что настоящее современное оборудование не может обеспечить «безопасную» загрузку / сохранение байтов. Люди, которые разрабатывают модели аппаратной памяти, согласны с людьми C / C ++ и понимают, что инструкции по хранению байтов не были бы очень полезны для программистов / компиляторов, если бы они могли наступать на соседние байты.
Все современные (не-DSP) архитектуры, за исключением раннего Alpha AXP, имеют инструкции по хранению и загрузке байтов, и, AFAIK, все они архитектурно определены так, чтобы не влиять на соседние байты. Однако они достигают этого на аппаратном уровне, а программное обеспечение не нужно заботиться о правильности. Даже самая первая версия MIPS (в 1983 г.) имела загрузку / сохранение байтов и полуслов, и это очень ориентированная на слова ISA.
Однако он на самом деле не утверждает, что большинству современного оборудования требуется какая-либо специальная поддержка компилятора для реализации этой части модели памяти C ++, просто некоторые могут. Возможно, он действительно говорит только о DSP с адресацией по словам во втором абзаце (где реализации C и C ++ часто используют 16- или 32-битные char
, как именно тот вид обходного пути компилятора, о котором говорил Страуструп.)
Большинство «современных» процессоров (включая все x86) имеют кэш L1D. Они будут извлекать целые строки кеша (обычно 64 байта) и отслеживать грязные / не грязные строки для каждой строки кеша. Таким образом, два соседних байта практически идентичны двум соседним словам, если они оба находятся в одной строке кэша. Запись одного байта или слова приведет к выборке всей строки и, в конечном итоге, обратная запись всей строки. См. Что каждый программист должен знать о памяти Ульриха Дреппера. Вы правы в том, что MESI (или производная, такая как MESIF / MOESI) гарантирует, что это не проблема. (Но опять же, это потому, что оборудование реализует разумную модель памяти.)
Хранилище может фиксироваться в кэше L1D только тогда, когда линия находится в состоянии Modified (MESI). Таким образом, даже если внутренняя аппаратная реализация медленная для байтов и требует дополнительного времени для слияния байта с содержащим слово в строке кэша, это эффективно атомарное чтение и изменение записи, если оно не позволяет строка, которую нужно сделать недействительной и повторно получить между чтением и записью. (В этом кэше строка находится в измененном состоянии, ни один другой кеш не может иметь действительный копия). См. комментарий @ old_timer то же самое (но и для RMW в контроллере памяти).
Это проще, чем, например, атомарный xchg
или add
из регистра, который также требует ALU и доступа к регистру, поскольку все задействованное HW находится на одной и той же стадии конвейера, которая может просто остановиться на один или два дополнительных цикла. Это явно плохо для производительности и требует дополнительного оборудования, чтобы этап конвейера сигнализировал о том, что он остановился. Это не обязательно противоречит первому утверждению Страуструпа, потому что он говорил о гипотетическом ISA без модели памяти, но это все же натяжка.
На одноядерном микроконтроллере внутреннее слово-RMW для кэшированных хранилищ байтов было бы более правдоподобным, поскольку от других ядер не поступало бы недействительных запросов, на которые им пришлось бы отложить ответ во время атомарного обновления слова кеш-слова RMW. . Но это не помогает при вводе-выводе в некэшируемые регионы. Я говорю «микроконтроллер», потому что другие конструкции одноядерных процессоров обычно поддерживают какой-то многопроцессорный SMP.
Многие RISC ISA не поддерживают загрузку / сохранение невыровненных слов с помощью одной инструкции, но это отдельная проблема (сложность заключается в обработке случая, когда загрузка охватывает две строки кэша или даже страницы, что не может произойти с байтами или выровненными полуслова). Однако все больше и больше ISA добавляют в последние версии гарантированную поддержку невыровненной загрузки / сохранения. (например, MIPS32 / 64 Release 6 в 2014 г., и я думаю, что AArch64 и последние 32 -бит ARM).
4-е издание книги Страуструпа было опубликовано в 2013 году, когда Alpha уже давно умерла. Первое издание было опубликовано в 1985 году, когда RISC был новой большой идеей (например, Stanford MIPS в 1983 году , согласно временной шкале Википедии о компьютерном оборудовании, но "современные «В то время процессоры имели побайтовую адресацию с байтовыми хранилищами. Cyber CDC 6600 был с адресацией по словам и, вероятно, все еще существовал, но не мог называться современными.
Даже очень ориентированные на слова RISC-машины, такие как MIPS и SPARC имеет байтовое хранилище и байтовую загрузку (со знаком или нулевое расширение) инструкции. Они не поддерживают загрузку невыровненных слов, упрощая кэш (или доступ к памяти, если кеша нет) и порты загрузки, но вы можете загрузить любой отдельный байт с помощью одной инструкции и, что более важно, сохранить байт без какой-либо архитектурно видимой неатомарной перезаписи окружающих байтов. (Хотя кэшированные магазины могут
Я полагаю, что C ++ 11 (который вводит в язык модель памяти с поддержкой потоков) в Alpha потребуется использовать 32-битный char
, если нацелена на версию Alpha ISA без хранилищ байтов. Или ему пришлось бы использовать программное обеспечение atomic-RMW с LL / SC, когда оно не могло доказать, что никакие другие потоки не могут иметь указатель, который позволил бы им записывать соседние байты.
IDK насколько медленные инструкции загрузки / сохранения байтов в любых процессорах, где они реализованы на аппаратном уровне, но не так дешевы, как загрузка / сохранение слов. Загрузка байтов на x86 обходится дешево, если вы используете movzx/movsx
, чтобы избежать частичной регистрации ложных зависимостей или слияния срывов. На AMD до Ryzen _6 _ / _ 7_ требуется дополнительный упор ALU, но в противном случае расширение нуля / знака обрабатывается прямо в порту загрузки на процессорах Intel и AMD.) Основным недостатком x86 является то, что вам нужна отдельная инструкция загрузки вместо использования операнда памяти в качестве источника для инструкции ALU (если вы добавляете нулевой расширенный байт к 32 -битное целое число), что позволяет сэкономить пропускную способность и размер кода интерфейсного модуля. Или, если вы просто добавляете байт в регистр байтов, в x86 практически нет недостатков. В любом случае ISA RISC для загрузки и сохранения всегда нуждаются в отдельных инструкциях по загрузке и хранению. Хранилища байтов x86 не дороже 32-битных хранилищ.
В качестве проблемы производительности хорошая реализация C ++ для оборудования с медленными хранилищами байтов может поместить каждый char
в свое собственное слово и по возможности использовать загрузку / сохранение слов (например, для глобальных объектов вне структур и для локальных переменных в стеке). IDK, если какие-либо реальные реализации MIPS / ARM / любого другого имеют медленную загрузку / сохранение байтов, но если да, возможно, у gcc есть -mtune=
параметры для управления этим.
Это не помогает для _10 _ или разыменование char *
, когда вы не знаете, куда он может указывать. (Это включает volatile char*
, который вы бы использовали для MMIO.) Таким образом, если компилятор + компоновщик помещают char
переменных в отдельные слова, это не полное решение, это просто снижение производительности, если истинное хранилище байтов работает медленно.
PS: Подробнее об альфа-версии:
Альфа интересна по многим причинам: одна из немногих «чистых» 64-битных ISA, а не расширение существующей 32-битной ISA. И одна из самых свежих ISA с чистого листа, Itanium - еще одна, созданная несколькими годами позже, в которой были предприняты некоторые изящные идеи архитектуры процессора.
Из Linux Alpha HOWTO.
Когда была представлена архитектура Alpha, она была уникальной среди архитектур RISC в плане отказа от 8-битной и 16-битной загрузки и сохранения. Он поддерживал 32-битные и 64-битные загрузки и сохранения (длинное и четверное слово в номенклатуре Digital). Соавторы (Dick Sites, Rich Witek) обосновали это решение, сославшись на преимущества:
- Поддержка байтов в подсистеме кэша и памяти имеет тенденцию замедлять доступ для 32-битных и 64-битных величин.
- Поддержка байтов затрудняет встраивание высокоскоростной схемы исправления ошибок в подсистему кэш-памяти / памяти.
Alpha компенсирует это, предоставляя мощные инструкции для управления байтами и группами байтов в 64-битных регистрах. Стандартные тесты для строковых операций (например, некоторые из тестов Byte) показывают, что Alpha очень хорошо справляется с манипуляциями с байтами.
person
Peter Cordes
schedule
18.10.2017
char
без неатомарных RMW окружающих данных. (Таким образом,char
должен иметь размер слова на машинах, которые не могут атомарно хранить только байт, и большинство реализаций C ++ для современных процессоров, кроме DSP, имеют 8-битныеchar
). Страуструп совершенно неправ в этом (особенно в 2017 году). В любом случае, если вы не хотите закрывать как дубликат, может быть, измените Q? - person Peter Cordes   schedule 13.10.2017char b,c;
в то же слово. Они оба однобайтовые, и никто не говорит о том, чтобы получить их обоих с невыровненной нагрузкой. (OP процитировал полный абзац позже.) - person Peter Cordes   schedule 13.10.2017