Современный процессор устроен так, что внутри него есть несколько физических ядер. Ядро (Core) — это реальный вычислительный блок с регистрами, кэшами, арифметико-логическим устройством (ALU, Arithmetic Logic Unit). Одно ядро может исполнять инструкции независимо от других, и именно количество ядер определяет, сколько задач реально можно выполнять параллельно. Если в системе восемь ядер, значит восемь независимых потоков команд могут выполняться одновременно без взаимного ожидания.
Кроме физических ядер существуют потоки. Поток (Thread, ещё называют Logical Processor или Hardware Thread) — это логический исполнитель внутри одного ядра. Когда включена технология SMT (Simultaneous Multithreading), у Intel известная как HyperThreading (HT), одно ядро «раздваивается» и показывает себя операционной системе как два логических CPU. ОС думает, что процессоров больше, чем есть на самом деле. Смысл в том, что ядро может загружаться эффективнее: если один поток простаивает и ждёт данные из памяти, второй использует свободные вычислительные блоки. Но это не удваивает мощность: если оба потока выполняют тяжёлые вычисления, они делят одни и те же ресурсы и прироста почти нет.
Работа с множеством процессов сразу создаёт проблемы с контекстом. Контекстное переключение (Context Switch) — это момент, когда операционная система приостанавливает выполнение одного процесса и передаёт управление другому. Для этого нужно сохранить всё текущее состояние первого процесса: значения регистров, счётчик инструкций, указатели стека. Затем ОС загружает сохранённое состояние второго процесса и запускает его на том же ядре.
На каждое переключение уходит время, потому что процессору приходится не только переписать регистры, но и фактически «выбросить» часть данных из кэша. Новый процесс работает с другими данными, и строки кэша, которые были у предыдущего, становятся бесполезными. Когда ОС возвращается к первому процессу, его данные снова нужно загрузить из памяти. При большом числе переключений ядро значительную часть времени тратит не на выполнение инструкций, а на обслуживание этих переходов и на прогрев кэша заново.
В многопроцессорных системах добавляется проблема когерентности (Cache Coherency). У каждого ядра есть свои кэши L1 и L2, и одно и то же значение может храниться в нескольких копиях. Когда одно ядро изменяет переменную, остальные должны получить обновлённое значение. Для этого процессор использует протоколы когерентности, самый распространённый — MESI (Modified, Exclusive, Shared, Invalid), который рассылкой сигналов заставляет кэши синхронизироваться. При большом числе потоков и частых изменениях данных это превращается в нагрузку на шину и снижает масштабируемость приложений.
Синхронизация (Synchronization) между потоками становится ещё одним ограничением. Если несколько потоков обращаются к одному ресурсу, например к очереди задач или строке в таблице базы данных, они должны использовать блокировки (Locks или Mutexes). Пока один поток держит замок, остальные ждут. При высокой конкуренции это превращает параллельность в последовательное выполнение: ядра простаивают, нагрузка растёт, а сервис работает медленнее.
В результате видно, что физические ядра (Cores) дают реальный параллелизм, а потоки (Threads) через SMT позволяют лучше использовать простаивающие ресурсы, но не делают ядро в два раза мощнее. Контекстные переключения (Context Switches) тратят время и обнуляют кэш, когерентность (Cache Coherency) нагружает систему при совместном доступе, а синхронизация (Synchronization) может полностью убрать преимущество от многопоточности. Поэтому высокая загрузка CPU в графиках не всегда означает реальную скорость: часто это показатель того, что процессор занят ожиданием памяти (Memory Latency) и согласованием работы потоков.
Человек работает за компьютером, открывает одну вкладку за другой, пока их не становится сотня. Каждая вкладка — это отдельная задача для компьютера. Процессор имеет восемь ядер, с HyperThreading их видно как шестнадцать, но задач всё равно в несколько раз больше. Операционная система начинает тасовать вкладки по кругу: чуть-чуть работает одна, потом её откладывают, берут другую, потом третью, потом снова возвращаются к первой.
Каждое такое переключение стирает быстрые подсказки в кэше процессора. Когда вкладка получает время снова, нужных данных рядом уже нет, и приходится тянуть их заново из медленной памяти. Это тормозит процесс.
Проблема не только в переключениях. Все вкладки используют одни и те же ресурсы: интернет-соединение, память, доступ к видеокарте. Если одна вкладка их заняла, остальные ждут. Если несколько одновременно меняют одно и то же значение, процессор заставляет ядра синхронизировать свои копии. На это тоже уходит время.
Снаружи видно только одно: процессор загружен на сто процентов, ноутбук шумит, вкладки открываются медленно и всё начинает подтормаживать.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.