All streams
Search
Write a publication
Pull to refresh
69
0.9
Иван Савватеев @SIISII

Микроконтроллеры, цифровая электроника, ОС…

Send message

Как по мне, если 2 и 3 Цивы улучшали предыдущие, то дальше пошла деградация...

Но, тем не менее, код компоновщиком генерится. А можно ещё про LTO вспомнить (link-time optimization) -- частным случаем чего можно рассматривать Ваши преобразования переходов на ПДПшке (подозреваю, что, скорей, СМке или какой-нить ДВКшке и прочей Электронике-60, но суть дела не меняется).

Ну, если совсем строго говорить, компоновщик временами код генерирует-таки -- по крайней мере, на определённых платформах. Но уж точно не код _start.

Например, для 32-разрядных ARMов он при необходимости генерирует так называемые veneer -- куски кода для обеспечения перехода от одной подпрограммы к другой, если обеспечить переход модификацией адреса в команде перехода невозможно (в ARM невозможно указать в командах переходов полный 32-разрядный адрес, поэтому, если на этапе компоновки окажется, что целевой адрес слишком далёк, приходится лепить этот самый veneer, что и выполняется компоновщиком).

При кэшах толщиной в дофига (сколько там на типичном топовом процессоре x86 или ARM, L3 уже под сотню мегов может подходить?) сброс и перезаполнение это очень серьёзно, может и не оправдываться...

Тут есть вариант. Предварительные соображения такие. 1) "Кэш толщиной в дофига" -- это третий и последующие уровни, и такие кэши общие для нескольких процессоров (ядер). 2) У мэйнфреймов вся память -- это память, без всяких побочных эффектов и т.д. и т.п., поэтому нет нужды индивидуально настраивать процессоры, что вот эту область кэшируем как WB, эту -- как WT, а вот эту вообще не кэшируем.

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

Ну и, наконец, я бы добавил команду сериализации области памяти -- дополнил бы нынешние команды "атомарной сериализации", выполняющей её лишь для своих операндов. Тогда поток, окончив формировать что-то для других потоков, может сериализировать лишь область общих данных, не затрагивая всё остальное -- а это потенциально может оказаться сильно быстрей. Естественно, старое ПО по-прежнему использует команду BCR 15,0, и всё работает -- просто менее эффективно (примерно как PTLB и IPTE: очистка всего TLB вообще, которую ввели вместе с динамической переадресацией, и очистка конкретного элемента таблицы страниц, которую добавили в поздних 370).

BCR 15,0

15 - это битовая маска, которая означает переход по любому коду условия. Это не опция, эта особенность была ещё в System/360, просто нормально описана уже в 370, когда кеши и конвейер стали более распространенными.

Нет, речь именно о BCR 14,0 -- расширении, добавленном в z/Architecture.

Ну если совсем формально - для остальных архитектур тут тоже нет совсем идеальных гарантий в общем случае

Если я правильно помню, в IA-32 проц, выполняющий запись в память, обязан разослать уведомления об этом всем другим процам, а те, в свою очередь, должны аннулировать соответствующие строки своих кэшей. Соответственно, на IA-32 подобный кодовый фрагмент не зациклится: когда до второго процессора дойдёт уведомление о записи, он очистит свою строку и при следующем сравнении прочитает реальное содержимое памяти, где уже будет лежать новое значение.

Считается, что эта опция гарантированно есть?

Может, указан тип целевого процессора, где оно точно имеется, и компилятор об этом знает?

цена в виде полного сброса кэша на чём-то сложнее одиночной атомарной операции (даже не CAS) мне не нравится

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

Во-вторых, такой сброс в некоторых случаях, пожалуй, даже ускорит работу (я побаловался с Cortex-M7 и вводом-выводом по DMA, и у меня получилось, что с ним зачастую выгоднее буферы располагать в некэшируемой области памяти, чем при каждом вводе-выводе вручную в цикле чистить кэш -- а автоматом или одной командой он там не чистится).

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

Конвейер был уже в исходном 8086, на 6 байт следующих команд.

Не было там настоящего конвейера, там был буфер предвыборки (6 байт в 8086, 4 байта в 8088, если правильно помню) -- но это ещё не конвейер как таковой, ведь выполнение разных стадий разных команд во времени не совмещалось -- не считая как раз предвыборки нескольких следующих байтов без каких-либо дополнительных действий с ними. В настоящем же конвейере одна команда выбирается, другая декодируется, третья выполняется, для четвёртой записываются результаты...

Если на то пошло, "буфер предвыборки" был и на ЕС-1022, и на ЕС-1033, но конвейерными они ни разу не были. Просто они, в отличие от ЕС-1030, не "забывали" "лишнее полуслово", прочитанное из памяти при выборке предыдущей команды (если в одном прочитанном слове лишь одно полуслово относилось к текущей команде, а следующее было следующей командой или её началом, при начале выборки следующей команды использовалось именно это полуслово, а не производилась повторная выборка из памяти -- т.е., по большому счёту, то же самое, что в 8086/88).

такое кодирование реально чему-то существенно мешает

Да, мешает. Ещё раз повторяю: попробуйте нарисовать схему декодера даже для 8086 и схему для, скажем, современной z/Architecture со всеми её 64-разрядными расширениями (а не для исходной Системы 360). Разница в сложности будет во много раз. (Кстати, Валерий Черепенников @vvvphoenix в какой-то из своих статей про его будни в Интеле написал, как он быстро слепил на Верилоге декодер для ARM, но для Интела не осилил даже за намного большее время.)

Ну а 1% и всё такое прочее, про что Вы говорили, -- это по сравнению с процессором в целом, со всеми его гигантскими кэшами и т.д. и т.п. Так рассуждать некорректно: дело не в количестве транзисторов как таковом (крупный кэш всё равно больше места займёт), а в скорости работы. Чтобы запускать за такт несколько команд (суперскалярное выполнение), надо их успевать декодировать с соответствующей скоростью -- и для большинства архитектур это много проще, чем для IA-32/AMD64, и в первую очередь как раз из-за префиксов, делающих длину команды совершенно неопределённой. Пока не проверишь все префиксы, ты не можешь приступать к собственно команде, а заодно не узнаешь, где же начинается следующая команда (которую ты тоже должен декодировать в этом же такте, и следующую за ней тоже, если хочешь запускать 3 команды за такт) -- а количество префиксов ограничено лишь максимальной длиной кода команды, т. е. 15 байтами. (Там ещё дополнительные сложности есть; например, некоторые байты, воспринимаемые в 16/32-разрядных режимах как код команды, в 64-разрядном будут восприниматься как префикс, но это уже мелочи.)

Где? Ещё на CDC или уже на Cray?

На CDC. А первое внеочередное выполнение, правда, только для вещественных операций, -- одна из Систем 360, точно не помню, какая (но, кажись, 1969 год).

Кэш на ПК -- да, на некоторых 80386 уже был, но не на всех. На 80486, вроде б, был уже встроенный кэш в несколько килобайт? Но, опять-таки, в мире кэш появился намного раньше. У нас недокэш точно был на БЭСМ-6 -- этим отчасти компенсировалось отсутствие у неё регистров общего назначения как таковых (индексные регистры для адресации массивов плюс единственный аккумулятор для собственно вычислений); в более-менее массовых количествах он стал использоваться в поздних ЕСках и СМках (ЕС-1036, ЕС-1046, СМ-1420 с процом СМ-2420.01, но, вроде, не с исходным СМ-2420, и т.д.) -- это примерно середина 1980-х (сами машины появились раньше, но пока их произвели в достаточно товарных количествах...). У буржуинов, понятно, он появился ещё раньше.

Да, и кстати, главное в Вашем сообщении проигнорировал. EMS -- не аналог того, что было в Z8000, поскольку в последнем было полноценное внешнее MMU -- с защитой памяти, режимами доступа и всё такое. Т. е. связка "микропроцессор Z8000 + MMU из его микропроцессорного комплекта" давала возможность построить компьютер с виртуальной памятью, что у Интела появилось лишь в 80286 (в данном случае неважно, сегментная организация, страничная...).

В "Сериализации ЦП" в замечаниях по программированию есть мелкий примерчик, приведу его изложение здесь, чтоб не заставлять лишний раз доку листать :)

Процессор 1 выполняет команды:

MVI A,X'00'
BCR 15,0

Т.е. он записывает нуль в байтовую переменную A, после чего выполняет принудительную сериализацию (BCR 15,0), что гарантирует, что запись в A будет полностью завершена, и нуль попадёт в соответствующий байт физической памяти.

Второй процессор выполняет команды:

G CLI A,X'00'
  BNE G

Т.е. он сравнивает содержимое A с нулём и при неравенстве повторяет этот цикл.

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

Соответственно, этого примера достаточно, чтобы утверждать: архитектура не требует аппаратной поддержки согласованности содержимого кэшей. Понятно, что для программиста это менее удобно: в многопоточной программе нужно самому выполнять правильные телодвижения. Но это обеспечивает масштабирование системы: не требуется связь процессоров по схеме "все со всеми". Поэтому сделать мэйнфрейм с буквально 1М процессоров принципиально возможно, а ПК -- нет, ПКшные процы и сейчас-то занимаются, главным образом, согласованием своих кэшей.

Что касается грубости механизма, без acquire/release. Так оно и есть, но тут уже дело в древности архитектуры: принципы сериализации ничем не отличаются от таковых в Системе 370 (были ли они чётко прописаны в 360, я не помню, а смотреть лень), появились лишь новые команды, более подходящие для определённых случаев (скажем, когда полная сериализация не нужна, а нужно обеспечить её лишь в пределах небольшого участка памяти). Понятно, что разработчики какого-нибудь там RISC-V в этом плане находятся в выигрышной позиции: могут учесть весь опыт прошедших десятилетий (хорошо ли учли, понятия не имею, но это уже другой вопрос).

У нас была пара плат EMS для XTшек -- вполне себе работало. Ну и на АТшке 286-й, есно, тоже

Хм... Надо будет как-нить на Херкулесе попробовать.

Но переадресация в SVS особо не поможет: это ж, по сути, MVT, только с размещением разделов в виртуальном адресном пространстве, общем на всех, а соответственно, таблицы сегментов и страниц, по идее, общие. Но вообще без понятия, как точно они это реализовали

Глава 5, "Program Execution", разделы "Sequence of Storage Reference" и "Serialization"

Насколько помню, EMS -- это банки памяти, доступные по адресам ниже 1 Мбайта и поэтому пригодные для использования и на компах с 8086/88. Та память, которая выше 1 Мбайта, а значит, 80286+, -- это XMS. Но из-за отсутствия в 80286 режима V86 в ДОСе использовать её было невозможно (не считая первых 64 Кбайт без 16 байт из-за железной ошибки, позволявшей в реальном режиме получать адреса 10'0000-10'FFEF.

Ну, не Unix way, поскольку на Системе 370 (даже если не рассматривать полуэкспериментальную модель 360/67) виртуальная память появилась раньше (MMU к PDP-11 не сразу прикрутили).

вся память на System/360 (и, соответсвенно, ЕС ЭВМ Ряд 1) была доступна для чтения любой программой, ключи защиты блокировали только возможость доступа

Вообще, ключи защиты памяти могут блокировать доступ полностью (запрещая чтение). Если Ваша программа, работая не с нулевым ключом, могла читать чужую память, -- это проблемы ОС, а не железа. Речь, вероятно, о ДОС ЕС (DOS/360) -- как там с защитой, я понятия не имею. В ОС ЕС этот номер не прошёл бы.

А потом уже появилась MVS - фактически, новая система но под тем же именем, скопировать которую в СССР ниасилили

На чём её было гонять? На самой массовой машине Ряда 2 в лице ЕС-1035 с её памятью в 1 или 3 Мбайта и убогой производительностью? Ну а ЕС-1060 и старше, которые были более-менее быстры и имели приличный объём ОП, было крайне мало. Так что не думаю, что "ниасилили" -- не на чем эксплуатировать было.

1) Я рассуждаю о современных процессорах, страдающих из-за идиотского кодирования -- причём во времена перехода на AMD64 это было уже абсолютно очевидно.

2) Суперскалярность появилась в 1960-х годах.

Не полноценную: они работают дополнительными базовыми регистрами. Сегментации как таковой, со всеми её атрибутами типа размеров и прав, в 64-разр режиме убрали.

Насчёт перехода с 8080 на 8086 -- да, транзисторный бюджет был сильно ограничен. Но это не относится к 80286 -> 80386, там другой блок декодирования не слишком бы раздул процессор по сравнению с его уже имеющимся размером. Ну а про AMD64 вообще молчу.

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

Вообще-то, насчёт синхронизации всё, что нужно, сказано.

Никакого "автоматического" поддержания синхронизации кэшей и буферов между процессорами нет вообще, о чём документация вполне ясно и определённо говорит. Это хорошо для железа и его масштабируемости (не нужно делать схемы для поддержания согласованности кэшей), но создаёт определённые проблемы для программиста: нет гарантии, что, если ты записал нечто в память, об этом точно узнает код на другом процессоре.

Барьеров под таким названием таки нет, но фактически они есть -- это операция сериализации. Она всегда выполняется при любом прерывании, а также при выполнении некоторых команд. В частности, полную сериализацию делают CS, CDS и BCR 15,0.

Поскольку полная сериализация -- операция тяжёлая (запись всех изменённых строк кэша в ОП, после чего аннулирование всего кэша, чтобы изменения в памяти, внесённые другими процессорами и подсистемой ввода-вывода, были получены данным процессором), в z/Architecture ввели ряд дополнительных команд, выполняющих частичную сериализацию -- в пределах их операндов (по сути, они записывают изменённую строку кэша, содержащую операнд, в память, после чего аннулируют её).

В общем, не знаю, с чего Вы взяли, что этого нет.

Information

Rating
1,749-th
Location
Солнечногорск, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Embedded Software Engineer
Lead