я должен привязать крутящуюся нить к определенному ядру?

Мое приложение содержит несколько критичных к задержке потоков, которые «крутятся», то есть никогда не блокируются. Ожидается, что такой поток займет 100% одного ядра процессора. Однако кажется, что современные операционные системы часто передают потоки от одного ядра к другому. Так, например, с этим кодом Windows:

void Processor::ConnectionThread()
{
    while (work)
    {
        Iterate();
    }
}

Я не вижу в диспетчере задач ядро ​​"занято на 100%", общая загрузка системы 36-40%.

Но если я изменю его на это:

void Processor::ConnectionThread()
{
    SetThreadAffinityMask(GetCurrentThread(), 2);
    while (work)
    {
        Iterate();
    }
}

Затем я вижу, что одно из ядер процессора занято на 100%, а также общая нагрузка на систему снижена до 34-36%.

Значит ли это, что я должен стремиться SetThreadAffinityMask для "раскручивания" ниток? Если я улучшил задержку, добавив SetThreadAffinityMask в этом случае? Что еще мне нужно сделать, чтобы «вращать» потоки, чтобы уменьшить задержку?

Я занимаюсь портированием своего приложения на Linux, поэтому этот вопрос больше касается Linux, если это имеет значение.

upd нашел этот слайд, который показывает, что может помочь привязка потока ожидания, занятого в ожидании, к ЦП:

введите описание изображения здесь


person Oleg Vazhnev    schedule 19.09.2014    source источник
comment
FWIW, занятый поток мигрирует в другое ядро ​​только при запуске планировщика потоков. В зависимости от вашей операционной системы это обычно происходит каждые 10-15 миллисекунд. 10 миллисекунд - это эон для современных процессоров.   -  person 500 - Internal Server Error    schedule 19.09.2014
comment
Windows пытается не перегревать ядра, перемещая тяжелые потоки. Лучше не привязывать потоки к ядрам без конкретной конкретной и веской причины.   -  person Dialecticus    schedule 19.09.2014
comment
Лучшая задержка - это конкретная и веская причина   -  person Oleg Vazhnev    schedule 19.09.2014
comment
Достаточно хорошая задержка - это убедительно. Просто лучше - это недостаточно, если у вас уже есть достаточно хороших знаний.   -  person Dialecticus    schedule 19.09.2014
comment
Вы измерили уменьшение задержки?   -  person eerorika    schedule 19.09.2014
comment
Нет, я не знаю, как измерить задержку, поэтому задаю этот вопрос. Насколько я понимаю, задержка сродства потока должна быть лучше, но приятно знать, как это проверить :)   -  person Oleg Vazhnev    schedule 19.09.2014
comment
Это не значит, что общая задержка будет лучше. Эффект ограниченного выбора при выборе набора потоков для запуска на доступных ядрах может сделать сложную многопоточную систему в целом медленнее.   -  person Martin James    schedule 19.09.2014
comment
@MartinJames, поэтому я задаю вопрос - чтобы знать, как это реализовать, чтобы иметь лучшую задержку   -  person Oleg Vazhnev    schedule 19.09.2014
comment
Было бы полезно рассказать, что это за приложение на самом деле. Обычно для приложений реального времени лучше использовать специальный встроенный микроконтроллер, который можно использовать для решения критических задач по задержке и, при необходимости, сопрягать его с управляющим приложением ПК.   -  person dtech    schedule 21.09.2014
comment
@ddriver Исходное приложение работало в Windows, и у него нет никаких возможностей в реальном времени, поэтому я предполагаю, что у него нет никаких требований в реальном времени. Однако, если есть требования в реальном времени, их поддержка, как правило, будет осуществляться за счет общей задержки. Однако использование операционной системы реального времени (rtlinux, vxworks, qnx и т. Д.) И приоритетов реального времени уменьшит дрожание.   -  person Jason    schedule 22.09.2014
comment
@ Джейсон, меня больше интересует Stock Rhel, я не планирую использовать операционную систему в реальном времени   -  person Oleg Vazhnev    schedule 22.09.2014
comment
Что на самом деле делает эта ветка?   -  person David Schwartz    schedule 23.09.2014
comment
Какое жесткое ограничение задержки вы можете принять в потоке? Минимальное время для сетевой карты 10 ГБит, чтобы получить новый пакет, составляет около 1000 нс + время драйвера, которое должно составлять около 1000-5000 нс, в зависимости от того, насколько горячим кешем для драйвера.   -  person Surt    schedule 24.09.2014
comment
я пишу программное обеспечение HFT. поэтому я хочу, чтобы все происходило как можно быстрее. но, по крайней мере, сейчас, я не планирую использовать ОС реального времени, только стандартную сетевую карту RHEL + 10 ГБ Sollarflare или mellanox   -  person Oleg Vazhnev    schedule 24.09.2014
comment
Таким образом, каждый нс может стоить много долларов. Ваш поток будет GetDataFromNet, Decide (Купить / Продать / Ничего не делать), SendDataToNet. Показатель производительности подскажет, должны ли все 3 быть выполнены на одном ядре или разделены на производителей и потребителей. Вы не можете сделать настройку, которая использует 100% ЦП, так как это приостановит некоторые потоки на более длительное время 10 мс на стандартном RHEL afair. Оставьте 1-2 ядра полностью свободными для случайных запусков ОС.   -  person Surt    schedule 24.09.2014
comment
конечно я не собираюсь раскручивать / использовать все доступные ядра. Я планирую использовать один 10-ядерный процессор, поэтому можно оставить 2 ядра для ОС, остальные 8 я могу использовать для своей работы.   -  person Oleg Vazhnev    schedule 24.09.2014
comment
@javapowered Просто небольшой совет по архитектуре, но обычно вы хотите оптимизировать алгоритмы, прежде чем начинать копаться в оптимизации взаимодействия с операционной системой. Распределение памяти также огромно; по возможности избегайте кучи. Память также является одним из самых узких мест; поэтому оптимизируйте использование кэша и не делитесь данными между потоками, в которых нет необходимости. Запись на диск полностью убьет производительность. Вы также хотите избежать системных вызовов, так как они будут запускать переключение контекста, которое на современных чипах стоит примерно 2 мкс. источник - пишу алгоритмы для не отображаемых рынков.   -  person Jason    schedule 25.09.2014
comment
Кроме того, RTLinux и потоки с приоритетом реального времени обычно не уменьшают задержку по сравнению со стандартным Linux (особенно, если процессор не вызывает конкуренции). Однако это минимизирует джиттер. Вы можете увидеть редкие всплески задержки со стандартным Linux, но средняя задержка все равно будет ниже.   -  person Jason    schedule 25.09.2014
comment
Я предпочитаю лучшую среднюю задержку, редкие всплески - это нормально.   -  person Oleg Vazhnev    schedule 25.09.2014
comment
@javapowered Если ваша стратегия очень чувствительна к задержкам, вы также можете захотеть nice или renice свой процесс как root (кто-то упомянул методы Windows api ниже).   -  person Jason    schedule 26.09.2014
comment
@javapowered Я добавил достойную ссылку на перечисление топологии процессора Intel, на которую вы, возможно, захотите взглянуть.   -  person Jason    schedule 27.09.2014
comment
@ Джейсон, спасибо, как мне его использовать? почему это важно?   -  person Oleg Vazhnev    schedule 28.09.2014
comment
@javapowered Топология - это информация, которую планировщик использует для принятия решений по планированию (миграции ЦП и т. д.). Например, @Surt упомянул SMT и его влияние на производительность. Топология процессора сообщает вам, какие процессоры являются партнерами SMT. Он также сообщает вам, какие уровни кэша совместно используются процессорами, которые, как правило, быстрее использовать для межпоточного взаимодействия. Однако самое важное - сначала измерить, прежде чем приступить к оптимизации. Так, например, если вы начинаете профилирование с perf или с rdtsc и видите много промахов кеша или больших задержек, может иметь смысл закрепить потоки.   -  person Jason    schedule 28.09.2014


Ответы (5)


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

Причины (R)

  • ваш код, скорее всего, будет в вашем iCache
  • предикторы ветвления настроены на ваш код
  • ваши данные, вероятно, будут готовы в вашем dCache
  • TLB указывает на ваш код и данные.

Пока не

  • Your running a SMT sytem (ex. hyperthreaded) in which case the evil twin will "help" you with by causing your code to be washed out, your branch predictors to be tuned to its code and its data will push your out of the dCache, your TLB is impacted by its use.
    • Cost unknown, each cache misses cost ~4ns, ~15ns and ~75ns for data, this quickly runs up to several 1000ns.
    • Он сохраняет по каждой причине, упомянутой выше, которая все еще существует.
    • Если злой близнец также просто раскручивает, затраты должны быть намного ниже.
  • Or your allowing interrupts on your core, in which case you get the same problems and
    • your TLB is flushed
    • вы берете удары 1000 нс-20000 нс на переключатель контекста, большинство из них должно быть в нижнем конце, если драйверы хорошо запрограммированы.
  • Or you allow the OS to switch your process out, in which case you have the same problems as the interrupt, just in the hight end of the range.
    • switching out could also cause the thread to pause for the entire slice as it can only be run on one (or two) hardware threads.
  • Or you use any system calls that cause context switches.
    • No disk IO at all.
    • только асинхронный ввод-вывод остальное.
  • наличие большего количества активных (не приостановленных) потоков, чем ядер, увеличивает вероятность проблем.

Поэтому, если вам нужна задержка менее 100 нс, чтобы ваше приложение не взорвалось, вам необходимо предотвратить или уменьшить влияние SMT, прерываний и переключения задач на ваше ядро. Идеальным решением была бы Операционная система реального времени со статическим планированием . Это почти идеально подходит для вашей цели, но это новый мир, если вы в основном занимались серверным и настольным программированием.

Недостатки привязки потока к единственному ядру:

  • It will cost some total throughput.
    • as some threads that might have run if the context could have been switched.
    • но в этом случае важнее время ожидания.
  • If the thread gets context switched out it will take some time before it can be scheduled potentially one or more time slices, typically 10-16ms, which is unacceptable in this application.
    • Locking it to a core and its SMT will lessen this problem, but not eliminate it. Each added core will lessen the problem.
    • установка более высокого приоритета уменьшит проблему, но не устранит ее.
    • расписание с SCHED_FIFO и наивысшим приоритетом предотвратит большинство переключений контекста, прерывания могут вызывать временные переключения, как и некоторые системные вызовы.
    • Если у вас есть установка с несколькими процессорами, вы можете получить исключительное право собственности на один из процессоров через процессор. Это предотвращает его использование другими приложениями.

Использование pthread_setschedparam с SCHED_FIFO и наивысшим приоритетом, выполняющимся в SU и блокировкой он до мозга костей и его злой двойник должны обеспечивать лучшую задержку из всех перечисленных, только операционная система реального времени может исключить все переключения контекста.

Другие ссылки:

Обсуждение прерываний.

Ваша Linux может принять вызов sched_setscheduler с помощью SCHED_FIFO , но для этого требуется, чтобы у вас был свой собственный PID, а не только TID, или чтобы ваши потоки выполняли совместную многозадачность.
Это может быть не идеально, поскольку все ваши потоки будут переключаться только «добровольно», что лишает ядро ​​гибкости для его планирования.

Межпроцессное взаимодействие в 100 нс

person Surt    schedule 23.09.2014
comment
как я могу сказать Linux не использовать ядро ​​ни для чего другого, кроме моего потока? отключить прерывания и все остальное - person Oleg Vazhnev; 23.09.2014
comment
/ dev / cpuset - это псевдофайловая система, в которой вы можете настроить совместное использование процессора. Один из вариантов - cpu_exclusive, но он работает только на уровне процессора, а не на уровне ядра. И sched_setscheduler может установить ваш процесс в SCHED_FIFO, но тогда вам придется выполнять собственное планирование. - person Surt; 23.09.2014
comment
Это хороший момент, SMT обычно разделяют все, но регистрируют. Планировщик linux обычно знает о процессорах SMT (я бы предположил, что ядро ​​RHEL, не уверен в Windows). Я по-прежнему считаю perf лучшим инструментом для первой диагностики этих проблем. Может быть, вам пригодится ссылка на wiki rtlinux? - person Jason; 23.09.2014
comment
Добавлена ​​информация о pthread_setschedparam, настройка расписания на основе pthread. - person Surt; 24.09.2014
comment
оба ответа очень хороши, но я могу назначить награду только за один, но я поддержал ваш ответ) - person Oleg Vazhnev; 27.09.2014
comment
Сейчас я использую RHEL 7.1. до сих пор не понимаю, что мне делать. Я не хочу делать собственное планирование, тогда мне не следует использовать sched_setscheduler? но что мне делать? просто привяжите поток к ядру каким-то образом, используя некоторую функцию c ++ setaffinity, и все? - person Oleg Vazhnev; 17.03.2015

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

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

Одна из основных причин, по которой установка сродства к процессору задачи является полезной, заключается в том, что она дает более предсказуемое поведение кеша и tlb (буфер обратного преобразования). Когда задача переключает процессор, операционная система может переключить его на процессор, у которого нет доступа к кешу последнего процессора или tlb. Это может увеличить количество промахов в кэше для задачи. Это особенно проблема взаимодействия между задачами, так как требуется больше времени для взаимодействия между кешами более высокого уровня и, наконец, с памятью. Для измерения статистики кеширования в Linux (производительности в целом) я рекомендую использовать perf.

Лучшее предложение - действительно измерить, прежде чем пытаться исправить сходство. Хороший способ количественно оценить задержку - использовать инструкцию rdtsc (по крайней мере, на x86). Это считывает источник времени процессора, что обычно дает наивысшую точность. Измерение событий дает точность примерно наносекунду.

volatile uint64_t rdtsc() {
   register uint32_t eax, edx;
   asm volatile (".byte 0x0f, 0x31" : "=d"(edx), "=a"(eax) : : );
   return ((uint64_t) edx << 32) | (uint64_t) eax;
}
  • примечание - инструкция rdtsc должна быть объединена с ограничителем нагрузки, чтобы гарантировать выполнение всех предыдущих инструкций (или используйте rdtscp)
  • также обратите внимание - если rdtsc используется без постоянного источника времени (в linux grep constant_tsc /proc/cpuinfo, вы можете получить ненадежные значения при изменении частоты и если задача переключает ЦП (источник времени)

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

Дополнительное чтение ...

person Jason    schedule 21.09.2014
comment
спасибо, поэтому я должен вызвать rdtsc в потоке производителя, затем вызвать rdtsc в потоке потребителя, вычислить разницу. тогда я должен попытаться привязать потребительский поток к определенному ядру или отсоединить его и сравнить, что лучше? у вас есть полный пример использования rdtsc? также, есть ли какие-либо внешние инструменты Linux, которые можно использовать для проверки того, насколько хороша задержка моей программы? возможно измерить количество переключений контекста или длительность или что-то в этом роде? - person Oleg Vazhnev; 22.09.2014
comment
@javapowered Нет проблем. Вы можете рассчитать дельты между rdtsc (вы, вероятно, захотите rdtscp между потоками), но вы можете добавить дополнительную задержку, поделившись переменной (особенно если она пересекает границу строки кеша). Обычно более точно рассчитывать тайминги на поток. Вы, вероятно, захотите также сделать функцию inline. В Linux вы можете использовать perf для получения статистики по всему, от переключения контекста до страницы с ошибками кэширования промахов. Если у вас установлено perf, попробуйте perf list посмотреть, что вы можете записать. - person Jason; 22.09.2014
comment
Что касается конкретного обеспечения того, чтобы задержка оставалась прежней или улучшалась, я бы попытался сначала измерить, используя rdtsc в обеих версиях. Имея достаточное количество точек данных, вы можете сравнить статистику (минимальное, максимальное, среднее, стандартное отклонение). Если новая версия имеет худшую задержку, вы можете попробовать выполнить диагностику с помощью perf. Если вы видите много промахов кеша или миграций ЦП, вы можете явно установить привязку к процессорам, которые совместно используют кеши (pthread_setaffinity_np или sched_setaffinity). Планировщик linux обычно очень хорош в этом. - person Jason; 22.09.2014
comment
Добавил еще пару полезных ссылок. На сайте Preshing есть много отличной информации по эффективному многопроцессорному программированию (избегайте volatile). Выделение памяти также обычно является большим узким местом, поэтому понимание основного механизма кучи - хорошая идея. - person Jason; 27.09.2014
comment
если rdtsc лучше, чем таймер высокого разрешения c ++ 11? stackoverflow.com/a/5524138/93647 - person Oleg Vazhnev; 17.03.2015
comment
@javapowered Многие библиотеки (вероятно, большинство реализаций C ++ 11/14) фактически эффективно реализуют вызовы таймера в терминах или напрямую как rdtsc или rdtscp. 74ns может быть более чем одной инструкцией, но это зависит от процессора и частоты. Всегда есть компромисс между сложностью кода и точностью / производительностью. Обычно вы можете использовать objdump с ОС POSIX, чтобы увидеть, что на самом деле испускает компилятор. - person Jason; 25.03.2015

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

На самом деле лучший способ уменьшить задержку - повысить приоритет процесса и потока (ов) опроса. Обычно ОС прерывает ваши потоки сотни раз в секунду и позволяет другим потокам работать некоторое время. Ваш поток может не работать несколько миллисекунд.

Повышение приоритета уменьшит эффект (но не устранит его).

Подробнее о SetThreadPriority и SetProcessPriorityBoost. В документации есть некоторые детали, которые вам нужно понять.

person egur    schedule 23.09.2014

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

  • busy wait with no affinity set
    • busy wait with affinity set
    • образец наблюдателя
    • сигналы

Неизменным победителем стал «Ожидание в ожидании с набором аффинити». Насчет этого сомнений нет.

Теперь, как многие отмечали, не забудьте оставить пару ядер свободными, чтобы ОС могла работать свободно.

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

person Ariel Silahian    schedule 15.06.2016

Это просто глупо. Все это снижает гибкость планировщика. Если раньше он мог запускать его на любом ядре, которое считал лучшим, теперь он не может. Если планировщик не был написан идиотами, он переместил бы поток на другое ядро ​​только в том случае, если бы у него была для этого веская причина.

Итак, вы просто говорите планировщику: «Даже если у вас есть действительно веская причина для этого, все равно не делайте этого». Почему ты так сказал?

person David Schwartz    schedule 23.09.2014
comment
Планировщик не совсем всеведущий, и он должен балансировать использование ресурсов между задачами. Существует ограничение на количество информации, которую он может учитывать при принятии решений о планировании. - person Jason; 23.09.2014
comment
@Jason Тем не менее, он не переместит задачу в другое ядро, если для этого нет веской причины. Он может не делать того, что вы могли бы захотеть сделать, но он не будет активно делать то, что не должен делать без причины. - person David Schwartz; 23.09.2014
comment
Я согласен, планировщики ядра в основном чрезвычайно хороши в том, что они делают. Планировщик linux подвергся огромному количеству исследований. Однако они не совсем безошибочны, поэтому я думаю, что лучше всего сначала измерить что-то вроде perf. - person Jason; 23.09.2014
comment
Планировщик переместит поток в другое ядро, если он отключит контекст, а затем другое ядро ​​станет свободным раньше, чем его исходное. Это стоит 5000-50000 нс помимо времени бездействия. - person Surt; 24.09.2014
comment
@Surt Планировщик может переместить поток на другое ядро, если другое ядро ​​освобождается раньше, чем это делает оригинал. Это не обязательно. Он может дождаться освобождения исходного ядра. Он будет делать то, что считает лучшим в данных обстоятельствах, которые должны быть такими, какими вы хотите. Вы бы предпочли подождать, пока исходное ядро ​​простаивает, в то время как другое совершенно хорошее ядро ​​будет потрачено впустую, даже если планировщики считают это глупым? - person David Schwartz; 24.09.2014
comment
Нет, я бы хотел, чтобы планировщик выкинул скваттера :) - person Surt; 24.09.2014
comment
@Surt Если есть скваттер, тайники все равно разнесут. Нет необходимости выбрасывать скваттер, если другой сердечник свободен. Планировщик знает все эти вещи и разработан действительно умными ребятами. - person David Schwartz; 24.09.2014
comment
Это потому, что изменение ядра стоит намного дороже, см. переключение контекста - person Surt; 24.09.2014
comment
@Surt Верно, но в данном случае вы уже оплатили все эти расходы. Так что экономить не на чем. Нет никакого преимущества в том, чтобы выбросить приседающего. - person David Schwartz; 24.09.2014
comment
Нет, если ОС немедленно вытеснит скваттера. OP хочет минимально возможную задержку на его сетевой карте при любой стоимости, это означает привязку потока к этому ядру и, по сути, делает его эксклюзивным для этого потока. См. Комментарии под OP. - person Surt; 24.09.2014
comment
@Surt Я не согласен. Тайники уже взорваны. Вы уже оплатили затраты, а есть простаивающее ядро. Но в любом случае это не имеет значения. Планировщик, как мы надеемся, знает, кто из нас прав. - person David Schwartz; 24.09.2014