Перейти к содержанию
Educora
Университет25 мин53 / 59

Архитектура компьютера

Изучи архитектуру фон Неймана, цикл команд и регистры процессора, иерархию кэш-памяти, двоичную арифметику в дополнительном коде и основы чисел с плавающей запятой IEEE 754.

Проверь себя
В этом уроке ты узнаешь
  • Объяснять устройство компьютера фон Неймана и цикл «выборка–декодирование–исполнение»
  • Вычислять процессорное время и среднее время доступа к памяти
  • Записывать целые числа в дополнительном коде и распознавать переполнение
  • Кодировать число в формате IEEE 754 и объяснять ошибки округления

Процессор твоего телефона с частотой 3 ГГц выполняет 3 миллиарда тактов в секунду, но если написать в Python 0.1 + 0.2 == 0.3, ответ будет False. Оба факта вытекают из устройства компьютера: процессор выбирает и выполняет команды одну за другой, а числа хранит в конечном числе битов. В этом уроке мы изучим те части «железа», которые сильнее всего влияют на программиста.

Архитектура фон Неймана и цикл команд

Определение
Архитектура фон Неймана

Программа и данные хранятся в одной и той же памяти; процессор (арифметико-логическое устройство, устройство управления и регистры) последовательно выбирает команды из памяти и выполняет их; всё соединено шиной. Чтобы сменить программу, достаточно изменить содержимое памяти, а не провода.

РегистрНазначение
PC — счётчик командадрес следующей команды
IR — регистр командывыполняемая сейчас команда
MAR / MDRадрес памяти и читаемые/записываемые данные
ACC и регистры общего назначенияпромежуточные результаты вычислений
Флагипризнаки нуля, знака, переноса, переполнения
  1. 1
    Выборка (fetch)

    Адрес из PC попадает в MAR, команда читается из памяти в IR, PC переходит к следующей команде.

  2. 2
    Декодирование (decode)

    Устройство управления определяет код операции и операнды.

  3. 3
    Исполнение (execute)

    АЛУ вычисляет или данные читаются/пишутся в память; команда перехода меняет PC. Затем цикл повторяется.

Python
memory = [('LOAD', 5), ('ADD', 6), ('STORE', 7), ('PRINT', 7), ('HALT', 0), 20, 22, 0]
pc, acc, executed = 0, 0, 0
while True:
    op, addr = memory[pc]
    pc += 1
    executed += 1
    if op == 'LOAD':
        acc = memory[addr]
    elif op == 'ADD':
        acc += memory[addr]
    elif op == 'STORE':
        memory[addr] = acc
    elif op == 'PRINT':
        print('memory[7] =', memory[addr])
    elif op == 'HALT':
        break
print('instructions executed:', executed, '| PC =', pc, '| ACC =', acc)
▸ Ожидаемый результат
memory[7] = 42
instructions executed: 5 | PC = 5 | ACC = 42
Игрушечный процессор с аккумулятором: в ячейках 0–4 команды, в ячейках 5–7 данные — в одном массиве, по принципу фон Неймана. Цикл: выборка (memory[pc]), увеличение PC, декодирование (if op == …), исполнение.
t = N · CPI / ft = N · CPI / f
где:
  • tпроцессорное время программы, с
  • Nчисло выполненных команд
  • CPIсреднее число тактов на команду
  • fтактовая частота, Гц

«Основное уравнение» производительности процессора: чтобы ускориться, нужно меньше команд (лучший алгоритм и компилятор), меньший CPI (конвейер, кэш) или более высокая частота.

Пример 1: процессорное время

Программа выполняет 2 · 10⁹ команд, CPI = 1,5, частота 3 ГГц. Каково процессорное время? Что будет, если компилятор сократит число команд на 20 %, но CPI вырастет до 1,6?

Показать решение
t = 2 · 10⁹ · 1,5 / (3 · 10⁹) = 3 · 10⁹ / 3 · 10⁹ = 1 с.
Новый вариант: N = 1,6 · 10⁹, t = 1,6 · 10⁹ · 1,6 / (3 · 10⁹) = 2,56 / 3 ≈ 0,853 с.
Хотя каждая команда требует больше тактов, общее время уменьшилось на ≈ 15 % — нельзя судить по одному показателю.

Иерархия памяти и кэш

Процессор примерно в сто раз быстрее оперативной памяти (RAM) — это «бутылочное горлышко» фон Неймана. Решение — иерархия памяти: маленькие и быстрые уровни кэша рядом с процессором и большие, медленные виды памяти дальше. Кэш опирается на локальность: недавно использованные данные скоро понадобятся снова (временная локальность), как и соседние адреса (пространственная локальность). Поэтому память подгружается строками кэша по 64 байта.

УровеньТипичный объёмПримерная задержка
Регистрынесколько сотен байт≈ 1 такт
Кэш L132–64 КБ на ядро≈ 1 ns
Кэш L20,25–2 МБ на ядро≈ 3–5 ns
Кэш L3общий, десятки МБ≈ 10–20 ns
Оперативная память (RAM)гигабайты≈ 60–100 ns
SSDсотни ГБ – ТБ≈ 20–100 мкс
Порядки величин для современного настольного процессора (точные значения зависят от модели). Каждая ступень вниз примерно в 3–10 раз медленнее, но больше.
AMAT = thit + m · tmiss
где:
  • AMATсреднее время доступа к памяти
  • thitвремя доступа при попадании в кэш
  • mдоля промахов
  • tmissштраф за промах (обращение к следующему уровню)
Пример 2: двухуровневый кэш

L1: 1 нс, доля промахов 5 %. L2: 4 нс, промахивается в 20 % дошедших до него запросов. RAM: 100 нс. Найди AMAT. А если бы L2 не было?

Показать решение
Штраф за промах L1 — это собственное AMAT уровня L2: 4 + 0,2 · 100 = 24 нс.
AMAT = 1 + 0,05 · 24 = 1 + 1,2 = 2,2 нс.
Без L2: AMAT = 1 + 0,05 · 100 = 6 нс — примерно в 2,7 раза медленнее.
Вывод: «дружественный к кэшу» код (последовательный обход массива) гораздо быстрее разбросанных обращений (связный список).

Целые числа: дополнительный код

В n-битном дополнительном коде старший бит имеет отрицательный вес: −2ⁿ⁻¹. Поэтому отрицательные числа начинаются с 1, а отдельный «вычитатель» не нужен: АЛУ вычисляет a − b как a + (−b). Само сложение строится из логических элементов: в полусумматоре бит суммы — a XOR b, а перенос — a AND b.

−x = (NOT x) + 1; n bit: −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1
где:
  • NOT xинверсия всех битов x
  • nчисло битов (8 бит: −128 … 127)
Пример 3: −45 и переполнение

а) Запиши −45 в 8-битном дополнительном коде. б) Чему будет равно 100 + 50 в 8-битной знаковой арифметике?

Показать решение
а) 45 = 00101101 → инверсия: 11010010 → +1: 11010011.
Проверка: −128 + 64 + 16 + 2 + 1 = −45 ✓ (как беззнаковое это 211 = 256 − 45).
б) 100 = 01100100, 50 = 00110010, сумма = 10010110.
Старший бит стал 1 → −128 + 16 + 4 + 2 = −106. Сумма двух положительных чисел оказалась отрицательной — это переполнение: 150 > 127. Результат «заворачивается»: 150 − 256 = −106.
Интерактив
Загрузка симуляции…
Набери 11010011: без знака это 211, а в дополнительном коде 211 − 256 = −45. Затем проверь 10000000 (−128) и 01111111 (127) — границы 8 бит.
Интерактив
Загрузка симуляции…
Бит суммы полусумматора: XOR даёт 1 только при разных входах (1 + 1 = 10₂ — сумма 0, перенос 1).

Числа с плавающей запятой: IEEE 754

x = (−1)ˢ · 1,m₂ · 2^(e − 127)
где:
  • sбит знака (1 бит)
  • eсмещённый порядок (8 бит, смещение 127)
  • mдробная часть мантиссы (23 бита; ведущая 1 не хранится)

32-битный формат (single). 64-битный double: 1 + 11 + 52 бита, смещение 1023, точность около 15–16 десятичных цифр. float в Python — это double.

Пример 4: кодируем −6,25

Запиши −6,25 в формате IEEE 754 single (биты и шестнадцатеричный код).

Показать решение
Знак: отрицательное → s = 1.
6,25 = 6 + 0,25 = 110₂ + 0,01₂ = 110,01₂ = 1,1001₂ · 2².
Порядок: e = 2 + 127 = 129 = 10000001₂.
Мантисса (без ведущей 1): 1001 и 19 нулей.
Биты: 1 | 10000001 | 10010000000000000000000
Группы по 4: 1100 0000 1100 1000 0000 … → C0C80000₁₆.
Python
import struct, math

def to_bits(x, bits=8):
    return format(x % 2 ** bits, '0' + str(bits) + 'b')

def from_bits(s):
    v = int(s, 2)
    return v - 2 ** len(s) if s[0] == '1' else v

print(to_bits(45), to_bits(-45), from_bits('11010011'))
print('100 + 50 in 8 bits:', from_bits(to_bits(100 + 50)))

raw = struct.pack('>f', -6.25).hex()
print('-6.25 as float32:', raw, format(int(raw, 16), '032b'))
print(0.1 + 0.2, 0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))
print(struct.unpack('>f', struct.pack('>f', 0.1))[0])
▸ Ожидаемый результат
00101101 11010011 -45
100 + 50 in 8 bits: -106
-6.25 as float32: c0c80000 11000000110010000000000000000000
0.30000000000000004 False True
0.10000000149011612
x % 2 ** bits «наматывает» число на n-битный круг — это и есть дополнительный код. struct показывает настоящие 32 бита: тот же C0C80000, что мы получили вручную. 0,1 — бесконечная двоичная дробь (0,000110011…₂), поэтому во float32 это 0,10000000149…, а в double 0,1 + 0,2 = 0,30000000000000004.

Главное

  • В компьютере фон Неймана программа и данные в одной памяти; процессор повторяет цикл выборка–декодирование–исполнение.
  • Процессорное время t = N · CPI / f; производительность зависит от всех трёх множителей.
  • Кэш опирается на локальность; AMAT = thit + m · tmiss.
  • Дополнительный код: −x = NOT x + 1, n бит покрывают −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1; выход за диапазон — переполнение.
  • IEEE 754: знак, смещённый порядок, мантисса; числа вроде 0,1 неточны, поэтому не сравнивай float через ==.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Как записывается −20 в 8-битном дополнительном коде?