# Архитектура компьютера ## Лекция 14 ### Уровневая организация.
Языки высокого уровня.
Низкоуровневый параллелизм Пенской А.В., 2025 --- ## Уровневая организация это естественный способ организации вычислительных систем
Примеры: 1. Lava Flow или уровневая организация курильщика $\longrightarrow$ 1. Уровневый архитектурный стиль (Layered Style) 1. OSI Model 1. Уровни организации вычислительного процесса Количество уровней — десятки.

---- ### Уровневый архитектурный стиль
 - Изоляция, модифицируемость и переносимость - Управление сложностью и структура кода разработчикам - Разделения интересов
(**Layered Style**)   
---- ### Open Systems Interconnection (OSI) Model
Зачем уровни? Обеспечить **модульность** и **вариативность**. 1. Application Layer 1. Presentation Layer 1. Session Layer 1. Transport Layer 1. Network Layer 1. Data Link Layer 1. Physical Layer Что неверно в визуализации?

---- ### Уровни организации выч. процесса
 1. Layered project organization. 1. Frameworks, библиотеки, API. 1. Языки программирования. 1. Операционные системы. 1. Системы команд (ISA), ПЛИС. 1. Виртуализированные ресурсы.

---- ### Разделение на уровни (Disaggregation) 
- Интегрированные решения
(in-house). Уровни переплетены и адаптированы.
- Уровни разделены по индустриям/специальностям. - Эффект масштаба. - Независимое существование. Закон Мура. [Proebsting's Law](https://proebsting.cs.arizona.edu/law.html)
---- ### Смешение уровней. Интеграция  1. Зачем: для обеспечения системных характеристик: энергопотребление, реальное время, производительность. 1. Смешение уровней $\longrightarrow$ рост связанности и сложности системы. 1. Смешение уровней при разработке. 1. Сквозные инструментальные решения. ---- ### Документирование. Граф актуализации
1. Цепочка преобразований моделей выч. процесса через: - стадии жизненного цикла
(Design $\rightarrow$ Dev $\rightarrow$ Use), - вычислительные платформы (High- $\rightarrow$ Low-level Langs $\rightarrow$ ISA $\rightarrow$ HW $\rightarrow$ Physic). 1. Ориентированный ациклический граф, вершины — модели, рёбра — трансляции/интерпретации. 1. Назначение: анализ инструментальных цепочек.

Подробности: [Разработка и исследование архитектурных стилей проектирования уровневой организации встроенных систем](http://fppo.ifmo.ru/?page1=16&page2=52&page_d=1&page_d2=142415) ---
## Вокруг процессора фон Неймана 1. Параллелизм уровня инструкций,
(Low-Level Parallelism): - Суперскалярность - VLIW 1. Языки программирования высокого уровня. Структурное программирование.

--- ## Низкоуровневый параллелизм
(Low-Level Parallelism) ("не влияет" на прикладное программирование) 1. **Конвейеризация**. Разбиение выполнения инструкции на последовательность этапов. 1. **Множественные функциональные узлы в ЦПУ**. Независимые функциональные блоки для арифметических и булевых операций, выполняемых одновременно. - Суперскалярный процессор - Very Long Instruction Word --- ### Суперскалярный процессор
Проблемы: - разные операции выполняются в разное время (сумма, деление); - простаивание конвейера во время длинных операций. Решение: - анализ потока инструкций на лету и автоматическая параллелизация инструкций, прозрачная для программиста.

Скалярная величина
величина, которая может быть представлена числом (целочисленным или с плавающей точкой).
---- #### Суперскалярный процессор. Структура
1. Инструкции читаются в очередь команд по порядку. 1. Декодируются и перемещаются на станции резервирования. 1. Станции резервирования выполняют переупорядочивание: - по мере доступности данных; - по мере доступности вычислительных ресурсов.

Notes: 18-600 Foundations of Computer Systems, Carnegie Mellon University, J.P. Shen ---- #### Суперскалярный процессор. Достоинства 1. Рост производительности. Сглаживание длительности выполнения инструкций. 1. Повышение уровня загрузки ресурсов. 1. Совместимость с существующим машинным кодом. 1. Компилятор может устранить значительное количество конфликтов за счёт сортировки инструкций. #### Суперскалярный процессор. Недостатки 1. Конфликты по данным оказывают значительное влияние на производительность и сложность процессора. 1. Высокое энергопотребление. 1. Проблемы детерминированности работы многоядерных процессоров. ---- #### Барьеры памяти (Fence)
Барьер памяти
вид барьерной инструкции, которая приказывает компилятору (при генерации инструкций) и процессору (при исполнении инструкций) устанавливать строгую последовательность между обращениями к памяти до и после барьера.
Все обращения к памяти перед барьером будут гарантированно выполнены до первого обращения к памяти после барьера.
```с // Processor 1: while (f == 0); // Memory fence required here print(x); // Processor 2: x = 42; // Memory fence required here f = 1; ``` 
--- ### Very Long Instruction Word (VLIW)
**RISC**: упростим процессор за счёт языков высокого уровня! **VLIW**: упростим процессор за счёт переноса параллелизма инструкций в compile-time! 1. Много АЛУ. 1. Объединим несколько инструкций в одну. 1. Уберём из механизмы суперскаляра. 1. Компилятор знает всё $\longrightarrow$ максимальная оптимизация и параллелизм.

---- #### VLIW. Система команд и устройство  ----
#### VLIW. Достоинства 1. Упрощение процессора, снижение энергопотребления. 1. Упрощение декодера. Рост частоты. 1. Компилятор имеет больше информации о коде, он лучше знает, что параллельно!
#### VLIW. Недостатки 1. Сложность компилятора. 1. Высокая нагрузка на каналы данных и регистровые файлы. 1. Конфликты конвейера приводят к простою всех узлов. 1. Проблемы условных переходов. 1. Низкая плотность кода. 1. Ширина команды — огр. микроархитектуры.
 Notes:
--- ### VLIW vs. Superscalar (структурированный поток vs. просто поток инструкций)  ---- ### Уровневая организация.
Суперскаляр $\longleftrightarrow$ VLIW  Notes: Understanding EPIC Architectures and Implementations, Mark Smotherman,
--- ## Языки высокого уровня Инструкции и Go To $\rightarrow$ Структурные блоки и их последовательность
1. Естественные элементы для архитектуры фон Неймана: - последовательный код$^*$, - условный оператор$^*$, - циклы, подпрограммы. 1. Распределение регистров. Математические выражения. 1. Функции, области видимости и автоматическая память. 1. Исключения, состояния и перезапуски.$^{**}$ 1. Полиморфизм. Замыкания.$^{**}$ - $^*$ — переосмыслены. - $^{**}$ — опустим.
  Подробности: [Курс ФП. История](https://gitlab.se.ifmo.ru/functional-programming/main)
---- ### Распределение регистров. Выражения   1. Проблемы: - кол-во регистров конечно (ARM 15/31, x86 8/16, MIPS 32/32), - не все регистры одинаковы (особенно в CISC). 1. Проблема компилятора или программиста низкого уровня. 1. Код не ограничивается выражениями. 1. Согласование использования регистров между вызовами процедур. ---- #### Раскраска регистров 
$R_1, R_2$ — регистры аргументов. $R_{LK}$ — возврат, $R_1$ — результат.
Источник: [Register allocation](https://cs420.epfl.ch/archive/20/c/08_reg-alloc.html) В статье пример с Lisp.
--- ### Подпрограммы. Процедуры
1. Зачем: - переиспользование машинного кода, - оптимизация работы кеша инструкций. 1. Инструкции: - Через `inline`. - Через `goto`. - Через `call`, `return`. 1. Данные: рабочая память выделена статически. 1. Нет реентерабельности. 1. Проблемы: кеширование, сброс регистров, переходы и конвейер.
```c int *x, *y; void *SWAP_RET; int tmp; swap: tmp = *x; *x = *y; *y = tmp; goto SWAP_RET; ``` ```c int tmp; void swap(int *x, int *y) { tmp = *x; *x = *y; // прерывание: isr() *y = tmp; } int a, b; void isr() { a = 1; b = 2; swap(&a, &b); } ```
---- #### Реентерабельность. Рекурсивный вызов
1. Реализация через `call`, `return`. - Статическое выделение памяти для каждого входа. - Автоматическая память, стек: `push`, `pop` (рекурсия). 1. Зачем: - параллелизм уровня задач, - рекурсивные алгоритмы. 1. Проблемы: автоматическая память, утечки данных, перезапись адреса возврата. ```c int fact(int n) { if (n == 0) return 1; return n * fact(n - 1); } ```
```c struct swap {int* x; int* y; int tmp;}; void swap(struct swap* base) { base->tmp = *(base->x); *(base->x) = *(base->y); *(base->y) = base->tmp; } struct swap enter1, enter2; swap(&enter1); swap(&enter2); void swap(int* x, int* y) { int tmp; // reserve mem tmp = *x; *x = *y; *y = tmp; } ``` 
--- ### Переосмысление условного оператора - (1) Традиционный подход: встроенная конструкция языка, реализуемая инструментарием (компилятор, интерпретатор). - (2) Полиморфизм + замыкания (анонимные функции). Smalltalk.
```smalltalk Bool subclass: #Object ! True subclass: #Bool ifTrue: aBlock ifFalse: bBlock ^ aBlock value ! ! False subclass: #Bool ifTrue: aBlock ifFalse: bBlock ^ bBlock value ! ! ```
```smalltalk (17 * 13 > 220). >>>true (17 * 13 > 220) ifTrue: [ 'bigger' ] ifFalse: [ 'smaller' ]. >>>'bigger' n := 1. [ n < 1000 ] whileTrue: [ n := n*2 ]. n >>> 1024 ```
---- - (3) Нормальный порядок вычислений. Ленивые вычисления. Охватывающее выражение полностью редуцируется, применяя функции до вычисления аргументов (с кешированием).
```python def my_if(cond, if_true, if_false): if cond: if_true else: if_false my_if(True, print("foo"), print("bar")) ``` ```python if True: print("foo") else: print("bar") ``` ```python print("foo") ```
Нормальный порядок (~макросы) ``` go #define square(X) ((X) * (X)) x = square(1 + 2); -> x = ((1 + 2) * (1 + 2)); ``` Ленивые вычисления: ```haskell let square x = x * x in square (1 + 2) -- let square x = x * x tmp = 1 + 2 in square tmp -- let square x = x * x tmp = 1 + 2 in tmp * tmp ```
--- ### Переосмысление последовательного кода - (1) Optional Chaining (Swift). Глубокий JSON с опциональными полями. ```swift let string: String? = "hello" // or nil let count = string?.count if count != nil { print(count!) } ``` ---- - (2) `async/await` То, что выглядит последовательным кодом, им не является (кооперативная многозадачность). ```python import asyncio async def main(): print('hello') await asyncio.sleep(1) # будет возвращено замыкание, с оставшимся # кодом, которое будет выполнено после # завершения sleep. print('world') asyncio.run(main()) # asyncio.run обеспечивает ожидание асинхронных # функций и запуск соответствующих замыканий. # (кооперативная многозадачность) ``` ---- - (3) Монады. Явно определяем операцию связывания (`bind`, `>>=`): - результата прошлого шага - и действия следующего шага. (без погружения в детали, очень синтетический пример, без синтаксического сахара и реального применения) ```haskell class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b instance Monad Maybe where return :: a -> Maybe a return x = Just x (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b Nothing >>= _ = Nothing Just x >>= f = f x string = Just "hello" count = string >>= (\s -> return (length s)) ``` Async/Await — см. библиотеку lwt для OCaml. Детали: см. курс [Функционального программирования](https://gitlab.se.ifmo.ru/functional-programming/main).