Немного истории
На заре вычислительной техники динамическая память вполне себе работала на частоте процессора. Мой первый опыт работы с компьютером был связан с клоном компьютера «ZX Spectrum». Процессор Z80 осуществлял обработку инструкций в среднем по 4 такта на операцию, при этом два такта использовалось на осуществление регенерации динамической памяти, что дает нам при частоте в 3,5 МГц, не более 875 000 операций в секунду.
Однако спустя некоторое время частоты процессоров достигли такого уровня, когда динамическая память уже не справлялась с нагрузкой. Для компенсации этого, было введено промежуточное звено в виде кэш-памяти, что позволило за счет операций выполняемых на небольшом объеме данных сгладить разницу в скорости работы процессора и основной памяти.
Давайте рассмотрим что представляет из себя оперативная память компьютера сейчас, и что с ней можно сделать, чтобы увеличить быстродействие компьютерной системы.
Вкратце о статической и динамической памяти
Память строится в виде таблицы, состоящей из строк и столбцов. В каждой ячейке таблицы находится информационный бит (мы обсуждаем полупроводниковую память, впрочем множество других реализаций строиться по тому же принципу). Каждая такая таблица называется «банком». В микросхеме/модуле может размещаться несколько банков. Совокупность модулей памяти проецируется в линейное адресное пространство процессора в зависимости от разрядности отдельных элементов.
Ячейка статической памяти строится на основе триггера, который обычно находиться в одном из стабильных состояний «А» или «Б»(А =! Б). Минимальное количество транзисторов для одной ячейки составляет 6 штук, при этом сложность трассировки в ячейках видимо не позволяет сделать модули статической памяти в 1 гиг, по цене обычного модуля в 8 гиг.
Ячейка динамической памяти состоит из одного конденсатора отвечающего за хранение информации и одного транзистора отвечающего за изоляцию конденсатора от шины данных. При этом в качестве конденсатора используется не навесной электролит, а паразитная емкость p-n перехода между «подложкой» и электродом транзистора (специально для этих целей увеличенная, обычно от нее стараются избавиться). Недостатком конденсатора является ток утечки (как в нем самом, так и в ключевом транзисторе) от которого очень сложно избавиться, кроме того с увеличением температуры он увеличивается что влечет вероятность искажения хранимой информации. Для поддержки достоверности, в динамической памяти применяется «регенерация», она заключается в периодическом обновлении хранимой информации не реже заданного периода в течении которого информация сохраняет достоверное значение. Типовой период регенерации составляет 8 мс, при этом чаще обновлять информацию можно, реже не рекомендуется.
В остальном принцип функционирования идентичен и заключается в следующем:
— первоначальная выборка строки памяти приводит к доступу ко всему ее содержимому помещаемому в буферную строку с которой идет дальнейшая работа, или происходит мультиплексирование обращения к столбцам (старый, медленный подход);
— запрошенные данные передаются к главному устройству (обычно это ЦПУ), или происходит модификация заданных ячеек при операции записи (тут есть небольшая разница, для статической памяти возможна непосредственная модификация ячейки выбранной строки, для динамической памяти модифицируется буферная строка, и только потом выполняется обратная запись содержимого всей строки в специальном цикле);
— закрытие и смена строки памяти так-же различна для разного типа памяти, для статической возможна мгновенная смена строки если данные не менялись, для динамической памяти необходимо содержимое буферной строки обязательно записать на место, и только потом можно выбрать другую строку.
Если на заре вычислительной техники каждая операция чтения или записи завершалась полным циклом памяти:
— выбор строки;
— операция чтения/записи из ячейки;
— смена/перевыбор строки.
Современный операции работы с микросхемами «синхронной памяти а ля DDRX» заключается в следующем:
— выбор строки;
— операции чтения/записи ячеек строки группами по 4-8 бит/слов (допускается множественное обращение в рамках одной строки);
— закрытие строки с записью информации на место;
— смена/перевыбор строки.
Такое решение позволило сэкономить время доступа к данным когда после чтения значения из ячейки «1», требуется обращение к ячейкам «2, 3, 4, или 7» расположенным в той-же строке, либо сразу после операции чтения, необходимо записать назад измененное значение.
Подробнее о работе динамической памяти в союзе с кэшем
Контроллер памяти (в чипсете или встроенный в процессор) выставляет адрес блока и номер строки (старшую часть адреса блока) в микросхему/модуль памяти. Выбирается соответствующий блок (дальше будет рассматриваться работа в рамках одного блока) и полученный «двоичный номер» декодируется в позиционный адрес строки, после чего происходит передача информации в буфер, из которого в последствии осуществляется доступ к данным. Время в тактах необходимое на данную операцию называется tRCD и отображается в схемах «9-9-9/9-9-9-27» на втором месте.
После того как строка активизирована можно обращаться к «столбцам» для этого контроллер памяти передает адрес ячейки в строке, и спустя время «CL» (указывается в выше обозначенной схеме «х-х-х» на 1 месте) данные начинают передаваться от микросхемы памяти в процессор (почему во множественном числе? потому что здесь вмешивается КЭШ) в виде пакета из 4-8 бит (для отдельно взятой микросхемы) в строку кэша (размер зависит от процессора, типовое значение 64 байта — 8 слов по 64 бита, но встречаются и другие значения). Спустя определенное количество тактов, необходимых для передачи пакета данных можно сформировать следующий запрос на чтение данных из других ячеек выбранной строки, или выдать команду на закрытие строки которая выражается в виде tRP указанное в виде третьего параметра из «х-х-х-...». Во время закрытия строки, данные из буфера записываются обратно в строку блока, после окончания записи можно выбрать другую строку в данном блоке. Кроме этих трех параметров есть минимальное время в течении которого строка должна быть активна «tRAS», и минимальное время полного цикла работы со строкой разделяющего две команды по активизации строки (влияет на случайный доступ).
Нужно обязательно помнить что у оперативной памяти DDR есть две частоты:
— основная тактовая частота определяющая темп передачи команд и тайминги;
— эффективная частота передачи данных (удвоенная тактовая частота, которой и маркируются модули памяти).
Интеграция контроллера памяти увеличило быстродействия подсистемы памяти за счет отказа от промежуточного передающего звена. Увеличение каналов памяти требудет учитывать это со стороны приложения, так например четырех канальный режим при определенном расположении файлов не дает прироста производительности (12 и 14 конфигурации).
Обработка одного элемента связного списка с разным шагом (1 шаг = 16 байт)
Теперь немного математики
Процессор: рабочие частоты процессоров сейчас достигают 5 ГГц. По заявлениям производителей, схемотехнические решения (конвейеры, предсказания и прочие хитрости) позволяют выполнять одну инструкцию за такт. Для округления расчетов возьмем значение тактовой частоты в 4 ГГц что даст нам одну операцию за 0,25 нс.
Оперативная память: возьмем для примера оперативную память нового формата DDR4-2133 с таймингом 15-15-15.
Дано:
процессор
Fтакт = 4 ГГц
Tтакт = 0,25 нс (по совместительству время выполнения одной операции «условно»)
Оперативная память DDR4-2133
Fтакт = 1066 МГц
Fдата = 2133 МГц
tтакт = 0,94 нс
tдата = 0,47 нс
СПДмакс = 2133 МГц * 64 = 17064 Мбайт/с (скорость передачи данных)
tRCmin = 50 нс (минимальное время между двумя активациями строк)
Время получения данных
Из регистров и кэша, данные могут быть предоставлены в течении рабочего такта (регистры, кэш 1 уровня) или с задержкой в несколько тактов процессора для кэша 2-го и 3-го уровня.
Для оперативной памяти ситуация похуже:
— время выбора строки составляет: 15 clk * 0,94 нс = 14 нс
— время до получения данных с команды выбора столбца: 15 clk * 0,94 нс = 14 нс
— время закрытия строки: 15 clk * 0,94 нс = 14 нс (кто бы подумал)
Из чего следует что время между командой запрашивающей данные из ячейки памяти (в случае если в кэш не попали) может варьироваться:
14 нс — данные находятся в уже выбранной строке;
28 нс — данные находятся в невыбранной строке при условии что предыдущая строка уже закрыта (блок в состоянии «idle»);
42-50 нс — данные находятся в другой строке, при этом текущая строка нуждается в закрытии.
Количество операций которые может выполнить (вышеобозначенный) процессор за это время составляет от 56 (14 нс) до 200 (50 нс смена строки). Отдельно стоить отметить что ко времени между командой выбора столбца и получением всего пакета данных добавляется задержка загрузки строки кэша: 8 бит пакета * 0,47 нс = 3,76 нс. Для ситуации когда данные будут доступны «программе» только после загрузки строки кэша (кто знает что и как там накрутили разработчики процессоров, память по спецификации позволяет выдать нужные данные вперед), мы получаем еще до 15-и пропущенных тактов.
В рамках одной работы я проводил исследование скорости работы памяти, полученные результаты показали, что полностью «утилизировать» пропускную способность памяти возможно только в операциях последовательного обращения к памяти, в случае произвольного доступа увеличивается время обработки (на примере связного списка из 32-х битного указателя и трех двойных слов одно из которых обновляется) с 4-10 (последовательный доступ) до 60-120 нс (смена строк) что дает разницу в скорости обработки в 12-15 раз.
Скорость обработки данных
Для выбранного модуля имеем пиковую пропускную способность в 17064 Мбайт/с. Что для частоты в 4 ГГц дает возможность обрабатывать за такт 32-х битные слова (17064 Мб / 4000 МГц = 4,266 байт на такт). Здесь накладываются следующие ограничения:
— без явного планирования загрузки кэша, процессор будет вынужден простаивать (чем выше частота, тем больше ядро просто ждет данные);
— в циклах «чтение модификация запись» скорость обработки снижается в два раза;
— многоядерные процессоры разделят между ядрами пропускную способность шины памяти, а для ситуации когда будут конкурирующие запросы (вырожденный случай), производительность работы памяти может ухудшиться в «200 раз (смена строк) * Х ядер».
Посчитаем:
17064 Мбайт/с / 8 ядер = 2133 Мбайт/с на ядро в оптимальном случае.
17064 Мбайт/с / (8 ядер * 200 пропущенных операций) = 10 Мбайт/с на ядро для вырожденного случая.
В переводе на операции получаем для 8-и ядерного процессора: от 15 до 400 операций на обработку байта данных, или от 60 до 1600 операций/тактов на обработку 32-х битного слова.
На мой взгляд медленно как-то. По сравнению с памятью DDR3-1333 9-9-9, где время полного цикла примерно равно 50 нс, но отличаются время таймингов:
— время доступа к данным уменьшается до 13,5 нс (1,5 нс * 9 тактов);
— время передачи пакета из восьми слов 6 нс (0,75 * 8 вместо 3.75 нс) и при случайном доступе к памяти, разница в скорости передачи данных практически исчезает;
— пиковая скорость составит 10 664 МБайт/с.
Не слишком все далеко ушло. Ситуацию немного спасает наличие в модулях памяти «банков». Каждый «банк» представляет собой отдельную таблицу памяти к которой можно обращаться раздельно, что дает возможность сменить строку в одном банке пока идет чтение/запись данных из строки другого, за счет уменьшения простоя позволяет «забить» шину обмена данными под завязку в оптимизированных ситуациях.
Собственно здесь пошли нелепые идеи
Таблица памяти, содержит в себе заданное количество столбцов, равное 512, 1024, 2048 бит. С учетом времени цикла по активации строк в 50 нс, мы получаем потенциальную скорость обмена данными: «1/0,00000005 с * 512 столбцов * 64 бит слово = 81 920 Мбайт/с» вместо текущих 17 064 Мбайт/с (163 840 и 327 680 МБайт/с для строк из 1024 и 2048 столбцов). Скажете: «всего раз в 5 (4,8) быстрее», на что я отвечу: «это скорость обмена, когда все конкурирующие запросы обращены к одному банку памяти, и доступная пропускная возможность увеличивается пропорционально количеству банков, и увеличением длины строки каждой таблицы (потребует увеличение длины операционной строки), что в свою очередь упирается главным образом в скорость шины обмена данными».
Смена режима обмена данными потребует передачи всего содержимого строки в кэш нижнего уровня, для чего надо разделить уровни кэша не только по скорости работы, но и по размеру кэш строки. Так например реализовав «длину» строки кэша N-го уровня в (512 столбцов * 64 размер слова) 32 768 бит, мы можем за счет уменьшения количества операций сравнения увеличить общее количество строк кэша и соответственно увеличить максимальный его объем. Но если сделать параллельную шину в кэше такого размера, мы можем получить уменьшение частоты функционирования, из чего можно применить другой подход организации кэша, если разбить указанную «Jumbo»-строку кэша на блоки по длине строки верхнего кэша и производить обмен с небольшими порциями, это позволит сохранить частоту функционирования, разделив задержку доступа на этапы: поиск строки кэша, и выборку нужного «слова», в найденной строке.
Что касается непосредственно обмена между кэшем и основной памятью: необходимо передавать данные с темпом обращения к строкам одного банка, или имея определенный запас для распределения запросов к разным банкам. Помимо этого, есть сложность со временем доступа к данным размещенным в разных областях строки, для последовательной передачи помимо первоначальной задержки связанной с выборкой строки, есть задержка передачи данных зависящей от количества данных «в пакете», и скорости передачи. Даже подход «rambus» может не справиться с возросшей нагрузкой. Ситуацию может спасти переход на последовательную шину (возможно дифференциальную), за счет дальнейшего уменьшения разрядности данных, мы можем увеличить пропускную скорость канала, я для уменьшения времени между передачей первого и последнего бита данных, применить разделение передачи строки на несколько каналов. Что позволит использовать меньшую тактовую частоту одного канала.
Оценим скорость такого канала:
1/0,00000005 нс = 20 МГц (частота смены строк в рамках одного блока)
20 Мгц * 32 768 бит = 655 360 Мбит/с
Для дифференциальной передачи с тем-же размером шины данных получаем:
655 360 Мбит/с / 32 канала = 20 480 Мбит/с на канал.
Такая скорость выглядит приемлемо для электрического сигнала (10 Гбит/с для сигнала со встроенной синхронизацией на 15 метров доступен, почему бы и 20 ГБит/с с внешней синхронизацией на 1 метр не осилить), однако необходимое дальнейшее увеличение скорости передачи для уменьшения задержки передачи между первым и последним битом информации, может потребовать увеличения пропускной способности, с возможной интеграцией оптического канала передачи, но это уже вопрос к схемотехникам, у меня маловато опыта работы с такими частотами.
и тут Остапа понесло
Изменение концепции проецирования кэша на основную память к использованию «основной памяти как промежуточного сверхбыстродействующего блочного накопителя» позволит переложить предсказание загрузки данных с схемотехники контроллера на алгоритм обработки (а уж кому лучше знать куда он ломанется через некоторое время, явно не контроллеру памяти), что в свою очередь позволит увеличить объем кэша внешнего уровня, без ущерба производительности.
Если пойти дальше можно дополнительно изменить концепцию ориентирования архитектуры процессора с «переключение контекста исполнительного устройства», на «рабочее окружение программы». Такое изменение может существенно улучшить безопасность кода через определение программы как набора функций с заданными точками входа отдельных процедур, доступным регионом размещения данных для обработки, и возможностью аппаратного контроля возможности вызова той или иной функции из других процессов. Такая смена позволит также эффективнее использовать многоядерные процессоры за счет избавления от переключения контекста для части потоков, а для обработки событий использовать отдельный поток в рамках доступного окружения «процесса», что позволит эффективнее использовать 100+ ядерные системы.
P.S.: случайное использование зарегистрированных товарных знаков или патентов является случайным. Все оригинальные идеи доступны для использования по лицензионному соглашению «муравейник».
Автор: kasperos