41

在学习“汇编语言”(在 x86 架构的 linux 中,使用 GNU 作为汇编器)的同时,一个令人惊叹的时刻是使用系统调用的可能性。当您的程序在用户空间中运行时,这些系统调用非常方便,有时甚至是必要的。
然而,系统调用在性能方面相当昂贵,因为它们需要中断(当然还有系统调用),这意味着必须从用户空间中的当前活动程序到内核空间中运行的系统进行上下文切换。

我想说的是:我目前正在实现一个编译器(用于大学项目),我想添加的额外功能之一是对多线程代码的支持,以提高编译程序的性能. 因为一些多线程代码将由编译器自己自动生成,这几乎可以保证其中也会有非常少量的多线程代码。为了获得性能上的胜利,我必须确保使用线程会实现这一点。

然而,我担心的是,为了使用线程,我必须进行系统调用和必要的中断。因此,微小的(自动生成的)线程将受到进行这些系统调用所需时间的高度影响,甚至可能导致性能损失......

因此,我的问题是双重的(下面有一个额外的奖励问题):

  • 是否可以编写可以同时在多个内核上同时运行多个线程而无需系统调用的汇编代码
  • 如果我有非常小的线程(就像线程的总执行时间一样小),性能损失,或者根本不值得付出努力,我会获得性能提升吗?

我的猜测是,如果没有系统调用,多线程汇编代码是不可能的。即使是这种情况,您是否有建议(甚至更好:一些真实的代码)来尽可能高效地实现线程?

4

7 回答 7

28

简短的回答是你不能。当您编写汇编代码时,它会在一个且仅一个逻辑(即硬件)线程上按顺序(或使用分支)运行。如果您希望某些代码在另一个逻辑线程上执行(无论是在同一个内核上,在同一个 CPU 上的不同内核上,甚至在不同 CPU 上),您需要让操作系统设置另一个线程的指令指针 ( CS:EIP) 指向您要运行的代码。这意味着使用系统调用来让操作系统做你想做的事。

用户线程不会为您提供所需的线程支持,因为它们都运行在同一个硬件线程上。

编辑:将 Ira Baxter 的答案与Parlanse结合起来。如果您确保您的程序在开始时每个逻辑线程中都有一个线程运行,那么您可以构建自己的调度程序而不依赖于操作系统。无论哪种方式,您都需要一个调度程序来处理从一个线程到另一个线程的跳跃。在调用调度程序之间,没有特殊的汇编指令来处理多线程。调度器本身不能依赖任何特殊的程序集,而是依赖于每个线程中调度器部分之间的约定。

无论哪种方式,无论您是否使用操作系统,您仍然必须依赖一些调度程序来处理跨线程执行。

于 2009-06-15T08:45:44.073 回答
17

“医生,医生,我这样做的时候很痛”。医生:“不要那样做”。

简短的回答是您可以在不调用昂贵的操作系统任务管理原语的情况下进行多线程编程。只需忽略操作系统进行线程调度操作。这意味着您必须编写自己的线程调度程序,并且永远不会将控制权交还给操作系统。(而且你必须比相当聪明的操作系统家伙更聪明地处理你的线程开销)。我们之所以选择这种方法,正是因为 Windows 进程/线程/光纤调用都太昂贵,无法支持数百条指令的计算粒度。

我们的 PARLANSE 编程语言是一种并行编程语言:参见http://www.semdesigns.com/Products/Parlanse/index.html

PARLANSE 在 Windows 下运行,提供并行“grains”作为抽象并行结构,并通过高度调整的手写调度程序和 PARLANSE 编译器生成的调度代码的组合来调度此类grain,该编译器考虑grain 的上下文以最小化调度高架。例如,编译器确保grain 的寄存器在可能需要调度(例如,“等待”)时不包含任何信息,因此调度程序代码只需要保存PC 和SP。事实上,调度程序代码通常根本无法控制。分叉的grain 只存储分叉的PC 和SP,切换到编译器预分配的堆栈并跳转到grain 代码。谷物的完成将重新启动分叉器。

通常有一个联锁来同步颗粒,由编译器使用本机 LOCK DEC 指令实现,该指令实现相当于计数信号量的内容。应用程序可以在逻辑上分叉数百万个谷子;如果工作队列足够长,那么调度程序会限制父颗粒产生更多的工作,因此更多的工作将无济于事。调度程序实现了工作窃取,以允许工作匮乏的 CPU 从相邻的 CPU 工作队列中获取准备好的颗粒。这已实现处理多达 32 个 CPU;但我们有点担心 x86 供应商实际上可能会在未来几年内使用更多的东西!

PARLANSE 是一门成熟的语言;我们从 1997 年开始使用它,并在其中实现了数百万行并行应用程序。

于 2009-06-16T04:48:33.080 回答
8

实现用户模式线程。

历史上,线程模型被概括为 N:M,也就是说 N 个用户模式线程运行在 M 个内核模型线程上。现代使用是 1:1,但并非总是如此,也不必如此。

您可以自由地在单个内核线程中维护任意数量的用户模式线程。只是你有责任在它们之间进行足够频繁的切换,以使它们看起来是并发的。您的线程当然是合作的,而不是先发制人的;您基本上在自己的代码中分散了 yield() 调用,以确保发生定期切换。

于 2009-04-03T17:38:11.607 回答
5

如果你想获得性能,你将不得不利用内核线程。只有内核可以帮助您让代码在多个 CPU 内核上同时运行。除非您的程序受 I/O 限制(或执行其他阻塞操作),否则执行用户模式协作多线程(也称为Fiber)不会为您带来任何性能。您将只是执行额外的上下文切换,但您的真实线程正在运行的一个 CPU 仍将以 100% 的方式运行。

系统调用变得更快了。现代 CPU 支持该sysenter指令,该指令比旧int指令快得多。另请参阅这篇文章,了解 Linux 如何以最快的方式进行系统调用。

确保自动生成的多线程让线程运行足够长的时间以获得性能。不要试图并行化一小段代码,你只会浪费时间生成和加入线程。还要注意内存效应(尽管这些效应更难测量和预测)——如果多个线程正在访问独立的数据集,由于缓存一致性问题,它们的运行速度将比它们重复访问相同数据的速度快得多。

于 2009-04-10T14:49:37.293 回答
3

首先,您应该学习如何在 C 中使用线程(pthreads、POSIX theads)。在 GNU/Linux 上,您可能想要使用 POSIX 线程或 GLib 线程。然后你可以简单地从汇编代码中调用 C。

这里有一些提示:

于 2009-04-10T14:39:15.400 回答
3

系统调用现在并没有那么慢,使用syscallsysenter代替int. 不过,只有在创建或销毁线程时才会有开销。一旦它们运行,就没有系统调用。用户模式线程不会真正帮助您,因为它们只在一个内核上运行。

于 2009-04-10T14:44:59.973 回答
3

现在有点晚了,但我自己对这种话题很感兴趣。事实上,除了并行化/性能之外,没有什么特别需要内核干预的线程。

强制 BLUF

Q1:不需要。至少需要初始系统调用才能跨各种 CPU 内核/超线程创建多个内核线程。

Q2:视情况而定。如果您创建/销毁执行微小操作的线程,那么您就是在浪费资源(线程创建过程将大大超过踏板在退出之前使用的时间)。如果您创建 N 个线程(其中 N 是系统上的 ~# of cores/hyper-threads)并重新分配它们,那么答案可能是肯定的,具体取决于您的实现。

Q3:如果您提前知道精确的操作排序方法,您可以优化操作。具体来说,您可以创建相当于 ROP 链的内容(或前向调用链,但这实际上可能最终实现起来更复杂)。这个 ROP 链(由线程执行)将连续执行“ret”指令(到它自己的堆栈),其中该堆栈被连续添加(或在它滚动到开头的情况下添加)。在这样一个(奇怪的!)模型中,调度程序保留一个指向每个线程的“ROP 链末端”的指针,并向其写入新值,从而代码在内存中循环执行函数代码,最终导致 ret 指令。同样,这是一个奇怪的模型,但仍然很有趣。

在我价值 2 美分的内容上。

我最近通过管理各种堆栈区域(通过 mmap 创建)并维护一个专用区域来存储“线程”的控制/个性化信息,创建了在纯汇编中有效地作为线程运行的东西。尽管我没有这样设计,但可以通过 mmap 创建一个大块内存,我将其细分为每个线程的“私有”区域。因此,只需要一个系统调用(尽管它们之间的保护页面很聪明,但它们需要额外的系统调用)。

此实现仅使用进程产生时创建的基本内核线程,并且在整个程序执行过程中只有一个用户模式线程。程序通过内部控制结构更新自己的状态并自行安排。I/O 等在可能的情况下通过阻塞选项处理(以降低复杂性),但这不是严格要求的。当然,我使用了互斥锁和信号量。

要实现这个系统(完全在用户空间中,如果需要也可以通过非 root 访问),需要以下内容:

线程归结为的概念: 堆栈操作的堆栈(有点自我解释和明显) 一组要执行的指令(也很明显) 一小块内存,用于保存单个寄存器内容

调度程序归结为:调度程序指定的有序列表(通常是优先级)中一系列线程的管理器(请注意,进程从不实际执行,只是它们的线程执行)。

线程上下文切换器:注入到代码的各个部分(我通常将它们放在重型函数的末尾)的宏,大致相当于“线程屈服”,它保存线程的状态并加载另一个线程的状态。

因此,确实有可能(完全在汇编中并且除了初始 mmap 和 mprotect 之外没有系统调用)在非根进程中创建用户模式线程状构造。

我只添加了这个答案,因为您特别提到了 x86 程序集,而这个答案完全是通过一个完全用 x86 程序集编写的自包含程序得出的,该程序实现了最小化系统调用和最小化系统端线程的目标(减去多核功能)高架。

于 2016-10-14T01:47:37.343 回答