Как я могу отличить высокопроизводительные и низкопроизводительные ядра/потоки в C++?

Говоря о многопоточности, часто кажется, что потоки рассматриваются как равные — точно такие же, как основной поток, но работающие рядом с ним.

Однако на некоторых новых процессорах, таких как чип Apple M1 и готовящийся Intel серии Alder Lake не все потоки одинаково производительны, поскольку эти чипы имеют отдельные высокопроизводительные ядра и высокую эффективность. , более медленные ядра.

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

Есть ли способ запросить свойства std::thread и указать, на каких ядрах они будут работать в C++?


person janekb04    schedule 19.07.2021    source источник
comment
Только с std это невозможно. Вы можете запросить собственный дескриптор, используя native_handle, и в зависимости от возвращаемого типа используйте соответствующую библиотеку для запроса этой информации (учитывая, что эта библиотека предоставляет эту информацию). Но для этого нет ни универсального, ни платформенно-независимого решения.   -  person t.niese    schedule 19.07.2021
comment
Нити не привязаны к чипу. ОС перемещает потоки вперед и назад по мере необходимости.   -  person Mooing Duck    schedule 19.07.2021
comment
@MooingDuck для M1 и macOS, на самом деле, можно попросить ОС, чтобы она запускала поток предпочтительно на высокоэффективном ядре. И теоретически вы можете заблокировать (в зависимости от ОС и процессора) блокировку процесса/потока на одном процессоре. Что часто делают одни серверы для виртуальных хостов.   -  person t.niese    schedule 19.07.2021
comment
Вам нужно будет использовать API ОС, чтобы выделить поток конкретному ядру. Нет никаких гарантий, что потоки будут выполняться на разных ядрах или выполняться исключительно на одном ядре. Потоки могут выполняться в одноядерной (в многоядерной) системе, как и другие задачи.   -  person Thomas Matthews    schedule 19.07.2021
comment
Одной из существующих разработок оборудования, которая может иметь подобные проблемы, является numa. То, как код с поддержкой NUMA должен обрабатывать распределение потоков между разными процессорами (чтобы наилучшим образом использовать преимущества разных скоростей доступа к памяти), может быть полезным.   -  person 1201ProgramAlarm    schedule 19.07.2021
comment
Вы знаете, ядро ​​изобретено для решения нескольких задач. Если вы думаете, что справитесь с задачами лучше, чем ядро, вы ошибаетесь. Вы можете использовать некоторые API уровня ОС для управления своими потоками, в Linux это pthread API.   -  person Yves    schedule 20.07.2021
comment
Отвечает ли это на ваш вопрос? Как связать обрабатывать только физические ядра межсистемным способом?   -  person user2284570    schedule 21.07.2021
comment
Альтернативой является использование cpuset для привязки процесса к набор процессоров. Это не поддерживает многопоточность, но, по-видимому, ваши процессы достаточно дешевы, чтобы переходить от потока к процессу нормально.   -  person Akshat Mahajan    schedule 21.07.2021


Ответы (5)


Как отличить высокопроизводительные и низкопроизводительные ядра/потоки в C++?

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

Любая попытка нарушить абстракцию потока (например, определить, является ли ядро ​​низкопроизводительным или высокопроизводительным ядром) ошибочна. Например. ОС может изменить ваш поток на низкопроизводительное ядро ​​сразу после того, как вы обнаружите, что работаете на высокопроизводительном ядре, что заставит вас предположить, что вы используете высокопроизводительное ядро, когда это не так.

Даже привязка вашего потока к конкретному ядру (в надежде, что он всегда будет использовать высокопроизводительное ядро) может/будет иметь неприятные последствия (заставит вас выполнить меньше работы, потому что вы помешали себе использовать более быстрое, чем ничего низкое). -ядро производительности, когда высокопроизводительные ядра заняты выполнением другой работы).

Самая большая проблема заключается в том, что C++ создает худшую абстракцию (std::thread) поверх вероятно лучшей абстракции, предоставляемой ОС. В частности, невозможно установить, изменить или получить приоритет потока с помощью std::thread; поэтому вы остаетесь без какого-либо контроля над подсказками производительности, которые необходимы (для ОС, планировщика) для принятия правильных решений по управлению нагрузкой, производительностью и питанием.

Говоря о многопоточности, часто кажется, что потоки рассматриваются как равные.

Часто люди думают, что мы все еще используем системы с разделением времени 1960-х годов. Хватит слушать этих дураков. Современные системы не позволяют тратить процессорное время на неважную работу, пока более важная работа находится в ожидании. Эффективное использование приоритетов потоков является фундаментальным требованием к производительности. Все остальное (нагрузка, производительность и решения по управлению питанием) по необходимости находится вне вашего контроля (на другой стороне используемой вами абстракции потоков). .

person Brendan    schedule 20.07.2021
comment
Да, TL:DR обычно не нужно различать. ОС уже перенесет ваши ресурсоемкие потоки на высокопроизводительные ядра, если они там не запускаются, если достаточное их количество свободно. Возможно, вы захотите убедиться, что ОС действительно делает это, используя низкоуровневые API, особенно если ваша рабочая нагрузка является скачкообразной (и, следовательно, может быть не простой для планировщика), или, как x264, вы обычно запускаете больше потоков, чем имеется логических ядер, чтобы ядра оставались занятыми, когда один поток временно перестает выполнять работу. - person Peter Cordes; 20.07.2021
comment
Хм, я предполагаю, что решение о том, сколько потоков запустить, может зависеть от сочетания ядер и от того, насколько замедление работы некоторых потоков влияет на вашу стратегию распараллеливания. Однако это не основной вопрос потока, а просто перечисление ядер по типу и определение их относительной производительности для вашей рабочей нагрузки. - person Peter Cordes; 20.07.2021
comment
Если высокопроизводительным ядрам требуется больше наноджоулей энергии для каждой выполненной единицы работы, перенос ресурсоемких заданий на ядра с более высокой производительностью может позволить выполнять задания быстрее, но за счет увеличения потребления батареи. Какой курс действий лучше для пользователя, может зависеть от того, сколько времени пройдет, прежде чем пользователь получит доступ к средству зарядки — что-то, что пользователь может знать и, возможно, сможет сообщить приложению, но ОС, вероятно, не будет этого делать. знать,. - person supercat; 20.07.2021
comment
Учитывая сложность задачи и системы, я бы определил успешную оптимизацию как потерю менее 20% производительности. - person Eric Duminil; 20.07.2021
comment
@supercat: это сложнее (например, для задач, связанных с пользовательскими интерфейсами, требуется задержка, а не пропускная способность, простаивающие ЦП требуют больше времени для пробуждения, загрузка от других процессов не известна ни одному процессу, некоторая работа может быть выполнена заранее в фоновом режиме, чтобы ускорить работу, чувствительную к производительности, иногда управление питанием означает управление температурой или шумом вентилятора, гиперпоточность сама по себе создает дополнительное медленное ядро ​​​​по сравнению с компромиссами с быстрым ядром, ...). Я также предположил бы, что пользователь предпочел бы сообщить ОС, сколько времени до зарядки (чем для каждого приложения или службы), и ОС может предсказать на основе прошлых моделей использования. - person Brendan; 20.07.2021
comment
С чипами M1, которые имеют ядра с низкой и высокой производительностью и не поддерживают гиперпоточность, обычно лучше всего использовать ровно столько потоков, сколько ядер, и запускать другой поток, когда он завершится. ОС перемещает потоки между ядрами, чтобы убедиться, что каждое из них может выполнять одинаковый объем работы. - person gnasher729; 20.07.2021
comment
вы остались без контроля над подсказками производительности, которые необходимы - не совсем так. Он не был стандартизирован из-за того, насколько подсказки различаются в зависимости от системы, но вы можете использовать системные вызовы с помощью std::thread::native_handle(). - person val is still with Monica; 20.07.2021
comment
Перестаньте слушать этих дураков. =› Можем ли мы избежать преувеличений? ОС имеет общие эвристики, которые в целом хороши, но в особых случаях недостаточны или неудобны. Типичными примерами являются случаи использования, когда задержка имеет значение, и в этом случае резервирование эксклюзивного использования определенного количества ядер и привязка потоков к этим ядрам работает намного лучше, чем надежда на то, что ОС позволит вам достичь вашей цели. И да, это требует выхода за пределы С++, стандарт там ничего не предлагает. - person Matthieu M.; 20.07.2021
comment
+1 Если время так критично, вам, вероятно, нужна RTOS. Попытка перехитрить планировщик ОС — пустая затея. - person J...; 20.07.2021
comment
Трата времени на неважную работу, в то время как более важная работа ждет, безусловно, возможна в современных системах, если программист не будет осторожен... подробности в инверсии приоритетов Google. - person Jeremy Friesner; 21.07.2021
comment
@ gnasher729: Использование столько потоков, сколько есть процессоров, идеально подходит для смущающей параллельной работы. Тем не менее, почти ничто не смущает параллелей (особенно на смартфонах и настольных системах). Часто лучше/проще разделить работу по логическим границам (например, может быть поток с высоким приоритетом для пользовательского интерфейса; поток с более высоким или низким приоритетом в зависимости от того, активна ли вкладка, приоритетный поток для каждой вкладки браузера, несколько потоков с низким приоритетом для предварительной выборки и проверка обновлений/изменений, ...). Если большинство потоков проводят большую часть своего времени в ожидании, то количество процессоров не имеет большого значения. - person Brendan; 21.07.2021
comment
@valisstillwithMonica: Да. Это похоже на хорошую std::thread абстракцию, которую вы можете использовать для написания красивого чистого переносимого кода и избежания специфичной для платформы смоляной ямы, а затем вы смотрите и понимаете, что это всего лишь поверхностный слой краски, скрывающий std::thread::native_handle() трамплин для прыжков, чтобы сделать его немного более раздражающим. нырнуть с головой в специфическую для платформы битумную яму. - person Brendan; 21.07.2021
comment
Современные системы не позволяют тратить процессорное время на неважную работу, в то время как ожидание более важной работы является преувеличением достигнутого прогресса. Это слишком просто и обычно, чтобы в конечном итоге время процессора ужасно тратилось впустую на ожидание некоторых входящих данных (нажатие клавиши, символ на последовательном порту) или в следующую секунду. Но для того, чтобы сделать что-то в реальном времени и подкованное на процессоре, обычно требуются навыки программирования, если только нет фреймворка, который выполнял эту тяжелую работу. Это должно быть сделано и можно быть сделано с помощью правильных методов. - person fgrieu; 21.07.2021
comment
Однако почти ничего не смущающе параллельно. Я бы не согласился. Я думаю, что эти случаи просто менее заметны, потому что они решаются тривиально, но я постоянно сталкиваюсь с досадно параллельными случаями, особенно с параллелизмом данных. Я работаю в области биоинформатики, поэтому наиболее очевидным и распространенным является анализ каждой хромосомы, но я также видел его во многих негеномных проблемах. И мы все еще работаем над общими системами (и да, отдельные узлы могут быть общими)... они не исчезли после 60-х годов для ученых только потому, что они исчезли для персональных компьютеров. - person ttbek; 21.07.2021
comment
@ttbek: я пишу этот комментарий, используя Chrome (эклектичная смесь потоков), в то время как в ОС (Windows) около 60 процессов ждут, когда что-то произойдет. Это может быть более 200 потоков, и ни один из них не будет смущающе параллельным. Вскоре я запущу Eclipse IDE (еще одна эклектичная смесь потоков) и соберу свой проект (make, mingw). В конце концов я сделаю перерыв и сыграю в игру. Ни одна из игр, которые у меня когда-либо были, не отличается постыдной параллельностью (за исключением работы, выполняемой графическим процессором, а не потоками). Трудно угадать, сколько лет прошло с тех пор, как процессоры на любом из моих компьютеров делали что-то постыдно параллельное. - person Brendan; 22.07.2021
comment
@ttbek: я думаю, что почти все нормальные люди / пользователи такие (все смартфоны, ноутбуки, рабочие станции и большинство серверов); и становится все хуже (вещи, которые раньше использовали смущающе параллельные потоки, вместо этого смещаются / перешли на GPGPU). Это похоже на смущающе параллельные потоки, которые почти не существуют за пределами некоторых редких/специализированных ниш (например, HPC, где у вас есть несколько компьютеров/узлов, работающих над одной и той же проблемой), которые просто включают вашу редкую/специализированную нишу. - person Brendan; 22.07.2021
comment
@ Брендан Я бы сказал, что это в основном различие между потребителями и серверными системами. - person Voo; 22.07.2021
comment
@Voo: Hrrm - что за сервер (HTTP, mysql, множество маленьких виртуальных машин, делающих разные вещи, ...)? - person Brendan; 22.07.2021

Есть ли способ запросить свойства std::thread и указать, на каких ядрах они будут работать в C++?

Нет. Для этого в C++ нет стандартного API.

API-интерфейсы для конкретных платформ имеют возможность указывать конкретное логическое ядро ​​(или набор таких ядер) для программного потока. Например, в GNU есть pthread_setaffinity_np.

Обратите внимание, что это позволяет вам указать ядро ​​​​1 для вашего потока, но это не обязательно поможет получить ядро ​​​​производительности, если вы не знаете, какое это ядро. Чтобы понять это, вам может потребоваться перейти на уровень ниже уровня ОС и перейти к программированию сборки для конкретного процессора. Насколько я понимаю, в случае с Intel вы должны использовать расширенный интерфейс аппаратной обратной связи.

person eerorika    schedule 19.07.2021
comment
Эта функция выглядит полезной для запроса тактовой частоты для заданного идентификатор потока. - person Patrick Roberts; 19.07.2021
comment
@PatrickRoberts Учитывая, что процессоры обычно снижают тактовые частоты в режиме ожидания и повышают их, когда заняты, может возникнуть необходимость нагрузить ядро ​​​​при вызове функции, чтобы определить, какое ядро ​​​​будет максимальным. - person eerorika; 19.07.2021
comment
@PatrickRoberts: Также имейте в виду, что не только часы, но и узкая ширина конвейера (и выполнение по порядку на некоторых чипах ARM big.LITTLE или очень ограниченный размер окна выполнения OoO на Intel Gracemont по сравнению с Golden Cove) делают высокие -эффективные ядра медленнее, чем высокопроизводительные ядра. Но да, вы могли бы провести микротестирование или запросить часы после нескольких мс прогрева с потоком, прикрепленным к ядру, чтобы определить, какие ядра есть какие. Пока вы не предполагаете, что тактовая частота является коэффициентом производительности, все в порядке, это просто показатель соотношения медленных и быстрых ядер. - person Peter Cordes; 19.07.2021
comment
instlatx64.atw.hu содержит списки будущих процессоров Alder Lake, но пока нет ссылок на фактические выходные данные CPUID, поэтому IDK, что CPUID скажет о семействе/модели, независимо от того, будет ли это однородным по ядрам или нет. Они ссылаются на review.coreboot.org/c/coreboot/+/49629/3/src/soc/intel/common/, который имеет один номер в качестве кода CPUID для CPUID_ALDERLAKE_M_A0, но coreboot может быть только запуск CPUID на загрузочном ядре, что может означать, что ему не нужно знать/распознавать CPUID для других ядер. - person Peter Cordes; 19.07.2021
comment
@PeterCordes Если вы можете прикрепить свой поток, почему бы просто не прикрепить поток непосредственно к большому или маленькому ядру? Я не знаю насчет iOS, но в Linux есть системные вызовы для определения того, какое ядро ​​есть какое... - person Aron; 20.07.2021
comment
@Арон: Почему бы и нет? Потому что тогда ваш поток вообще не запустится, если все большие ядра заняты, но есть маленькие простаивающие ядра. Это может быть или не быть желательным. См. ответ Брендана. Но да, хорошо, что сходство потоков может принимать набор ядер, а не одно, поэтому вы можете привязать какой-то поток к любому большому ядру. Однако это требует, чтобы вы уже определились, какой номер ядра какой, и это реальный вопрос, потому что для этого также нет переносимого API. (Если они есть, они даже менее переносимы, чем POSIX (pthreads), и могут потребовать таблицы сведений о ЦП) - person Peter Cordes; 20.07.2021
comment
@PeterCordes, потому что в каждой архитектуре есть многоядерный HMP... - person Aron; 20.07.2021
comment
Вам не нужно опускаться ниже уровня ОС. У ОС должен быть способ сообщить вам об этом. Чуть ниже стандартного уровня С++. - person user253751; 20.07.2021
comment
@Aron there are linux syscalls for working out which core is which Какой системный вызов? Я добавлю это к ответу. - person eerorika; 20.07.2021

Нет, в стандартной библиотеке C++ нет прямого способа запросить подтип ЦП или указать, что вы хотите, чтобы поток выполнялся на определенном ЦП.

Но std::threadjthread) имеет .native_handle(), что на большинстве платформ будет позволь тебе сделать это.

Если вы знаете реализацию библиотеки потоков для вашего std::thread, вы можете использовать native_handle() для доступа к базовым примитивам, а затем использовать базовую библиотеку потоков для выполнения такой низкоуровневой работы.

Конечно, это будет полностью непереносимо.

person Yakk - Adam Nevraumont    schedule 19.07.2021

iPhone, iPad и более новые Mac имеют высокопроизводительные и низкопроизводительные ядра по определенной причине. Ядра с низкой производительностью позволяют выполнять разумный объем работы, используя минимально возможное количество энергии, что увеличивает срок службы батареи устройства. Эти дополнительные ядра нужны не только для развлечения; если вы попытаетесь обойти их, вы можете получить гораздо худший опыт для пользователя.

Если вы используете стандартную библиотеку C++ для запуска нескольких потоков, операционная система обнаружит, что вы делаете, и будет действовать соответствующим образом. Если ваша задача занимает всего 10 мс на высокопроизводительном ядре, она будет перемещена на низкопроизводительное ядро; это достаточно быстро и экономит заряд батареи. Если у вас есть несколько потоков, использующих 100% процессорного времени, высокопроизводительные ядра будут использоваться автоматически (а также низкопроизводительные ядра). Если батарея разряжается, устройство может переключиться на все ядра с низкой производительностью, которые будут выполнять больше работы за счет имеющегося у вас заряда батареи.

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

person gnasher729    schedule 20.07.2021

Вы не можете выбрать ядро, на котором будет физически запланирован запуск потока, используя std::thread. Подробнее см. здесь. Я бы предложил использовать такую ​​структуру, как OpenMP, MPI, или вам придется копаться в родных API Mac OS, чтобы выберите ядро, на котором будет выполняться ваш поток.

person lmeninato    schedule 19.07.2021
comment
Как OpenMP или MPI могут повлиять на планирование потоков? - person John Laxson; 20.07.2021
comment
С обоими вы можете контролировать, какая задача будет выполняться на каком физическом ядре, хотя на одной машине я бы не стал использовать MPI. - person lmeninato; 20.07.2021