Лекция 1.1. Введение и мотивация


Закон Мура и «The free lunch is over?»

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

Закон Амдала

N – число потоков, P – доля параллельного кода, S - ускорение кода

$$ S = \frac{Time\ on\ 1\ core}{Time\ on\ N\ cores} = \frac{1}{1 - P + \frac{P}{N}} $$

при \(N \to \infty \) максимальное ускорение кода \( \frac{1}{1 - P} \)

Параллелизм на уровне инструкций (ILP)

Нет зависимости по данным, (1) и (2) можно выполнить параллельно.

e = a + b // 1
f = c + d // 2

Способы использования ILP:

  • Конвейер
  • Суперскалярное исполнение
    • Внеочередное исполнение
    • Переименование регистров
    • Спекулятивное исполнение
    • Предсказание переходов
  • Длинное машинное слово (VLIW)
  • Векторизация (SIMD)

Примеры архитектуры многопроцессорных компьютеров

Симметричная мультипроцессорность (SMP)

smp

Ассиметричный доступ к памяти (NUMA)

numa

Виды многопоточных систем:

  • Одновременная многопоточность (SMT) – в каждый данный момент может исполняться несколько потоков.
  • Временная многопоточность (TMT) – в каждый данный момент может исполняться только один поток.

Операционные системы

Типы операционных систем

  • Однозадачные
  • Система с пакетными заданиями (batch processing)
  • Многозадачные / с разделением времени (time-sharing)
    • Кооперативная многозадачность (cooperative multitasking)
    • Вытесняющая многозадачность (preemptive multitasking)

Основные понятия в современных ОС

  • Процесс – владеет памятью и ресурсами. ОС создаёт иллюзию того, что каждый процесс имеет абстрактную вычислительную систему в своём полном распоряжении.
  • Поток – контекст исполнения внутри процесса. В одном процессе может быть несколько потоков, все потоки работают с общей памятью процесса.
  • В научных работах исторически сложилось называть потоки процессами и использовать большие буквы: P, Q, ...

В теории мы их будем смешивать, хоть это и некорректно

Формализм

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

А ещё формальная модель нужна для формализации отношений между прикладным программистом и разработчиком компилятора и системы исполнения кода.

Модели программирования

  • «Классическое» однопоточное / однозадачное (можем использовать ресурсы многоядерной системы только запустив множества разных, независимых задач)
  • Многозадачное программирование (можем использовать ресурсы многоядерной системы в рамках решения одной задачи)
    • Модель с общей памятью (рассматриваем в рамках этого курса)
    • Модель с передачей сообщений (распределенное программирование)

Общие объекты

Потоки выполняют действия над общими, разделяемыми объектами. В этой модели не важны операции внутри потоков: вычисления, обновления регистров процессора, обновления регистров потока.

Общие переменные

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

Модель с общими переменными – это хорошая абстракция современных многопроцессорных систем и многопоточных ОС. На практике, это общая память процесса, которая доступна для чтения и записи всем потокам, исполняемым в данном процессе.

В теоретических трудах общие переменные часто называют регистрами.

Многопоточные программы в общем случае недетерминированы, в отличие от однопоточных программ. Поэтому мы говорим, что программа А имеет свойство Р, если программа А имеет свойство Р при любом исполнении.

Моделирование работы программы

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

@JCStressTest
@State
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE)
public class SimpleTest1 {
    int x;
    int y;
    
    @Actor 
    public void threadP(IntResult r) {
        x = 1;
        r.r2 = y;
    }
    
    @Actor 
    public void threadQ(IntResult r) {
        y = 1;
        r.r1 = x;
    }
}

Однако на практике у нас всё сломалось, все четыре результата возможны, при этом {1, 1} реже всех. Почему? Потому что тестировали на TSO (Total Store Order) модели памяти, где операции записи кладутся в буфер, и чтение в некоторых случаях было выполнено раньше, чем запись «дошла» до памяти.