Может ли современное оборудование x86 не хранить в памяти ни одного байта?

Говоря о модели памяти C ++ для параллелизма, Страуструп язык программирования C ++, 4-е изд., Разд. 41.2.1, говорит:

... (как и большинство современного оборудования) машина не могла загрузить или сохранить что-либо меньшее, чем слово.

Однако мой процессор x86, которому несколько лет, может хранить и хранит объекты размером меньше слова. Например:

#include <iostream>
int main()
{
    char a =  5;
    char b = 25;
    a = b;
    std::cout << int(a) << "\n";
    return 0;
}

Без оптимизации GCC компилирует это как:

        [...]
        movb    $5, -1(%rbp)   # a =  5, one byte
        movb    $25, -2(%rbp)  # b = 25, one byte
        movzbl  -2(%rbp), %eax # load b, one byte, not extending the sign
        movb    %al, -1(%rbp)  # a =  b, one byte
        [...]

Комментарии написаны мной, но сборка - GCC. Конечно, все работает нормально.

Очевидно, я не понимаю, о чем говорит Страуструп, когда объясняет, что оборудование может загружать и хранить не меньше, чем слово. Насколько я могу судить, моя программа ничего не делает , а загружает и сохраняет объекты размером меньше слова.

Тщательная ориентация C ++ на недорогие, дружественные к оборудованию абстракции отличает C ++ от других языков программирования, которые легче освоить. Поэтому, если у Страуструпа есть интересная мысленная модель сигналов в автобусе или есть что-то еще в этом роде, то я хотел бы понять модель Страуструпа.

О чем говорит Страуструп, пожалуйста?

БОЛЬШЕ ЦИТАТЫ С КОНТЕКСТОМ

Вот цитата Страуструпа в более полном контексте:

Подумайте, что могло бы случиться, если бы компоновщик разместил [переменные типа char, например] c и b в одном и том же слове в памяти и (как и большинство современного оборудования) машина не могла бы загрузить или сохранить что-либо меньшее, чем слово ... Без колодца -определенная и разумная модель памяти, поток 1 может прочитать слово, содержащее b и c, изменить c и записать слово обратно в память. В то же время поток 2 может делать то же самое с b. Затем любой поток, которому удалось прочитать слово первым, и какой поток сумел записать свой результат обратно в память последним, определит результат ...

ДОПОЛНИТЕЛЬНЫЕ ЗАМЕЧАНИЯ

Я не верю, что Страуструп говорит о строках кеша. Даже если бы он был, насколько я знаю, протоколы согласованности кэша прозрачно справились бы с этой проблемой, за исключением, может быть, аппаратного ввода-вывода.

Я проверил технический паспорт своего процессора. Электрически мой процессор (Intel Ivy Bridge), кажется, обращается к памяти DDR3L с помощью какой-то 16-битной схемы мультиплексирования, поэтому я не знаю, о чем это. Однако мне не ясно, имеет ли это какое-то отношение к точке зрения Страуструпа.

Страуструп - умный человек и выдающийся ученый, поэтому я не сомневаюсь, что он берет в расчет что-то разумное. Я смущен.

См. Также этот вопрос. Мой вопрос во многом похож на связанный вопрос, и здесь также могут быть полезны ответы на связанный вопрос. . Однако мой вопрос касается также модели оборудования / шины, которая мотивирует C ++ быть таким, какой он есть, и заставляет Страуструпа писать то, что он пишет. Я не ищу ответа только относительно того, что стандарт C ++ формально гарантирует, но также хочу понять, почему стандарт C ++ гарантирует это. Какая основная мысль? Это тоже часть моего вопроса.


person thb    schedule 13.10.2017    source источник
comment
У CPU есть инструкции для работы с отдельными байтами. Но RAM хранится дискретными словами; в зависимости от того, к какому конкретному семейству принадлежит процессор x86, два, четыре или, возможно, восемь байтов. Я не припомню, чтобы в верхней части моей головы вы были в значительной степени изолированы от этих вещей, когда взламывали C ++. Итак, когда процессору нужно возиться с одним байтом, он извлекает все слово из ОЗУ, портит один байт, а затем отправляет его туда, откуда оно пришло. Это, по сути, краткое изложение общего процесса, но, конечно, на самом деле гораздо сложнее.   -  person Sam Varshavchik    schedule 13.10.2017
comment
@old_timer: Интересно. Я думал, что протокол MESI решил эту проблему (за исключением, возможно, снижения производительности из-за ненужной в противном случае обратной записи в строку кэша / invalidation), но я недостаточно знаю об этом, чтобы быть уверенным.   -  person thb    schedule 13.10.2017
comment
@SamVarshavchik: Полезный комментарий. Я уже немного читал подобные вещи, так что, по крайней мере, это звучит правильно. Однако разве мой код не является буквально контрпримером?   -  person thb    schedule 13.10.2017
comment
в зависимости от определения процессора ядро ​​процессора может отправлять байтовую запись, это ядро ​​не выполняет операции чтения-изменения-записи, которые происходят по линии, а в системе x86 есть много слоев и много шин, в идеале только один уровень должен иметь дело с этим, а остальные уровни получают транзакции полной ширины, и чтение-изменение-запись не являются проблемой. Об этом позаботится оборудование - вот краткий ответ, может быть, на 8088/8086 об этом было о чем беспокоиться, но если вы наблюдаете подобные проблемы на чем-то недавнем, купите новый компьютер.   -  person old_timer    schedule 13.10.2017
comment
@SamVarshavchik: Это не так. Современные процессоры будут извлекать целые строки кеша (обычно 64 байта) и отслеживать грязные / не грязные строки для каждой строки кэша. Таким образом, два соседних байта практически идентичны двум соседним словам, если они находятся в одной строке кэша. Запись одного приведет к выборке всей строки и, в конечном итоге, обратной записи всей строки. Лишь очень немногие процессоры не имеют инструкций по загрузке и сохранению байтов (ранний Alpha AXP - один из классических примеров: обработка символов в строке включала загрузку слов и сдвиг / маску). работаю над написанием этого в качестве ответа.   -  person Peter Cordes    schedule 13.10.2017
comment
кэш создается из памяти меньшего размера, не обязательно на всю ширину строки кэша, он зависит от реализации / производителя, даже при изменении того же дизайна более высокого уровня на новый процесс, что означает новую библиотеку ячеек, что означает новую ширину. возможность не выполнять чтение-изменение-запись полной ширины строки кэша становится возможной, но на усмотрение дизайнера, так как с помощью программного обеспечения они могут абстрагировать ее на любом уровне, который они хотят… может также быть полной шириной, зависит от библиотеки ячеек. предоставлено Intel, как проектирует микросхемы, так и производит или создает библиотеку ячеек, чтобы они могли влиять на то, что доступно   -  person old_timer    schedule 13.10.2017
comment
потребовалось бы больше логики для использования преимуществ частичных модификаций строки, вы могли бы, например, делать две или более модификации строки за раз, но для этого потребуется больше логики, поэтому полная модификация строки действительно имеет наибольший смысл и требует минимум работы от логического дизайнера / дизайнера.   -  person old_timer    schedule 13.10.2017
comment
@old_timer: распространено ли отслеживать грязные / чистые данные с меньшей степенью детализации, чем полная строка кеша? Это отличается от простого использования меньших строк кэша, но усложняет управление состоянием. Я предполагаю, что вы, вероятно, сочли бы всю строку грязной для MESI, но, возможно, оптимизировали бы обратную запись в DRAM, если вы используете DRAM, который поддерживает меньшие размеры пакетов. (Размер естественного пакета SDRAM DDR1 / 2/3/4 составляет до 64 Б, IIRC. См. What Every Программист должен знать о памяти)   -  person Peter Cordes    schedule 13.10.2017
comment
чистый / грязный имеет смысл только для всей строки, определение строки кеша определяется реализацией, x86 vs arm vs other, этот чип x86 vs тот, этот дизайн материнской платы против этого и т. д.   -  person old_timer    schedule 13.10.2017
comment
Я прочитал этот вопрос, поскольку Страуструп пытается передать, что память не имеет ширины 8 бит (хотя это часто бывает, но не обрабатывается таким образом), особенно память в кешах по пути к драму. Это может быть что угодно, от ширины линии до долей, но большинству людей, даже некоторым разработчикам микросхем, не нужно об этом знать или заботиться. Вы хотите записать 8 бит в строку, в худшем случае вам придется выполнить чтение-изменение-запись всей строки, но это атомарно в разумном дизайне, поскольку круговой обход похож на два или около того часов, а процессор как в ядро процессора этого не делает, это безумие.   -  person old_timer    schedule 13.10.2017
comment
DDR1 / 2/3/4 фактически реализован в частях шириной 8, 16 или 32 бит, поэтому ваша карта памяти имеет 8 частей с одной стороны или обе, или 4 части с одной стороны или обе (или, возможно, даже 2 с одной стороны. или и то, и другое на этом пути к DDR3). Но контроллеры памяти не обращаются к ним таким образом (в дизайне ПК x86), но тот же IP-адрес контроллера памяти вполне может иметь доступ к ним как 32-битную ширину, скажем, для другой платформы, например телефона. Зависит от того, какой ip вы покупаете и что вы с ним делаете.   -  person old_timer    schedule 13.10.2017
comment
Если вы хотите убедиться, что можете записать байт, используйте любую программную магию (или сборку), которую вы выберете, чтобы записать слово / двойное слово по некоторому адресу, а затем записать байт в один из тех байтов, которые вы только что написали, прочитайте слово обратно, обратите внимание, что ваша запись байта сработала ... если вы думаете, что есть магия оптимизации, тогда пусть программа подождет несколько часов между каждой записью и чтением, сделайте другие вещи, много программ переключаются и выключаются ... посмотрите, что байт изменился ...   -  person old_timer    schedule 13.10.2017
comment
Вы также не получите одновременно, даже если двойная / многопортовая ... транзакции на каком-то уровне становятся сериализованными одна, а затем другая.   -  person old_timer    schedule 13.10.2017
comment
stackoverflow .com / questions / 19903338 /   -  person NoSenseEtAl    schedule 13.10.2017
comment
@thb: я думаю, что этот вопрос является возможным дубликатом того, который вы связали: stackoverflow.com/questions/19903338/. Ответы там указывают на то, что C ++ 11 в настоящее время требует возможности изменять char без неатомарных RMW окружающих данных. (Таким образом, char должен иметь размер слова на машинах, которые не могут атомарно хранить только байт, и большинство реализаций C ++ для современных процессоров, кроме DSP, имеют 8-битные char). Страуструп совершенно неправ в этом (особенно в 2017 году). В любом случае, если вы не хотите закрывать как дубликат, может быть, измените Q?   -  person Peter Cordes    schedule 13.10.2017
comment
@PeterCordes - уверены ли мы, что правильно интерпретируем вопрос? Я беру цитату Страуструпа по поводу выравнивания памяти, а не того, что может делать ЦП. Конечно, ЦП может вертеть биты и байты, но это не меняет требования к выравниванию памяти. Возможно, я неправильно интерпретирую то, что он говорит, но я вижу, что в дискуссии смешиваются яблоки и апельсины.   -  person David C. Rankin    schedule 13.10.2017
comment
@ DavidC.Rankin: Контекст - это то, что происходит, если компоновщик помещает char b,c; в то же слово. Они оба однобайтовые, и никто не говорит о том, чтобы получить их обоих с невыровненной нагрузкой. (OP процитировал полный абзац позже.)   -  person Peter Cordes    schedule 13.10.2017
comment
Я полагаюсь на вашу коллективную мудрость, я просто хотел убедиться, что ничто не ускользнет от трещин.   -  person David C. Rankin    schedule 13.10.2017
comment
@PeterCordes: Вы правы. Я не могу полностью отделить свой вопрос от предыдущего вопроса, за исключением того, что мой вопрос сформулирован более точно. Однако есть и другие незначительные отличия. В меру своих несовершенных способностей я отредактировал вопрос, чтобы подчеркнуть различия, такие как они есть.   -  person thb    schedule 13.10.2017
comment
@ DavidC.Rankin: В конце концов, я думаю, что у тебя все в порядке. Возможно, Страуструп на самом деле говорил не о внешне видимом поведении процессора, а только о том, что они могут делать внутри. Я наконец закончил написать ответ после нескольких переделок.   -  person Peter Cordes    schedule 18.10.2017


Ответы (6)


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) обосновали это решение, сославшись на преимущества:

  1. Поддержка байтов в подсистеме кэша и памяти имеет тенденцию замедлять доступ для 32-битных и 64-битных величин.
  2. Поддержка байтов затрудняет встраивание высокоскоростной схемы исправления ошибок в подсистему кэш-памяти / памяти.

Alpha компенсирует это, предоставляя мощные инструкции для управления байтами и группами байтов в 64-битных регистрах. Стандартные тесты для строковых операций (например, некоторые из тестов Byte) показывают, что Alpha очень хорошо справляется с манипуляциями с байтами.

person Peter Cordes    schedule 18.10.2017
comment
Я не знаю, чем вы занимаетесь в своей повседневной работе, но я понимаю и признаю глубину знаний, необходимых для сбора в уме такого уровня детализации, охватывающего цикл RMW и поведение ЦП / кеша в течение двух десятилетий и более. , разработки оборудования на уровне байт / слово + нагрузка. Я всегда узнаю, сколько мне еще предстоит узнать из ваших ответов :) - person David C. Rankin; 19.10.2017
comment
@ DavidC.Rankin: Я занимаюсь оптимизацией / настройкой внештатных сотрудников, когда я не участвую в разработке бесплатного программного обеспечения или не улучшаю уровень знаний здесь, на SO. И спасибо, я просто надеюсь, что я прав относительно частей, о которых я догадывался (например, что очень немногие микроархитектуры действительно используют внутренний RMW для байтовых хранилищ.) У меня действительно нет практического опыта работы с большим количеством, кроме недавнего x86, но я Давно интересуюсь чтением о конструкции процессоров. - person Peter Cordes; 19.10.2017
comment
Также wrt. писать изобретения, см. Атомное оружие Херба Саттера‹ ›Оружие, часть 2 @ 3:14 - person Daniel Nitzan; 23.01.2021
comment
^^ хотя следует отметить, что внутренние (атомарные) word-RMW для байтовых хранилищ разрешены ISO C ++, поскольку они не внешне видимы. - person Daniel Nitzan; 23.01.2021

Не только процессоры x86 способны читать и писать один байт, но и все современные процессоры общего назначения способны на это. Что еще более важно, большинство современных процессоров (включая x86, ARM, MIPS, PowerPC и SPARC) способны атомарно читать и записывать отдельные байты.

Я не совсем понимаю, о чем имел в виду Страуструп. Раньше были машины с адресацией по нескольким словам, которые не были способны к 8-битной байтовой адресации, такие как Cray, и, как упомянул Питер Кордес, ранние процессоры Alpha не поддерживали байтовую загрузку и сохранение, но сегодня это единственные процессоры, не способные к байтовой адресации. загрузки и магазины - это определенные DSP, используемые в нишевых приложениях. Даже если мы предположим, что он имеет в виду, что большинство современных процессоров не имеют атомарной байтовой нагрузки и хранит, это не относится к большинству процессоров.

Однако простые атомарные загрузки и сохранения не очень полезны в многопоточном программировании. Вам также обычно требуются гарантии заказа и способ сделать операции чтения-изменения-записи атомарными. Еще одно соображение заключается в том, что хотя ЦП может иметь инструкции загрузки и сохранения байтов, компилятор не обязан их использовать. Компилятор, например, все еще мог бы сгенерировать код, описанный Страуструпом, загружая как b, так и c, используя инструкцию загрузки одного слова в качестве оптимизации.

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

person Ross Ridge    schedule 13.10.2017
comment
Я не вижу цитату, в которой говорится, что чтение слова за одну загрузку является оптимизацией для получения b и c в одной инструкции. Нередко (как правило, не обязательно для x86) при чтении считывается ширина шины, а в процессоре соответствующая байтовая полоса изолируется по мере приближения к ядру процессора. Я думаю, что цитата предполагает, что процессор читает все слово, изменяет один байт и записывает его обратно, и если у вас есть начало чтения потока 1, затем начало чтения потока 2, затем запись потока 1, запись потока 2, байт потока 1 потерян. но это работает не так. - person old_timer; 13.10.2017
comment
старые процессоры ARM также не имеют инструкций по загрузке / сохранению полуслова - person phuclv; 13.10.2017
comment
проблема не требует 16-битного местоположения, два 8-битных байта в 32-битном местоположении также будут работать. arm1 выглядит как 32-битная шина данных, поэтому проблема могла бы возникнуть, если бы здесь возникла проблема. (а слово в руке - 32 бита). Если я правильно это читаю, то у armv3 тоже не было strh, но мы, конечно, знаем, что у armv4 есть. - person old_timer; 13.10.2017
comment
Раньше я писал для процессора Texas TMS9995 на C. Этот процессор мог загружать / хранить только 16-битные слова. байтовые операции должны были выполняться как IIRC чтения-изменения-записи. - person Richard Hodges; 13.10.2017

Не уверен, что Страуструп имел в виду под "СЛОВОМ". Может это минимальный размер памяти машины?

В любом случае не все машины были созданы с разрешением 8 бит (BYTE). На самом деле я рекомендую эту замечательную статью Эрика С. Реймонда, описывающую историю компьютеров: http://www.catb.org/esr/faqs/things-every-hacker-once-knew/

«... Раньше также было общеизвестно, что 36-битные архитектуры объясняют некоторые неудачные особенности языка C. Исходная машина Unix, PDP-7, имела 18-битные слова, соответствующие полусловам на более крупных 36-битных компьютеры. Их более естественно представить в виде шести восьмеричных (3-битных) цифр ».

person Sharon Katz    schedule 13.10.2017
comment
Я не думаю, что это разумная интерпретация. Первое издание было опубликовано в 1985 году, и даже тогда современные подразумевали 8-битные байты. И слово определенно не означает байт; Машины с адресацией по словам назывались машинами с адресацией по словам, а не описывали, что они имеют 36-битные байты или что-то еще. Возможно, он хотел написать что-то подобное, но то, что он написал, просто неправильно. - person Peter Cordes; 13.10.2017
comment
Вы можете быть правы. Я не совсем понимаю, что он имел в виду. В любом случае, я думаю, что мой ответ проливает свет на вопрос о разрешении памяти машин, поэтому оставлю его с небольшим редактированием. - person Sharon Katz; 13.10.2017
comment
в восьмеричные дни 9-битный байт имел большой смысл, и были 9-битные байтовые системы с элементами данных размером 9, 18 и 36 бит. потому что это имело смысл ... слово не обязательно должно состоять из двух байтов, посмотрите на руку, и я предполагаю, что mips ... четыре байта в слово. - person old_timer; 13.10.2017
comment
В 1985 году определение слова было намного яснее, чем сейчас. Это была единица памяти максимального размера, которая могла быть извлечена или записана за минимальный цикл транзакции памяти и соответствовала размеру (большинства) регистров ЦП. Таким образом, 8086/80186/80286 имели размер слова 16 бит; 8088 был своеобразной эмуляцией 16-битных слов. В 80386/80486/80586 использовалось 32-битное слово. Его структура шины состояла из 4 проводов, которые указывали, какие из четырех байтов на шине данных, по которым выполняется транзакция, действительны, поэтому поддерживался непосредственный ввод-вывод однобайтов. - person wallyk; 13.10.2017
comment
Единственные современные процессоры того времени, которые не могли выполнять прямой байтовый ввод-вывод, - это мэйнфреймы CDC 1970-х годов и DEC Alpha. Я не понимаю, о чем думал Бьярн, когда делал это заявление. - person wallyk; 13.10.2017
comment
@wallyk: IDK, в каком издании книги появился этот абзац. Возможно, это произошло вскоре после того, как Alpha была представлена ​​(до того, как она получила байтовую загрузку / сохранение), и по какой-то причине Страуструп был в восторге от Alpha. Или он повторял то, что читал; Некоторые разработчики языков высокого уровня печально известны тем, что не являются экспертами по оборудованию или, по крайней мере, время от времени делают неверные заявления об оборудовании. (например, Андрей Александреску (известный C ++ STL) иногда неправильно понимает, на что похоже оборудование в своих лекциях, хотя в остальном они очень хороши, включая советы по производительности / оптимизации.) - person Peter Cordes; 13.10.2017
comment
@PeterCordes Я предполагаю, что он был представлен в 4-м издании, поскольку C ++ не имел модели параллелизма до C + 11. - person Ross Ridge; 13.10.2017
comment
@RossRidge: Надеюсь, что нет. Как указывает KerrekSB на ранее Q о том же абзаце, это не гонка данных C ++ 11 для одновременной записи в соседние элементы char array[], поэтому char не может быть одним байтом на машинах без хранилищ байтов. Кроме того, в цитате говорится, что без четко определенной и разумной модели памяти. Если это было новым в 4-м издании, это действительно глупая ошибка. Надеюсь, это было намного раньше, когда Alpha была следующей большой вещью. - person Peter Cordes; 13.10.2017
comment
@PeterCordes Что ж, тем не менее, он находится в 4-м издании, опубликованном в 2013 году. В следующем абзаце говорится о ваших опасениях, утверждая, что модель памяти C ++ гарантирует, что два потока выполнения могут обновлять и получать доступ к отдельным ячейкам памяти, не мешая друг другу. Это именно то, чего мы наивно ожидали. Задача компилятора - защитить нас от порой очень странного и тонкого поведения современного оборудования. Как добиться этого с помощью комбинации компилятора и оборудования, зависит от компилятора. ... - person Ross Ridge; 13.10.2017
comment
начал пытаться прочитать эту ссылку, но RS232 - это не протокол, это стандарт электрического / распиновки, это то, что каждый должен знать время от времени, вы не можете сравнить его с USB, который является стандартом протокола вывода / электрического И. Поэтому мне пришлось прекратить читать там. - person old_timer; 13.10.2017
comment
@RossRidge: Хорошие моменты. Я не пытался понять то, что формально гарантирует стандарт C ++. Как показывает Арне, C ++ может лучше всего помочь программисту повысить производительность, если программист поймет, почему стандарт C ++ гарантирует то, что он делает. - person thb; 13.10.2017

Автор, кажется, обеспокоен тем, что поток 1 и поток 2 попадают в ситуацию, когда чтение-изменение-запись (не в программном обеспечении, программное обеспечение выполняет две отдельные инструкции размером в байтах, где-то ниже по строке логика должна выполнять чтение- изменить-запись) вместо идеального чтения изменить запись чтение изменить запись, станет чтение, чтение, изменение, запись, запись или какой-либо другой момент времени, так что оба будут считывать предварительно измененную версию и последнюю, записавшуюся, выигрывают. чтение чтение изменение изменение запись запись или чтение изменение чтение изменение запись запись или чтение изменение чтение запись изменение запись.

Задача состоит в том, чтобы начать с 0x1122, и один поток хочет сделать его 0x33XX, другой хочет сделать его 0xXX44, но, например, с чтением, чтением, изменением, изменением, записью, вы получите 0x1144 или 0x3322, но не 0x3344

Разумный (системный / логический) дизайн просто не имеет этой проблемы, конечно, не для процессора общего назначения, такого как этот, я работал над проектами с такими проблемами синхронизации, как этот, но это не то, о чем мы здесь говорим, совершенно разные конструкции системы для разных целей. Чтение-изменение-запись не охватывает достаточно большое расстояние в разумном дизайне, а x86 - разумный дизайн.

Чтение-изменение-запись будет происходить очень близко к первому задействованному SRAM (в идеале L1 при запуске x86 обычным образом с операционной системой, способной запускать скомпилированные C ++ многопоточные программы) и происходить в течение нескольких тактовых циклов, поскольку RAM на скорости автобуса в идеале. И, как указал Питер, это считается всей строкой кэша, которая испытывает это внутри кеша, а не чтением-изменением-записью между ядром процессора и кешем.

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

Цитата говорит о том, что переменные назначены одному и тому же слову в памяти, так что это одна и та же программа. Две отдельные программы не собираются совместно использовать такое адресное пространство. так

Вы можете попробовать это, создать многопоточную программу, в которой один пишет, скажем, адрес 0xnnn00000, другой записывает адрес 0xnnnn00001, каждый выполняет запись, затем чтение или, лучше, несколько записей того же значения, что и одно чтение, проверьте, было ли чтение они записали байт, затем повторяется с другим значением. Пусть это поработает некоторое время, часы / дни / недели / месяцы. Посмотрите, не сбивает ли вы систему ... используйте сборку для фактических инструкций записи, чтобы убедиться, что она выполняет то, что вы просили (не C ++ или любой компилятор, который делает или утверждает, что не будет помещать эти элементы в одно и то же слово). Может добавлять задержки, чтобы обеспечить большее количество вытеснений кеша, но это снижает ваши шансы «одновременных» коллизий.

В вашем примере, если вы убедитесь, что вы не сидите по две стороны границы (кеш или другой), например 0xNNNNFFFFF и 0xNNNN00000, изолируйте две байтовые записи на адреса, такие как 0xNNNN00000 и 0xNNNN00001, есть инструкции, расположенные один за другим, и посмотрите, получите ли вы чтение, чтение, изменение, изменение, запись, запись. Оберните вокруг него тест, чтобы убедиться, что два значения различаются в каждом цикле, вы читаете слово в целом с любой задержкой позже, когда захотите, и проверяете два значения. Повторяйте в течение дней / недель / месяцев / лет, чтобы убедиться, что это не поможет. Ознакомьтесь с особенностями выполнения вашего процессора и микрокода, чтобы узнать, что он делает с этой последовательностью инструкций, и при необходимости создайте другую последовательность инструкций, которая пытается инициировать транзакции в течение нескольких или около того тактов на дальней стороне ядра процессора.

РЕДАКТИРОВАТЬ

проблема с цитатами в том, что все дело в языке и использовании. "как у большинства современного оборудования" ставит всю тему / текст в болезненное положение, это слишком расплывчато, одна сторона может возразить, все, что мне нужно сделать, это найти один случай, который верен, чтобы все остальное было правдой, равно как и одна сторона Могу поспорить, если найду один случай, все остальное - неправда. Использование слова вроде как-то беспорядочно с этим как возможная карта освобождения из тюрьмы.

Реальность такова, что значительный процент наших данных хранится в DRAM в 8-битной памяти, просто мы не получаем к ним доступ как 8-битную, обычно мы обращаемся к 8 из них за раз, шириной 64 бита. Через какое-то количество недель / месяцев / лет / десятилетий это утверждение будет неверным.

В большой цитате написано «в то же время», а затем говорится, что прочтите ... сначала, напишите ... последнее, ну, сначала и последнее, и в то же время не имеют смысла вместе, это параллельное или последовательное? Контекст в целом касается вышеупомянутых вариантов чтения, чтения, изменения, изменения, записи, записи, где у вас есть одна запись последней, и в зависимости от того, когда это одно чтение, определяет, произошли ли обе модификации или нет. Не примерно в то же время, что «как большинство современного оборудования» не имеет смысла, вещи, которые фактически начинаются параллельно в отдельных ядрах / модулях, в конечном итоге сериализуются, если они нацелены на один и тот же триггер / транзистор в памяти, в конечном итоге один должен ждать, пока другой пойдет первым. Поскольку я основан на физике, я не думаю, что это будет неправильным в ближайшие недели / месяцы / годы.

person old_timer    schedule 13.10.2017

Это правильно. Процессор x86_64, как и оригинальный процессор x86, не может читать или записывать что-либо меньшее, чем (в данном случае 64-битное) слово из rsp. на память. И обычно он не будет читать или писать меньше, чем целая строка кеша, хотя есть способы обойти кеш, особенно при записи (см. Ниже).

В этом контексте, однако, Страуструп относится к потенциальным гонкам данных (отсутствие атомарности на наблюдаемом уровне). Эта проблема корректности не имеет отношения к x86_64 из-за протокола согласованности кеша, о котором вы упомянули. Другими словами, да, ЦП ограничен передачей всего слова, но это прозрачно обрабатывается, и вам, как программисту, обычно не нужно об этом беспокоиться. Фактически, язык C ++, начиная с C ++ 11, гарантирует, что параллельные операции с отдельными участками памяти имеют четко определенное поведение, то есть такое, которое вы ожидаете. Даже если оборудование не гарантирует этого, реализация должна будет найти способ, создав, возможно, более сложный код.

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

  • First, and this is only relevant for people who write device drivers, or design devices, memory-mapped I/O may be sensitive to the way it is accessed. As an example, think of a device that exposes a 64-bit write-only command register in the physical address space. It may then be necessary to:
    • Disable caching. It is not valid to read a cache line, change a single word, and write back the cache line. Also, even if it were valid, there would still be a great risk that commands might be lost because the CPU cache is not written back soon enough. At the very least, the page needs to be configured as "write-through", which means writes take immediate effect. Therefore, an x86_64 page table entry contains flags that control the CPU's caching behavior for this page.
    • Убедитесь, что слово всегда пишется целиком на уровне сборки. Например. рассмотрим случай, когда вы записываете в регистр значение 1, за которым следует 2. Компилятор, особенно при оптимизации пространства, может решить перезаписать только наименее значимый байт, потому что остальные уже должны быть равны нулю (то есть для обычное ОЗУ), или вместо этого он может удалить первую запись, потому что это значение в любом случае будет немедленно перезаписано. Однако здесь не должно происходить ни того, ни другого. В C / C ++ ключевое слово volatile жизненно важно для предотвращения таких неподходящих оптимизаций.
  • Во-вторых, и это актуально практически для любого разработчика, пишущего многопоточные программы, протокол согласованности кэша, хотя и аккуратно предотвращает катастрофу, может иметь огромные потери производительности, если им «злоупотреблять».

Вот несколько надуманный пример очень плохой структуры данных. Предположим, у вас есть 16 потоков, разбирающих текст из файла. Каждый поток имеет id от 0 до 15.

// shared state
char c[16];
FILE *file[16];

void threadFunc(int id)
{
    while ((c[id] = getc(file[id])) != EOF)
    {
        // ...
    }
}

Это безопасно, потому что каждый поток работает в разных местах памяти. Однако эти ячейки памяти обычно находятся в одной строке кэша или, самое большее, разделены на две строки кэша. Затем используется протокол согласования кэша для правильной синхронизации доступа к c[id]. И в этом заключается проблема, потому что это заставляет каждый другой поток ждать, пока строка кэша не станет монопольно доступной, прежде чем делать что-либо с c[id], если только он уже не запущен на ядре, которое «владеет» строкой кеша. Предполагая, что несколько, например 16, ядра, когерентность кеша обычно постоянно передает строку кэша от одного ядра к другому. По понятным причинам этот эффект известен как «пинг-понг строки кэша». Это создает ужасное узкое место в производительности. Это результат очень плохого случая ложного совместного использования, то есть потоков, совместно использующих строку физического кэша, без фактического доступа к одним и тем же ячейкам логической памяти.

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

(Это значительно упрощено, поскольку существуют разные уровни кешей ЦП, и несколько ядер могут использовать один и тот же кеш L2 или L3, но это должно дать вам общее представление о проблеме.)

person Arne Vogel    schedule 13.10.2017
comment
Да, я думаю, что вы правильно поняли мой вопрос, его основной мотив и то, чем он тонко отличается от связанного ранее вопроса. Среди других хороших моментов вы привели краткий, интересный пример злоупотребления кешем (или можно было бы назвать это перегрузкой кеша?). - person thb; 13.10.2017
comment
@thb: У нас уже есть термин пинг-понг с кеш-строкой, чтобы описать проблему производительности при ложном совместном использовании. Пробуждение - это то, что происходит, когда ваш рабочий набор не помещается в кеш. (Это похоже на то, что происходит, когда ваш рабочий набор не помещается в ОЗУ, а подкачка для пространства подкачки происходит постоянно. Это более типичное использование термина «перебивание».) - person Peter Cordes; 18.10.2017
comment
Все текущие процессоры x86-64 могут записывать один байт в строку кэша без выполнения цикла RMW (даже не скрытого внутри атомарного цикла RMW; это имело бы видимые последствия для производительности). AFAIK они также могут делать однобайтовые хранилища в некэшируемых областях памяти. (См. Ответ Маргарет, чтобы узнать, как это может работать для DDR3. Для фактического MMIO он превращается в сообщение PCIe, а не в операцию контроллера памяти вообще.) Все остальное, что вы говорите в своем ответе, является правильным и связано с, но не связано с t прямо ответить на вопрос: / - person Peter Cordes; 18.10.2017
comment
@PeterCordes: Похоже, что процессор x86-64 действительно может записывать байты (выровненные по rsp. 16-битные или 32-битные слова) атомарно в основную память, когда на странице не включено кэширование или сквозное кэширование. Однако Intel и AMD не поддерживают частичную обратную запись строки кэша, или они? Я должен признать, что все это делает замечания Страуструпа весьма вводящими в заблуждение относительно этой архитектуры. Большое спасибо за информацию! - person Arne Vogel; 19.10.2017

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

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

Большая проблема с памятью - это плотность, попытка разместить больше битов на минимальной площади.
Для этого удобно, с точки зрения электрического проектирования, выставить шину как можно шире (это способствует повторному использованию некоторых электрических сигналов, хотя я не рассматривал конкретные детали).
Итак, в архитектуре, где требуется большая память (например, x86) или простой недорогой дизайн является благоприятным (например, когда задействованы RISC-машины), шина памяти больше, чем наименьшая адресуемая единица (обычно байт).

В зависимости от бюджета и наследия проекта память может выставлять более широкую шину отдельно или вместе с некоторыми сигналами боковой полосы для выбора в нее определенного устройства.
Что это означает на практике?
Если вы посмотрите на техническое описание DDR3 DIMM вы Вы увидите 64 контакта DQ0 – DQ63 для чтения / записи данных.
Это 64-битная шина данных, 8 байтов за раз.
Это 8 байты очень хорошо обоснованы в архитектуре x86 до такой степени, что Intel ссылается на нее в разделе WC своего руководства по оптимизации, где говорится, что данные передаются из буфера заполнения размером 64 байта (помните: мы пока игнорируют кеши, но это похоже на то, как строка кеша записывается обратно) пакетами по 8 байтов (надеюсь, непрерывно).

Означает ли это, что x86 может записывать только QWORDS (64-разрядные)?
Нет, в той же таблице данных показано, что каждый модуль DIMM имеет DM0 – DM7, DQ0 – DQ7 и DQS0– Сигналы DQS7 маскируют, направляют и стробируют каждый из 8 байтов 64-битной шины данных.

Таким образом, x86 может читать и записывать байты изначально и атомарно.
Однако теперь легко увидеть, что это может быть не для каждой архитектуры.
Например, видеопамять VGA была адресуемой DWORD (32-разрядная) и приспособление его к миру байтовой адресации 8086 привело к беспорядочным битовым плоскостям.

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

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

С хранилищами дело обстоит сложнее: если мы не можем просто записать ту часть слова, которую мы только что обновили, нам нужно записать и неизмененную оставшуюся часть.
ЦП или программист должен прочитать старое содержимое , обновите его и запишите обратно.
Это операция чтения-изменения-записи, и это основная концепция при обсуждении атомарности.

Рассмотреть возможность:

/* Assume unsigned char is 1 byte and a word is 4 bytes */
unsigned char foo[4] = {};

/* Thread 0                         Thread 1                 */
foo[0] = 1;                        foo[1] = 2;

Есть ли гонка данных?
Это безопасно для x86, потому что они могут записывать байты, но что, если архитектура не может?
Оба потока должны будут прочитать весь foo массив, изменить его и напишите его.
В псевдо-C это будет

/* Assume unsigned char is 1 byte and a word is 4 bytes */
unsigned char foo[4] = {};

/* Thread 0                        Thread 1                 */

/* What a CPU would do (IS)        What a CPU would do (IS) */
int tmp0 = *((int*)foo)            int tmp1 = *((int*)foo)

/* Assume little endian            Assume little endian     */
tmp0 = (tmp0 & ~0xff) | 1;         tmp1 = (tmp1 & ~0xff00) | 0x200;

/* Store it back                   Store it back            */
*((int*)foo) = tmp0;               *((int*)foo) = tmp1;

Теперь мы можем видеть, о чем говорил Страуструп: два хранилища *((int*)foo) = tmpX препятствуют друг другу, чтобы увидеть это, учитывая эту возможную последовательность выполнения:

int tmp0 = *((int*)foo)                   /* T0  */ 
tmp0 = (tmp0 & ~0xff) | 1;                /* T1  */        
int tmp1 = *((int*)foo)                   /* T1  */
tmp1 = (tmp1 & ~0xff00) | 0x200;          /* T1  */
*((int*)foo) = tmp1;                      /* T0  */
*((int*)foo) = tmp0;                      /* T0, Whooopsy  */

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

Учитывая, насколько распространена ситуация, изображенная в примере с игрушкой, Страуструп подчеркнул важность четко определенной модели памяти.
Формализация модели памяти - тяжелая работа, это утомительный, подверженный ошибкам и абстрактный процесс, поэтому я также вижу немного гордости в словах Страуструпа.

Я не обновлял модель памяти C ++, но обновлял различные элементы массива в порядке.
Это очень надежная гарантия.

Мы исключили кеши, но на самом деле это ничего не меняет, по крайней мере, для случая x86.
x86 записывает в память через кеши, кеши удаляются строками по 64 байта .
Внутри каждое ядро ​​может обновлять строку в любой позиции атомарно, если загрузка / сохранение не пересекает границу строки (например, записывая ее ближе к концу).
Этого можно избежать, естественным образом выровняв данные (можете ли вы доказать что?).

В среде с несколькими кодами / сокетами протокол согласованности кэша гарантирует, что только ЦП одновременно может свободно записывать в кэшированную строку памяти (ЦП, у которого она находится в эксклюзивном или измененном состоянии).
В основном , семейство протоколов MESI использует концепцию, аналогичную блокировке СУБД.
Для целей записи это приводит к «назначению» разных областей памяти разным процессорам.
Так что на самом деле это не влияет на обсуждение выше.

person Margaret Bloom    schedule 13.10.2017
comment
Проблема в том, что Бьярн сказал, что неспособность написать что-либо меньшее, чем слово, применима к самому современному оборудованию, но не похоже, что был какой-то момент времени, когда это было близко к истине. . - person BeeOnRope; 13.10.2017
comment
@BeeOnRope, это правда. Возможно, он столкнулся с совершенно другой категорией оборудования, но это маловероятно. Скорее всего, он пытался что-то сказать :) - person Margaret Bloom; 16.10.2017