如何区分 C++ 中的高性能和低性能内核/线程?
Posted
技术标签:
【中文标题】如何区分 C++ 中的高性能和低性能内核/线程?【英文标题】:How can I distinguish between high- and low-performance cores/threads in C++? 【发布时间】:2021-09-27 08:23:16 【问题描述】:在谈论多线程时,通常似乎线程被视为平等 - 与主线程相同,但在其旁边运行。
但是,在某些新处理器上,例如 Apple "M" series 和即将推出的 Intel Alder Lake 系列,并非所有线程都具有同样的性能,因为这些芯片具有独立的高性能内核和高效率但速度较慢的内核。
并不是说还没有超线程之类的东西,但这似乎对性能有更大的影响。
有没有办法查询 std::thread
的属性并强制它们在 C++ 中运行哪些内核?
【问题讨论】:
线程未绑定到芯片。操作系统根据需要来回移动线程 @MooingDuck 对于 M1 和 macOS,很可能会询问操作系统它应该在高效核心上运行更可取的线程。而且您理论上可以锁定(取决于操作系统和cpu)将进程/线程锁定到单个cpu。这通常是为虚拟主机做一台服务器。 您将需要使用 OS API 将线程专用于特定内核。无法保证线程将在不同的内核上运行或仅在内核上执行。线程可以在单核(多核)系统上运行,就像其他任务一样。 一个可能存在类似问题的现有硬件开发是numa。 NUMA 感知代码必须处理分配给不同处理器的线程(以最好地利用不同的内存访问速度)的方式可能很有见地。 你知道,内核是为了处理多任务而发明的。如果你认为你可以比内核更好地处理任务,那你就错了。您可以使用一些操作系统级别的 API 来控制您的线程,在 linux 上它们是pthread
API。
【参考方案1】:
有什么方法可以查询 std::thread 的属性并强制它们在 C++ 中运行在哪些内核上?
没有。 C++ 中没有标准的 API。
特定于平台的 API 确实能够为软件线程指定特定的逻辑核心(或一组此类核心)。例如,GNU 有pthread_setaffinity_np
。
请注意,这允许您为线程指定“核心 1”,但这并不一定有助于获得“性能”核心,除非您知道那是哪个核心。要弄清楚这一点,您可能需要进入操作系统级别以下并进入特定于 CPU 的汇编编程。据我了解,就英特尔而言,您将使用增强型硬件反馈接口。
【讨论】:
This function 看起来对于查询给定线程 ID 的时钟速度很有用。 @PatrickRoberts 鉴于 CPU 通常在空闲时会降低时钟频率,而在忙碌时会加速,因此可能需要在调用函数时对内核施加压力以找出哪个内核将提升最高。 @PatrickRoberts:还请记住,这不仅仅是时钟,它是狭窄的管道宽度(以及一些 ARM big.LITTLE 芯片上的按顺序执行,或者 Intel Gracemont vs 上非常有限的 OoO 执行窗口大小. Golden Cove) 使高效核心比高性能核心慢。但是,是的,您可以进行微基准测试,或者在几毫秒的预热后查询时钟,将线程固定到内核,以检测哪些内核是哪个。只要您不假设时钟比率是性能比率,那很好,只是慢速与快速核心的指标。 instlatx64.atw.hu 列出了即将推出的 Alder Lake CPU,但还没有指向实际 CPUID 输出的链接,因此请 IDK CPUID 对系列/型号的说明,无论这是否在内核之间是统一的。他们确实链接了review.coreboot.org/c/coreboot/+/49629/3/src/soc/intel/common/…,它有一个数字作为CPUID_ALDERLAKE_M_A0
的 CPUID 代码,但 coreboot 可能只在引导核心上运行 CPUID,这可能意味着它不需要知道/识别其他核心的 CPUID .
@PeterCordes 如果您可以固定您的线程,为什么不直接将线程固定到大核心或小核心?我不了解 ios,但是有 linux 系统调用可以确定哪个核心是哪个......【参考方案2】:
您无法使用std::thread
选择线程将在物理上安排运行的内核。有关更多信息,请参阅here。我建议使用OpenMP、MPI 之类的框架,或者您将深入了解select the core for your thread to execute on. 的原生 Mac OS API
【讨论】:
OpenMP 或 MPI 如何影响线程调度? 两者都可以控制在什么物理内核上执行什么任务,尽管在单台机器上我不会使用 MPI。【参考方案3】:不,C++ 标准库无法直接查询 CPU 的子类型,或您希望线程在特定 CPU 上运行的状态。
但std::thread
(和jthread
)确实有.native_handle()
,在大多数平台上都允许你这样做。
如果您知道std::thread
的线程库实现,您可以使用native_handle()
获取底层原语,然后使用底层线程库来完成这种低级工作。
当然,这将是完全不可移植的。
【讨论】:
【参考方案4】:如何区分 C++ 中的高性能和低性能内核/线程?
请理解,“线程”是硬件功能的抽象,而您无法控制的东西(操作系统、内核的调度程序)负责创建和管理这个抽象。 “重要性”和性能提示是该抽象的一部分(通常以线程优先级的形式呈现)。
任何破坏“线程”抽象的尝试(例如,确定内核是低性能还是高性能内核)都是错误的。例如。操作系统可能会在您发现自己在高性能内核上运行后立即将您的线程更改为低性能内核,从而导致您假设自己在高性能内核上,而实际上并非如此。
即使将您的线程固定到特定核心(希望它始终使用高性能核心)也可能/将会适得其反(导致您完成的工作减少,因为您阻止自己使用“比没有快”的低性能核心,当高性能核心忙于其他工作时)。
最大的问题是 C++ 在操作系统提供的“可能更好”的抽象之上创建了一个更糟糕的抽象 (std::thread
)。具体来说,使用std::thread
无法设置、修改或获取线程优先级;因此,您无法控制做出良好的“负载、性能和电源管理”决策所必需的“性能提示”(对于操作系统、调度程序)。
在谈到多线程时,通常看起来线程被视为平等
人们通常认为我们仍在使用 1960 年代的分时系统。别再听这些蠢货了。 现代系统不允许 CPU 时间浪费在不重要的工作上,而等待更重要的工作。有效使用线程优先级是一项基本性能要求。 其他一切(“负载、性能和电源管理”决策)必然超出您的控制范围(在“线程”抽象的另一端,您'正在使用)。
【讨论】:
是的,TL:DR 你通常不需要区分。如果它们没有从那里开始,操作系统已经将您的计算密集型线程迁移到高性能内核上,如果它们有足够的空闲。您可能希望验证操作系统实际上正在使用低级 API 执行此操作,特别是如果您的工作负载是突发性的(因此对于调度程序来说可能并不简单),或者像 x264 您通常启动线程数多于逻辑核心数,以便在一个线程暂时无法工作时保持核心忙碌。 @supercat:比这更复杂(例如,处理用户界面的任务需要延迟而不是吞吐量,空闲 CPU/s 需要更长的时间才能唤醒,任何一个进程都不知道来自其他进程的负载, 一些工作可以在后台“预先完成”以使对性能敏感的工作更快,有时“电源管理”意味着温度或风扇噪音管理,超线程会创建额外的“自身慢核心与共享快核心”妥协, ...)。我还假设用户宁愿告诉操作系统在充电前多久(而不是每个应用程序或服务),并且操作系统可以根据过去的使用模式进行预测。 “您无法控制必要的“性能提示”” - 不完全正确。它没有标准化,因为每个系统有多少提示不同,但您完全可以在std::thread::native_handle()
的帮助下使用特定于系统的调用。
别再听这些傻瓜的声音了。 => 我们可以避免在这里夸张吗?操作系统具有通用启发式算法,通常很好,但在特殊情况下不足或不方便。典型的例子是延迟很重要的用例,在这种情况下,保留对特定数量的核心的独占使用并将线程固定到这些核心工作得更好,“希望”操作系统将允许您达到目标。是的,这需要超越 C++,该标准没有提供任何内容。
“现代系统不允许将 CPU 时间浪费在不重要的工作上,而等待更重要的工作”是夸大了所取得的进展。这太容易了,也太常见了,最终导致 CPU 时间浪费在等待一些传入数据(按键、串行端口上的字符)或下一秒。但它通常仍然需要编程技能来制作实时和精通 CPU 的东西,除非有一个框架可以完成这项艰苦的工作。这应该完成,并且可以用正确的技术完成。【参考方案5】:
iPhone、iPad 和较新的 Mac 具有高性能和低性能内核是有原因的。低性能内核允许在使用尽可能少的能量的同时完成一些合理的工作,从而使设备的电池寿命更长。这些额外的核心不仅仅是为了好玩。如果你试图绕过它们,最终可能会给用户带来更糟糕的体验。
如果您使用 C++ 标准库来运行多个线程,操作系统会检测到您在做什么,并采取相应的行动。如果你的任务在高性能核心上只需要 10ms,它会被转移到低性能核心上;它足够快并且可以节省电池寿命。如果您有多个线程使用 100% 的 CPU 时间,则将自动使用高性能内核(以及低性能内核)。如果您的电池电量不足,设备可以切换到所有低性能内核,这将完成更多工作您的电池电量。
你应该认真考虑你想做什么。你应该把用户的需求放在你感知的需求之前。除此之外,Apple 建议为您的线程分配特定于操作系统的优先级,如果您做得对,这会改善行为。赋予线程最高优先级以便获得更好的基准测试结果通常不是“正确”。
【讨论】:
以上是关于如何区分 C++ 中的高性能和低性能内核/线程?的主要内容,如果未能解决你的问题,请参考以下文章