如何区分C++中的高性能和低性能内核/线程?

在谈论多线程时,线程似乎通常被视为平等——与主线程相同,但在它旁边运行。

然而,在某些新处理器上,例如Apple M1芯片和即将推出的 Intel Alder Lake系列,并非所有线程的性能都与这些芯片的性能相同,因为这些芯片具有独立的高性能内核和高效、速度较慢的内核。

这并不是说还没有诸如超线程之类的东西,但这似乎对性能有更大的影响。

有没有办法查询std::thread的属性并强制它们在 C++ 中运行的核心?

回答

如何区分 C++ 中的高性能和低性能内核/线程?

请理解,“线程”是硬件功能的抽象,并且超出您控制范围的东西(操作系统、内核的调度程序)负责创建和管理此抽象。“重要性”和性能提示是该抽象的一部分(通常以线程优先级的形式呈现)。

任何打破“线程”抽象的尝试(例如确定内核是低性能还是高性能内核)都是错误的。例如,在您发现自己在高性能内核上运行后,操作系统可能会立即将您的线程更改为低性能内核,从而导致您假设自己在高性能内核上运行,而实际上并非如此。

即使将您的线程固定到特定的内核(希望它始终使用高性能内核)也可能/会适得其反(导致您完成更少的工作,因为您阻止自己使用“比没有快“当高性能内核忙于做其他工作时,低性能内核)。

最大的问题是 C++std::thread在操作系统提供的“可能更好”的抽象之上创建了一个更糟糕的抽象 ( )。具体来说,无法使用std::thread;来设置、修改或获取线程优先级。因此,您无法控制(对于操作系统、调度程序)做出良好“负载与性能与电源管理”决策所必需的“性能提示”。

在谈论多线程时,通常似乎线程被视为平等

人们通常认为我们仍在使用 1960 年代的分时系统。别再听这些傻子了。现代系统不允许将 CPU 时间浪费在不重要的工作上,而更重要的工作在等待。有效使用线程优先级是一项基本的性能要求。其他一切(“负载与性能与电源管理”的决定)必然超出您的控制范围(在您使用的“线程”抽象的另一侧)。

  • _别再听这些傻瓜了。_ => 我们能不能避免夸大其词?操作系统有_generic heuristics_,一般都很好,但在特殊情况下是不够的,或者不方便。典型的例子是延迟很重要的用例,在这种情况下,保留一定数量的内核的独占使用并将线程固定到这些内核上效果更好,而不是“希望”操作系统允许您达到目标。是的,这需要超越 C++,标准没有提供任何内容。
  • “您无法控制必要的“性能提示”” - 不完全正确。它没有标准化,因为每个系统的提示有多少不同,但是您完全可以在 `std::thread::native_handle()` 的帮助下使用特定于系统的调用。
  • 是的,TL:DR 你通常不需要区分。操作系统已经将您的计算密集型线程迁移到高性能核心上,如果它们不在那里启动,如果有足够多的空闲线程。您可能想要*验证*操作系统实际上是通过使用低级 API 来执行此操作的,特别是如果您的工作负载是突发的(因此对于调度程序来说可能并不简单),或者像 x264 一样,您通常启动的线程比实际数量多逻辑核心,当一个线程暂时没有工作要做时保持核心忙碌。
  • @supercat:它比这更复杂(例如,处理用户界面的任务需要延迟而不是吞吐量,空闲的 CPU/s 需要更长的时间来唤醒,任何一个进程都不知道来自其他进程的负载,一些工作可以是“预在后台完成”以使性能敏感的工作更快,有时“电源管理”意味着温度或风扇噪音管理,超线程会产生额外的“慢速核心与共享快速核心”的妥协,......)。我还假设用户更愿意在充电前告诉操作系统多长时间(而不是每个应用程序或服务),操作系统可以根据过去的使用模式进行预测。
  • “现代系统不允许将 CPU 时间浪费在不重要的工作上,而更重要的工作在等待”是对取得的进展的夸大。最终导致 CPU 时间在等待某些传入数据(按键、串行端口上的字符)或下一秒时被严重浪费,这太容易了,而且很常见。但它通常仍然需要编程技能来制作实时和 CPU 熟练的东西,除非有一个框架可以完成这项艰苦的工作。这_应该_完成,并且_可以_用正确的技术完成。
  • 考虑到任务和系统的复杂程度,我将成功的优化定义为损失少于 20% 的性能。
  • 嗯,我想启动多少线程的决定可能取决于内核的组合,以及它对您的并行化策略的影响程度,以使某些线程运行得更慢。不过,这不是线程-> 核心问题,只是按类型枚举核心,并为您的工作负载找到它们的相对性能。
  • 如果高性能内核为完成的每个工作单元需要更多纳焦耳的能量,那么将 CPU 密集型作业迁移到更高性能的内核可能会使这些作业更快完成,但代价是电池消耗增加。哪种操作对用户更好可能取决于用户访问充电设施之前的时间——用户可能知道并且可能能够告诉应用程序,但操作系统可能不会知道,。
  • @ttbek:我正在使用 Chrome(一种不拘一格的线程混合)撰写此评论,而操作系统(Windows)有大约 60 个进程在等待事情发生。它可能有 200 多个线程,没有任何令人尴尬的并行。很快我将启动 Eclipse IDE(另一种不拘一格的线程组合)并构建我的项目(make、mingw)。最终我会休息一下并玩一个游戏。我玩过的游戏都没有令人尴尬的并行(不包括 GPU 完成的工作,而不是线程)。很难猜测自从我的任何计算机中的任何 CPU 执行任何令人尴尬的并行操作以来已有多少年了。
  • @ttbek:我猜几乎所有普通人/用户都是这样(智能手机、笔记本电脑、工作站和大多数服务器);而且情况越来越糟(过去使用“令人尴尬的并行线程”的东西正在转移/已经转移到 GPGPU)。就像“令人尴尬的并行线程”几乎不存在于一些罕见/专业领域(例如,您有多个计算机/节点处理同一问题的 HPC)之外,而这恰好包括您的罕见/专业领域。

回答

有什么方法可以查询 std::thread 的属性并强制它们在 C++ 中运行的核心?

不。在 C++ 中没有用于此的标准 API。

特定于平台的 API 确实能够为软件线程指定特定的逻辑核心(或一组此类核心)。例如,GNU 有pthread_setaffinity_np.

请注意,这允许您为线程指定“核心 1”,但这不一定有助于获得“性能”核心,除非您知道这是哪个核心。要弄清楚这一点,您可能需要低于操作系统级别并进行特定于 CPU 的汇编编程。根据我的理解,在英特尔的情况下,您将使用增强型硬件反馈接口。

  • @PatrickRoberts:还要记住,这不仅仅是时钟,它是窄的管道宽度(以及一些 ARM big.LITTLE 芯片上的有序执行,或者英特尔 Gracemont 与 Golden Cove 上非常有限的 OoO 执行窗口大小),这使得高- 效率核心比高性能核心慢。但是,是的,您可以进行微基准测试,或者在预热几毫秒后查询时钟,将线程固定到核心,以检测哪些核心是哪些。只要您不假设时钟比率是性能比率,那很好,只是慢核与快核的指标。
  • @PatrickRoberts 鉴于 CPU 通常在空闲时降低时钟并在忙碌时提升,因此可能需要在调用该函数时对内核施加压力以查找哪个内核将提升最高。
  • http://instlatx64.atw.hu/ 有即将推出的 Alder Lake CPU 的列表,但还没有指向实际 CPUID 输出的链接,因此 IDK 将说明 CPUID 对系列/型号的看法,无论这是否在内核之间统一。他们确实链接了 https://review.coreboot.org/c/coreboot/+/49629/3/src/soc/intel/common/block/include/intelblocks/mp_init.h#49,其中有一个数字作为 CPUID `CPUID_ALDERLAKE_M_A0` 的代码,但 coreboot 可能只在引导内核上运行 CPUID,这可能意味着它不需要知道/识别其他内核的 CPUID。

回答

不,C++ 标准库没有直接的方法来查询 CPU 的子类型,或声明您希望线程在特定 CPU 上运行。

但是std::thread(和jthread)确实有.native_handle(),在大多数平台上都可以让您这样做。

如果您知道您的 线程库实现std::thread,则可以使用它native_handle()来获取底层原语,然后使用底层线程库来完成这种低级工作。

当然,这将是完全不可移植的。


回答

iPhone、iPad 和较新的 Mac 具有高性能和低性能内核是有原因的。低性能内核允许在使用尽可能少的能量的同时完成一些合理的工作,从而使设备的电池持续更长时间。这些额外的内核不仅仅是为了好玩;如果你试图绕过它们,最终会给用户带来更糟糕的体验。

如果您使用 C++ 标准库来运行多个线程,操作系统将检测您在做什么,并采取相应的行动。如果你的任务在一个高性能核心上只需要 10ms,它就会被转移到一个低性能核心上;它足够快,可以节省电池寿命。如果您有多个线程使用 100% 的 CPU 时间,则将自动使用高性能内核(以及低性能内核)。如果您的电池电量不足,设备可以切换到所有低性能核心,这将使用您拥有的电池电量完成更多工作。

你真的应该考虑一下你想做什么。你应该把用户的需求放在你感知的需求之前。除此之外,Apple 建议为您的线程分配特定于操作系统的优先级,如果您做得对,这会改善行为。为线程赋予最高优先级以便获得更好的基准测试结果通常不是“做对了”。


回答

您不能使用std::thread. 请参阅此处了解更多信息。我建议使用OpenMP、MPI 之类的框架,否则您将深入研究本机 Mac OS API 来选择要在其上执行线程的核心。

  • OpenMP 或 MPI 如何与影响线程调度相关?

以上是如何区分C++中的高性能和低性能内核/线程?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>