Concurrency, Synchronization and Consistency. Пост №8. Выводы по аппаратной части
Пятница, конец недели, перед тем как идти дальше решил все таки собрать воедино ключевые мысли из прошлых постов. Попытаюсь осмыслить как я сам понимаю ситуацию с развитием CPU и почему мы находимся там где находимся.
0. Производительность компьютера в 20м веке и начале 21го росла за счет бурного развития процессоров.
1. С ростом производительности CPU явно проявилось бутылочное горлышко архитектуры компьютера - CPU работает быстрее чем все остальные компоненты и простаивает.
2. Для того чтобы преодолеть узкое место были разработаны оптимизации, перечислю основные:
- Внедрение кешей рядом с процессором, чтобы минимизировать количество взаимодействий с памятью.
- Внедрение оптимизаций связанных переупорядочиванием операций. Это также нужно для уменьшения взаимодействий с памятью. Код не всегда исполняется процессором в том порядке в котором написан. Но процессор гарантирует что результат получится тот же. Как минимум для однопоточной программы.
3. Индустрия требует дальнейшего роста производительности, но мощность одного процессора вышла на плато. Free lunch is over. Инженеры придумали концепт многоядерного. СPU. Но языки программирования на тот момент просто не умели и не знали как утилизровать такое железо, и поэтому изначальный эффект многоядерный CPU давал для многопроцессных операционных систем. Поэтому разработчики программ начали создавать программы на основе дочерних процессов. Процессы как сущность операционной системы имеют большой оверхэд по ресурсам и контролируются операционной системой. Нужен примитив с меньшим оверхэдом и большим контролем - поток (thread). Самое главное что нужно знать - у потоков общее адресное пространство, то есть все данные программы - общая память.
4. Появление multicore CPU меняет правила игры. Во времена однопоточных программ все оптимизации на уровне CPU были к месту и программист читая код всегда мог понять что произойдет. На многоядерном процессоре мест где все может пойти не так становится больше:
- Без протокола когерентности кеши ядер CPU могут быть попросту несогласованы и хранить мусор.
- Нет строгих правил очередности применения операций над общей памятью и очередности применения изменений к кешам процессора (помним что у нас может произойти переупорядочивание операций с целью оптимизаций, и если для одного потока CPU гарантирует корректность то для многопоточной программы гарантии неочевидны). Тут в дело вступает понятие модели памяти описывающее правила игры. Инженеры вспоминают что в 1979 году был написан пейпер How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs.
5. Просто изменить архитектуру CPU и сделать его правильным и согласованным наверное можно, но не нужно - потому что он сразу станет очень медленным (представьте программу в которой все переменные типа atomic и к каждой приставлен mutex). И у разработчика софта просто не будет шансов где-то что-то осознанно оптимизировать. Поэтому были разработаны специальные инструкции процессора и примитивы позволяющие разработчику осознанно (в нужное время и в нужном месте) внедрять синхронизацию для обеспечения корректного и предсказуемого поведения программы (механизмы блокировки, барьеры памяти).
6. Что в итоге - мы как разработчики получили в свои руки более мощный ресурс чем раньше, но на наши плечи легла вся тяжесть написания корректного многопоточного кода. Многопоточное программирование содержит в себе целый класс подводных камней из за которых программы могут вести себя непредсказуемо (гонки, голодание итп). Это целая дисциплина без которой уже нельзя представить программирование.
*Мы здесь*😊
В целом это всё, что хотел сказать. Пишите в комментариях, с чем несогласны, возможно упустил что-то важное. На следующей неделе начнем выныривать из глубин на прикладной уровень, с примерами кода на всяком разном😊
Post #400
1.35K

- 🔥 13
- 👍 7
- ❤ 5
- 🥱 2