Не за горами выход новой версии ядра Linux 5.14. За последние несколько лет это обновление ядра является самым многообещающим и одно из самых крупных. Была улучшена производительность, исправлены ошибки, добавлен новый функционал. Одной из новых функций ядра стал Core Scheduling, которому посвящена наша статья. Это нововведение горячо обсуждали в интернете последние несколько лет, и наконец-то оно было принято в ядро Linux 5.14.

Если вы работаете с Linux или занимаетесь информационной безопасностью, вам интересны новые технологии, то добро пожаловать под кат.

▍Введение


Для того чтобы понять Core Scheduling и для чего он нужен, стоит разобраться, как работают многозадачные системы, и как они развивались.

На современных компьютерах одновременно могут работать сотни, а то и тысячи программ одновременно. Монопольный доступ к процессору и памяти остались в прошлом, и на данный момент практически нигде не используется кроме микроконтроллеров и RT(Real Time) задач.

Изначально все процессоры были одноядерными, и могли выполнять только одну задачу одновременно. Практически сразу появилась необходимость выполнять несколько задач одновременно на одноядерном процессоре. Для этого был придуман scheduler, он же планировщик в операционных системах, который занимается переключением задач и управляет ресурсами компьютера. Ядро операционной системы всегда работает с большими привилегиями, чем пользовательские программы, такой режим работы называют Ring 0. Поэтому планировщик может в любой момент приостанавливать выполнение одной задачи и перейти к выполнению следующей. В нём существует специальная очередь задач, в которую добавляются все работающие программы и обработчики ядра, например, обработчик прерываний. Изначально эта очередь была простая, как очередь в магазине, и каждая программа выполнялась по очереди, как бы подходя к кассе в магазине. Каждая задача, после того, как выполнила свою работу, должна была явно уведомить планировщик, что задача завершила свою работу и планировщик может переключиться на следующую задачу. Такую очередь называют FIFO — First In First Out первым пришёл, первым ушёл. А такую многозадачность называют совместной или кооперативной многозадачностью.

Дальше очередь стала более сложной и появились приоритеты с вытеснением, задачи с более высоким приоритетом могут встать в очереди перед задачами с более низким приоритетом, или некоторые задачи вовсе могут быть пропущены в этом цикле обхода очереди. Простой пример такой очереди, когда в очереди к директору компании стоят сотрудники, но приходит какой-нибудь проверяющий, или какое-то важное лицо, и всех просят подождать, пока директор пообщается с этим более важным человеком. А могут и вовсе кого-то выгнать из кабинета начальника: -«Семён Семёнович, незамедлительно покиньте кабинет директора, к нему министр тяжёлой промышленности приехал!». Решение о переключении задач при такой многозадачности принимает OC. Через запрограммированные промежутки времени по таймеру происходит прерывание, и запускается планировщик, который переключает процессор на выполнение следующей задачи. Такую многозадачность называют вытесняющая многозадачность или PREEMPT, preemptive multitasking.

Каждая задача в ядре Linux описывается структурой task_struct, а список задач хранится в виде циклического двусвязного списка (Связный список). Описание структуры task_struct можно найти в файле: include/linux/sched.h исходников ядра. task_struct ещё называют дескриптором процесса и в нём находится вся важная информация об исполняемом процессе. Мы не будем углубляться в эту тему слишком глубоко, так как это тема отдельной статьи. Но вы всегда можете найти комментарии и пояснения к коду, в файле: include/linux/sched.h, и изучить эту тему самостоятельно. Умение читать исходные коды ядра, является очень важным навыком для каждого разработчика, который хочет углубиться в программирование ядра Linux. Часть полезной информации также можно найти в официальной документации ядра Linux — Scheduler, но всё же лучше смотреть исходные коды ядра.

Со временем системы стали многоядерными и имели количество потоков выполнения — равное количеству ядер процессора или количеству процессоров, в случае с одноядерными процессорами, что, в свою очередь, повысило возможности компьютеров и количество выполняемых задач одновременно. Чтобы уменьшить накладные расходы переключения задач в ОС, разработчики процессоров придумали простое и элегантное решение, переложили часть функций на процессор. Так были придуманы Intel Hyper-Threading (Intel HT) и AMD Simultaneous Multithreading (AMD SMT).

Технологии очень простые: Теперь вместо реальных ядер процессор начал показывать виртуальные ядра в количестве реальных ядер умноженных на 2. Процессоры с 1 ядром начали иметь 2 виртуальных процессора (2 потока выполнения), которые видит ОС. Теперь в процессоре, имея два потока выполнения на каждое ядро, ядро процессора может не простаивать, когда один из потоков спит, а переключится на второй поток. Тем самым процессор будет переключаться между потоками с минимальными задержками, и по максимуму использовать возможности каждого ядра.

Как работают Intel HT и AMD SMT можно понять по картинкам ниже. Первая картинка показывает, как занята очередь процессора при выполнении двух задач, а на второй картинке показана временная диаграмма, и что в случае с Intel HT и AMD SMT на обе задачи суммарно было потрачено меньше времени.


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

Долгое время никто не замечал главной проблемы этой технологии. Все потоки используют одни и те же кэши и аппаратные возможности процессора. Это открыло уязвимость, что один процесс может извлекать данные, наблюдая за изменениями кэша другим процессом. Только с ростом потребностей бизнеса и популяризации виртуализации, такая уязвимость стала чаще обсуждаться. Единственным способом безопасно запускать два процесса не доверяющих друг-другу стало отключение Intel HT и AMD SMT. С появлением уязвимостей класса Spectre данная проблема ещё больше усугубилась и поставила облачных провайдеров перед очень тяжелым выбором: отключить Intel HT и AMD SMT? И даже вызывало сильное раздражение, так как клиенты — бизнес требуют безопасности их данных. Но что же это для них значило? Конечно же, увеличение затрат и миллионные потери прибыли. Если при наличии Intel HT и AMD SMT они могли иметь больше клиентов чем ядер, и клиенты могли балансироваться между ядрами и выравнивать нагрузку на процессор, то теперь все облачные технологии готовы были заключить в жесткие рамки количества реальных ядер. И тут облачные провайдеры задумались не на шутку, как сделать так чтобы оставить включенными Intel HT и AMD SMT, сделать данные клиента безопаснее, например, закрытые ключи шифрования и пароли. Так появился на свет Core Scheduling, который безопасно может выполнять потоки.

▍Core Scheduling


Как же работает Core Scheduling? Всё очень просто! Каждому процессу назначается метка — cookie, по которому его можно идентифицировать. Так же свой уникальный cookie можно назначить каждому пользователю. Тем самым ядро разрешает совместное использование процессора только в случае, если у двух процессов совпадает cookie. Как говорится, всё гениальное просто!

Для управления процессами или потоками используется системный вызов prctl:

int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

В случае с Core Scheduling системный вызов принимает вид:

int prctl(PR_SCHED_CORE, int command, pid_t pid, enum pid_type type, unsigned long *cookie);

Где PR_SCHED_CORE говорит, что это операция с Core Scheduling, command это команда которую надо выполнить, pid process id — цифровой идентификатор процесса, pid_type тип pid, cookie — беззнаковое 32 битное число метки.

Для работы с Core Scheduling существует 4 команды:

  • PR_SCHED_CORE_CREATE — говорит ядру создать новый cookie и назначить его процессу с pid. С помощью параметра pid_type мы можем управлять шириной назначения cookie. Если значение равно PIDTYPE_PID, то cookie устанавливается для конкретного процесса с pid, а при PIDTYPE_TGID всей группе потоков. cookie не должен быть равен NULL.
  • PR_SCHED_CORE_GET — получает cookie для указанного pid, и сохраняет его в переменной cookie. Вы могли обратить внимание, что системному вызову передается не само значение cookie, а указатель на место в памяти где находится значение переменной cookie, тем самым системный вызов свободно может читать и писать значения по этому адресу. Полезность данной команды заключается только в получении cookie двух процессов и их сравнения. Большей ценности данная команда не имеет.
  • PR_SCHED_CORE_SHARE_TO — процесс который вызвал prctl может поделится своим cookie с процессом pid которого был передан. В остальном команда полностью повторяет параметры PR_SCHED_CORE_CREATE.
  • PR_SCHED_CORE_SHARE_FROM — извлекает cookie у процесса с pid, и назначает вызвавшему prctl.

Конечно же каждый процесс не может просто извлекать и назначать cookie, как ему вздумается. Иначе это подрывает безопасность, и враждебный процесс сможет установить свой cookie, что в свою очередь делает бессмысленным применение Core Scheduling. На этот случай в ядре Linux есть проверка, может ли процесс вызывать ptrace() на цели. Поэтому cookie могут распространяться только среди процессов, которые уже имеют определенный уровень доверия между собой. Так же не возможно сгенерировать cookie в пользовательском пространстве, так как необходимо, чтобы не связанные процессы всегда получали уникальные cookie.

Включить Core Scheduling можно при сборке ядра: General setup --> [*] Core Scheduling for SMT. Так же вы можете прочитать мою статью LTO оптимизация ядра Linux.

Так вот, если в системе включен Core Scheduling, то когда планировщик берет новую задачу с наивысшим приоритетом из списка, то он отправляет прерывание на родственные процессоры, на что каждый из них должен проверить cookie новой задачи и ответить есть ли задачи на нём с таким же cookie. Таким образом, при совпадении cookie, процессор уже имеющий задачу с таким же cookie, начинает выполнение этой новой задачи. Если же в системе нет процессов имеющий одинаковый cookie, то процессор будет бездействовать, пока такие процессы не появятся. Чтобы предотвратить простой процессора, планировщик будет переносить процессы между ядрами.

К сожалению первые версии Core Scheduling имели большой недостаток — высокие накладные расходы, падала производительность системы т.к. в некоторые моменты времени Core Scheduling заставлял процессор бездействовать, что в итоге стало ещё хуже чем отключение Intel HT и AMD SMT. Но со временем удалось улучшить алгоритм и решать большую часть проблем, но всё же не бесплатно. Поэтому Core Scheduling не подходит каждому т.к. немного снижает производительность системы, а только тем, кому очень важна безопасность, например облачным провайдерам. Так же Core Scheduling не подходит задачам которым важна скорость реакции, так называемые Real Time задачи, для них вовсе рекомендуется отключить Intel Hyper-Threading(Intel HT) и AMD SMT.

Как можно понять из статьи не бывает ложки меда без капли дегтя. Но развитие технологий идет и старые проблемы решаются, хоть не всё всегда идеально. Изначально Core Scheduling был протестирован мною на ядре 5.13-rc с патчами, и в принципе в нём не сложно включить Core Scheduling, но в одном из обновлений ядра 5.13 перестали работать патчи, и их надо было много править, что не имело уже большого смысла для меня и ядро 5.14 уже было более интересно для изучения. Мой актуальный набор патчей для ядра вы всегда можете скачать по ссылке, там всегда актуальные и стабильные патчи, которые не влияют на стабильность системы, но приносят ряд улучшений.

Моя прошлая статья LTO оптимизация ядра Linux вызвала у читателей много вопросов. Было много доброжелателей, которые мне писали лично и благодарили. Были и такие которые писали, чтобы поругаться и даже катали жалобы на мои проекты. Конечно же доброжелателей было значительно больше. Поэтому дам краткий ответ на частые вопросы:

  1. Насколько LTO ядро быстрее? Ответ с бенчмарками есть по ссылке Squeezing More Performance Out Of The Linux Kernel With Clang + LTO
  2. Для кого подходит LTO Оптимизация ядра? В первую очередь это разработчикам мобильных устройств, которым очень важна производительность и быстрый отклик. Изначально LTO оптимизация появилась для ядра Linux мобильных устройств, но показала свою высокую эффективность и была перенесена на x86_64. Поэтому LTO Оптимизация ядра Linux подходит всем, кому важна более высокая производительность и быстрый отклик от системы. Например сервера, которым важна скорость обработки и отдачи контента. За месяцы тестов не было выявлено ни одного побочного эффекта от применения LTO Оптимизации. Так же в статье LTO оптимизация ядра Linux был дан ответ почему всё работает хорошо и почему именно выбран clang+llvm.
  3. Несколько читателей хабра мне написали и попросили сделать репозиторий для моих измененных пакетов arch-packages. Один из них, поработал какое-то время с моими пакетами, и вышел с предложением разместить репозиторий у него на сервере. Так появился Arch Linux Club. Теперь все легко могут протестировать, как влияют измененные пакеты на производительность. Через какое-то время мой проект заметили разработчики одного Linux дистрибутива(форка Arch Linux), связались со мной и написали восторженные отзывы.
  4. В чем различие моих пакетов от официальных Arch Linux? Основное различие больше патчей и багфиксов. После того, как начал переделывать пакеты, был поражен, что майнтенейры не всегда понимают, что у них в сборочных скриптах, и часто забивают на исправления ошибок и совсем не читают багрепорты в апстриме. Например, не редко было, что некоторые программы у меня при работе крешились, а мейнтейнеры ничего не делали. В этом разработчики RedHat, Gentoo, Suse просто на две головы выше.

    Это заставило меня ещё больше переделать пакетов и добавить патчи. Возможно мое начинание даже перейдет в свой дистрибутив. Основной упор в пакетах на безопасность и производительность где возможно включены -z,relro,-z,now и -fstack-protector-strong -fstack-clash-protection -fPIE. Подробнее что это даёт можно прочесть на сайте RedHat.

    Изначально Arch Linux собирает все пакеты с оптимизацией под старые процессоры, что не очень актуально в современных реалиях. Поэтому мною используются пакеты оптимизированные под современные процессоры особенно под AMD Ryzen, которым пользуюсь. Оптимизации под процессор: -march=x86-64-v3 -mtune=generic и -march=znver2. Так же было прочитано большое количество багрепортов, был переработан glibc убраны устаревшие и ненужные параметры, улучшена производительность, добавлены исправления ошибок и проблем безопасности в пакетах. На данный момент улучшена производительность и стабильность всей системы, пофиксены основные утечки памяти, которые присутствуют в официальных пакетах.

При подготовке статьи использовалась переписка разработчиков ядра Linux — Linux Kernel Mailing List.
Уважаемые читатели, спасибо за прочтение. Так как ваше мнение очень важно, вы можете оставить свои замечания по статье в комментариях. Тем самым вы поможете сделать контент для вас более качественным и интересным.

Комментарии (26)


  1. dlinyj
    04.08.2021 16:24
    +1

    Снимаю шляпу за качественный контет о Linux! Громадное спасибо за статью.


    1. nullc0de Автор
      04.08.2021 17:12
      +1

      @dlinyj Спасибо за отзыв.


  1. DustCn
    04.08.2021 17:28
    +1

    >>Теперь в процессоре, имея два потока выполнения на каждое ядро, ядро процессора может не простаивать, когда один из потоков спит, а переключится на второй поток,

    Что ему мешает сделать это же без HT?


    1. nullc0de Автор
      04.08.2021 17:43

      Что ему мешает сделать это же без HT?

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


      1. DustCn
        06.08.2021 00:08
        +1

        >>до запуска таймера будет простой процессора.

        Не будет. Спящий процесс (выполнивший системный yield() или sleep()) гарантированно снимется шедулером с исполнения досрочно, освободив ядро для другого процесса. Вы перепутали это с активным циклом ожидания (spin loop).


        1. nullc0de Автор
          06.08.2021 01:40

          Спасибо КЭП за разъяснение! У меня такое чувство, что люди совсем не читали статью и комментарии, сами себе чето-то надумали сами же на свой вопрос ответили…
          yield() или sleep() это совсем другой случай, про это мной нигде не было написано. Поток даже может не вызвать yield() или sleep(). Ваши знания как раз видимо и spin loop ограничены…
          И вы упустили самый главный момент, что yield() или sleep() займет суммарно значительно больше тактов процессора чем при SMT и HT.

          Специально нигде сильно не углублялся, чтобы не усложнять теорию, просто описал основные принципы работы. А если углубиться в тему, то там еще вылезет кучу специфичных вещей для ОС и конкретной версии ядра… Если бы вы внимательно читали статью, то поняли бы, что речь не совсем про планировщик, а про Core Scheduling… Условно вы предлагаете в автошколе учить водителей, тому на какие контакты приходит сигнал зажигания, какая микросхема как работает, как паять контакты, что где-то там должен быть цифровой сигнал определенной формы и тогда автомобиль заведется. Когда можно просто дать ключи, объяснить, как завести, проверить уровень бензина в баке и все. Зачем водителю знать, как устроен двигатель внутреннего сгорания на инженерном уровне? Если ему достаточно знать, что есть бензин, свеча, свеча воспламеняет бензин и толкает поршень.

          Мы не будем углубляться в эту тему слишком глубоко, так как это тема отдельной статьи. Но вы всегда можете найти комментарии и пояснения к коду, в файле: include/linux/sched.h, и изучить эту тему самостоятельно. Умение читать исходные коды ядра, является очень важным навыком для каждого разработчика, который хочет углубиться в программирование ядра Linux. Часть полезной информации также можно найти в официальной документации ядра Linux — Scheduler, но всё же лучше смотреть исходные коды ядра.


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


          1. DustCn
            07.08.2021 20:11
            +1

            Так откуда простой процессора при уходе задачи в слип? Я уже не про статью пишу, в про ваш комментарий.

            Главный вопрос к статье в том, что вы неправильно поставили акцент зачем был сделан HT, как и написали вам снизу - для более полного использования ФУ ядер, а не для экономии на переключении. Экономия на переключении конекста это скорее побочный эффект, и в этом случае делали бы не 2HT а 4 или 8. Только про КНЛь не вспоминайте, там совсем другая история.

            >>Условно вы предлагаете в автошколе учить водителей

            Нет. ПК пользователям я ничего не предлагаю. А вы, и читатели Хабра - это инженеры, и знать как все устроено это их профессия.


    1. mikhanoid
      05.08.2021 13:20

      Гранулярность разная. Потоки в операционке засыпают, когда ждут внешних событий: завершения ввода или вывода или других аналогичных событий. В HT под простоем подразумевается, что поток ждёт выполнения долгой инструкции. Обычно это запись или чтение памяти в незакэшированную в L1 память.

      Но HT работает не только в этих ситуациях. Допустим, у вас у одного потока много инструкций для FPU блоков процессора, а у другого для ALU, в этом случае процессор тоже может планировать исполнение потоков совместно. Или бывают ситуации, когда в одном потоке длинная цепочка нераспараллеливаемых инструкций (как пример, расчёт факториала последовательным умножением чисел), такая цепочка не грузит все доступные функциональные устройства процессора, и на них можно запускать другой поток.

      Какая-то такая картинка.


  1. Teplo_Kota
    04.08.2021 17:58
    +1

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


    1. mikhanoid
      05.08.2021 13:23

      Linux стал большим коммерческим проектом... Со всеми вытекающими последствиями для PR


  1. 13werwolf13
    05.08.2021 08:28
    +1

    В этом разработчики RedHat, Gentoo, Suse просто на две головы выше.

    так может стоило выбрать opensuse, thumbleweed такой же ролинг как и арч, только поштабильнее, а OBS (build.opensuse.org) в разы адекватнее aur и всех рачеспособов доставить пакетов о которых не позаботились мейнтейнеры. А для особо упоротых он позволяет собирать и для рача и для дебиана и так далее.. и не надо болеть головой о том где и как репо содержать


    1. nullc0de Автор
      05.08.2021 10:48
      +1

      13werwolf13 может и так. Просто у арча самые удобные для чтения скрипты сборки и нравится как сам дистрибутив устроен, но честно раньше я не ожидал, что там мейнтейнеры р*дяи, и тоже в скриптах сборки много грязи. Я им даже патчи на блюдечке преподносил для нескольких пакетов, чтобы они взяли и обновили зависимые пакеты. Все дистрибутивы это сделали, а они там что-то телятся, и многие совсем не умеют читать баги в астриме и писать патчи. Лично перерабатываю каждый скрипт сборки, смотрю багрепорты, оптимизирую код сборки, удаляю костыли времен царя гороха, где-то добавляю свои самопальные патчи, где могу сам решить проблему. Переход для меня не имеет смысл, уже на пути создании своего дистрибутива. В день просматривают не мало багрепортов и по объему написанных патчей RedHat впереди всех, больше всего багов они закрывают, но скрипты сборки тяжелые у них. Насчет универсальной сборки под arch, redhat, debian это не сложно сделать, все собирается в clean chroot просто разный упаковщик пакетов, главная проблема, что везде по разному именуются зависимости, вот это думаю они не смогли решит, иначе пакет будет сразу в себе все зависимости содержать, чтобы не ломать его работу.


  1. LynXzp
    05.08.2021 11:15

    Теперь же решение о переключении задач принимает OC по заданному таймеру. Такую многозадачность называют вытесняющая многозадачность или PREEMPT, preemptive multitasking. Простой пример такой очереди, когда в очереди к директору компании стоят сотрудники, но приходит какой-нибудь проверяющий, или какое-то важное лицо, и всех просят подождать, пока директор пообщается с этим более важным человеком.
    Тема таймера в примере не раскрыта. Не то чтобы это очень важно, но создается странное впечатление от прочитанного. Наверное можно было бы и без таймера более приоритетную задачу пропускать (по прерыванию от клавиатуры например). Но благодаря таймеру даже менее приоритетный поток может получить управление перед более приоритетным, если тот будет долго выполнятся.


    1. nullc0de Автор
      05.08.2021 11:30
      -1

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


  1. mvv-rus
    05.08.2021 19:43
    +1

    В теоретической части как-то многовато неточностей.

    Каждая задача, после того, как выполнила свою работу, должна была явно уведомить планировщик, что задача завершила свою работу и планировщик может переключиться на следующую задачу. Такую очередь называют FIFO — First In First Out первым пришёл, первым ушёл. А такую многозадачность называют совместной или кооперативной многозадачностью.

    Не совсем точно. Характер многозадачности (кооперативная или вытесняющая) и политика планировщика (простая очередь — FIFO, приоритетная очередь или ещё как) — это две независмые характеристики. К примеру, в классической системе с кооперативной многозадачностью — MS Windows 3.х — была функция DirectedYield, позволявшая передать управление из текущей задачи указанной задаче.
    Чтобы уменьшить накладные расходы переключения задач в ОС, разработчики процессоров придумали простое и элегантное решение, переложили часть функций на процессор. Так были придуманы Intel Hyper-Threading (Intel HT) и AMD Simultaneous Multithreading (AMD SMT).

    Причина появления HT — указана не совсем верно, точнее — совсем неверно. HT был введен с целью наиболее полно задействовать все функциональные блоки ядра, которые там есть самые разные — обычные вычисления, вычисления с плавоющей точкой и т.д. Поэтому, кстати, с HT мало толку, когда практически все потоки в системе выполняют одноообразные задачи, а потому задействуют одни и те же функциональные блоки. А потому, для таких систем (например, серверов MS Exchange) включать HT не рекомендуется — выигрыш от задействия разных функциональных блоков не оправдывает потерь на диспетчеризацию для дополнительных ядер.


    1. WASD1
      05.08.2021 20:19

      ЕМНИП 100% + 30% получается, если есть кэш-миссы прямо в память.


    1. nullc0de Автор
      05.08.2021 21:19

      Характер многозадачности (кооперативная или вытесняющая) и политика планировщика (простая очередь — FIFO, приоритетная очередь или ещё как) — это две независмые характеристики.

      Да так и есть.

      К примеру, в классической системе с кооперативной многозадачностью — MS Windows 3.х — была функция DirectedYield, позволявшая передать управление из текущей задачи указанной задаче.

      Хоть у меня была 3.11 не знаю, что там было.

      HT был введен с целью наиболее полно задействовать все функциональные блоки ядра, которые там есть самые разные — обычные вычисления, вычисления с плавоющей точкой и т.д.

      Они без этого все задействованы. Так что тут вы не правы. Как раз дело в шедулере, что переключения через OC затрагивают суммарно больше времени и процессорных тактов.

      А потому, для таких систем (например, серверов MS Exchange) включать HT не рекомендуется — выигрыш от задействия разных функциональных блоков не оправдывает потерь на диспетчеризацию для дополнительных ядер.

      Это уже особенность Windows c его более микроядерной архитектурой. В Linux же используется монолитное ядро.


      1. mvv-rus
        05.08.2021 22:27
        +1

        Они без этого все задействованы.
        Нет. Например, если происходит кэш-промах при обращении к данным, то процессору приходится обращаться к памяти, а это — достаточно долгая операция (сотни тактов), за время выполнения которой все выбранные с опережением независимые от инструкции, обращающейся к памяти, инструкции успевают исполниться и исполнительные устройства процессора в результате простаивают. Планировщик ОС при этом не получает никакого уведомления: все это происходит для него прозрачно (да и не сможет он на это эффективно среагировать).
        А вот наличие второго потока выполнения для другого логического процессора в физическом процессоре с поддержкой HT позволяет в данном примере загрузить эти простаивающие исполнительные устройства физического процессора (если, конечно, этому потоку тоже не понадобится вскоре обратиться в память за содержимым, которое остутствует в кэше).
        Как раз дело в шедулере, что переключения через OC затрагивают суммарно больше времени и процессорных тактов.
        Строго говоря, HT сама по себе в переключении потоков операционной системы в планировщике не участвует. Она просто представляет планировщику ОС физический процессор как два логических процессора, на каждый из которых ОС назначет по одному потоку выполнения. Огрубленно ( если не обращать внимание на детали, что современные ОС знают о логических процессорах и принимают меры, чтобы не снизить производительность их неправильным использованием), ОС работает с логическим процессом HT так же как с физическим ядром.
        Это уже особенность Windows c его более микроядерной архитектурой. В Linux же используется монолитное ядро.
        Вся эта работа по представлению физического процессора как двух логических происходит при выполнении пользовательской программы в пользовательском режиме работы логического процессора, безо всякого вмешательства ОС. Поэтому рост или потеря производительности от ядра ОС никак не зависит: это — интимное дело процессора и программы пользовательского режима.


        1. nullc0de Автор
          05.08.2021 22:38

          Нет. Например, если происходит кэш-промах при обращении к данным, то процессору приходится обращаться к памяти, а это — достаточно долгая операция (сотни тактов), за время выполнения которой все выбранные с опережением независимые от инструкции, обращающейся к памяти, инструкции успевают исполниться и исполнительные устройства процессора в результате простаивают. Планировщик ОС при этом не получает никакого уведомления: все это происходит для него прозрачно (да и не сможет он на это эффективно среагировать).
          А вот наличие второго потока выполнения для другого логического процессора в физическом процессоре с поддержкой HT позволяет в данном примере загрузить эти простаивающие исполнительные устройства физического процессора (если, конечно, этому потоку тоже не понадобится вскоре обратиться в память за содержимым, которое остутствует в кэше).

          Строго говоря, HT сама по себе в переключении потоков операционной системы в планировщике не участвует. Она просто представляет планировщику ОС физический процессор как два логических процессора, на каждый из которых ОС назначет по одному потоку выполнения. Огрубленно ( если не обращать внимание на детали, что современные ОС знают о логических процессорах и принимают меры, чтобы не снизить производительность их неправильным использованием), ОС работает с логическим процессом HT так же как с физическим ядром.

          Мною про это было написано. Вы сами же что-то надумали, и сами же ответили себе…


          1. mvv-rus
            06.08.2021 00:24

            Ну, если вы изначально думали именно так, то дальше смысла вести дискуссию нет.
            А если бы вы ещё и написали так, чтобы вас не могли понять неправильно — было бы совсем здорово.


  1. WASD1
    05.08.2021 19:54
    +1

    >> Чтобы уменьшить накладные расходы переключения задач в ОС, разработчики процессоров придумали простое и элегантное решение, переложили часть функций на процессор. Так были придуманы Intel Hyper-Threading (Intel HT) и AMD Simultaneous Multithreading (AMD SMT).

    В первый раз вижу такую странную интерпретацию причины появление Hyper Threading.
    Возможно у вас будет ссылка?

    Wiki, например, пишет, что целью было "увеличить параллелизм".
    А разъясняющие статьи - объясняют, что процессорное ядро имеет общий бэкэнд (ALU и наборы регистров) для двух логических потоков исполнения.
    И если основной поток "простаивает", например на длительных операциях (с точки зрения Out Of Order исполнения: не осталось полностью готовых (микро-)операций в основном потоке исполнения) - начинают исполняются операции второго потока исполения.


    1. nullc0de Автор
      05.08.2021 21:09

      WASD1 ссылок нет, вполне логичная интерпретация. Выше уже был дан ответ, почему накладные расходы на переключение выше при использовании средств ОС. И в статье есть картинка иллюстрирующая это, похожие картинки есть в интернете по запросам Hyper-Threading, Intel HT, Simultaneous Multithreading, AMD SMT. Можно добавить еще, что в случае вытесняющей многозадачностью с таймером происходит прерывание, а все прерывания, как нас раньше учили самые дорогие операции для процессора. А в случае кооперативной многозадачности, мы можем не дождаться, что поток нам даст управление т.к. он должен делать это вручную, и при этом может простаивать. В случае с HT и SMT работает простой принцип если конвеер процессора простаивает происходит автоматические переключение на симметричный поток. Для понимания его принципа работы 99% людей этого более чем достаточно. Это все есть на картинке, где показан принцип работы. Могу предположить, что еще предсказание переходов лучше работает при HT и SMT, и отсюда тоже чуть лучше будет производительность. Если человек не занимается слишком низкоуровневым программирование, и низкоуровневой оптимизацией кода или производством процессоров, глубже нет смысла углубляться.


      1. WASD1
        05.08.2021 21:50

        >> ссылок нет, вполне логичная интерпретация.
        @nullc0de жаль, только Intel о "логичности этой интерпретации" не знает.

        А не знает об этом Intel потому, что она неправильная (тем более, в ситуации, сервера, когда потоков больше, чем доступных процессорных ядер - у нас же ведь больше, иначе вы бы статью не писали, а просто Hyper Threading отключили бы).

        Если у вас 1 аппаратный поток исполниния, на 1 процессорное ядро и каждый thread работает по 100мс, то на этом процессорном ядре 10 раз в секунду будет сниматься thread и планироваться следующий.

        Если у вас 2 аппаратных потока исполнения, на 1 процессорное ядро (Hyper Threading) - и каждый процесс работает по 100мс, то на каждом из двух аппаратных потоках 10 раз в секунду будет сниматсья логический thread и планироваться новый.

        Те же самые результаты (одинаковое время работы логических thread до снятия с процессора) вы получите, если у вас будет 10 аппаратных потоков, 20 аппаратных потоков, если каждый thread в среднем будет работать 50мс, 20мс......

        Среднее время работы логического thread до снятия с процессора не зависит от наличия heper threading (разумеется в предположении, что логических thread у нас больше, чем потоков исполнения предоставленных железом).

        Поэтому да инженерная чаксть у вас вполне крутая, но по теоретической - поправьте пожалуйста и не вводите людей в заблуждение.


        1. nullc0de Автор
          05.08.2021 22:18

          WASD1 спасибо за ваше гениальное разъяснение претендующее на нобелевскую премию. Процессор не работает в категориях миллисекунд, он работает в категории тактов и инструкций процессора. Вы еще пытаетесь переложить ситуацию со 100% загрузкой процессора, на задачи, когда один поток уснул и работает другой поток. При 100% загрузке нет никакого преимущества и RT задачах тоже. И вы читали статью не внимательно, мною было сделано замечание на эту тему в статье. Читайте на здоровье www.cs.unc.edu/~anderson/papers/ecrts19d.pdf


          1. WASD1
            06.08.2021 13:41

            >> Процессор не работает в категориях миллисекунд, он работает в категории тактов и инструкций процессора.
            Процитируйте где я написал, что процессор работает в категориях миллисекунд (или опять "ссылки нет, но интерпретация логичная").
            И да в ситуации с HT/без HT "число инструкций за 1 квант времени" - изменяется очень сильно. Если вы об этом не знаете, или не понимаете, что измерять надо в сравнимых величинах - спросили бы, я бы вам объяснил (Intel давал цифру +30% производительности per Core при включении HT).


            >> Вы еще пытаетесь переложить ситуацию со 100% загрузкой процессора, на задачи, когда один поток уснул и работает другой поток. При 100% загрузке нет никакого преимущества и RT задачах тоже.
            Когда один поток уснул, то он снимется с процессора и произойдёт переключение контекста. Что при включении HT, что без включения HT.

            Есть эффекты второго порядка (почему число переключений контекста может быть ниже или выше на любую разумную метрику).
            Но для начала неплохо бы разобраться с основным эффектом: почему в gorss-подсчёте число переключений контекста (на поток исполнения процессора) не снижается.


  1. 0serg
    14.08.2021 14:17
    +1

    По HT выше куча объяснений — и все неправильные :).
    Ну то есть на очень высоком уровне все верно — HT нужен чтобы улучшить использование функциональных блоков CPU за счет паралеллизма. Но реализация работает не за счет того что «один поток использует fpu а другой alu».

    Идей там на самом деле две. Первая — это то что память работает намного медленнее чем CPU. Если процесс полез в память а с кэшем нам не повезло, то он может довольно долго ждать пока эти данные подгрузятся в кэш. Соответственно пока один поток ждет данные из памяти — второй может гонять вычисления на CPU.

    Вторая идея — это понятие зависимостей по данным. В этом случае исходных данных для запуска команды нет просто потому что их еще не вычислили. Например наивное суммирование массива: sum = sum + x[i]. Чтобы добавить в эту сумму i-е число надо вначале вычислить сумму с i-1м. Даже если это делать за такт (а в реальных CPU это зачастую занимает дольше) то мы будем по 1 числу за такт добавлять в сумму, а ALU у нас штуки 3. Зависимости по данным мешают полностью загружать исполнительные блоки процессора, тут-то на сцену и выходит HT который позволяет эти «незагруженные» ALU нагрузить вычислениями из второго потока.

    Современные видеокарты, кстати, работают по родственным принципам, только там не пара потоков в HT а сразу пара сотен. Выкидываем из процессора все сложные фишки типа out-of-order execution и ставим кучу ALU, после чего используем HT в громадных масштабах.