CPU乱序效果测试程序

Posted

技术标签:

【中文标题】CPU乱序效果测试程序【英文标题】:Test program for CPU out of order effect 【发布时间】:2016-02-24 06:29:34 【问题描述】:

我写了一个多线程程序来演示英特尔处理器的乱序效果。该程序附在本文末尾。 预期的结果应该是当 x 被 handler1 打印为 42 或 0 时。但是,实际结果始终为 42,这意味着不会发生乱序效应。

我用命令“gcc -pthread -O0 out-of-order-test.c”编译了程序 我在 Intel IvyBridge 处理器 Intel(R) Xeon(R) CPU E5-1650 v2 上的 Ubuntu 12.04 LTS(Linux 内核 3.8.0-29-generic)上运行编译程序。

有谁知道我应该怎么做才能看到乱序效果?

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int f = 0, x = 0;

void* handler1(void *data)

    while (f == 0);
    // Memory fence required here
    printf("%d\n", x);


void* handler2(void *data)

    x = 42;
    // Memory fence required here
    f = 1;


int main(int argc, char argv[])

    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, handler1, NULL);
    pthread_create(&tid2, NULL, handler2, NULL);

    sleep(1);
    return 0;

【问题讨论】:

这不是乱序,而是竞争条件。 (顺便说一句,x86 是一种有序加载/存储架构。)。 @Olaf,感谢您的评论。但是,根据***.com/questions/7346893/…,x86 至少存在存储后加载问题。如果它对一个核心有数据依赖,我知道硬件会保持秩序。否则,乱序机制可能会在前一条指令之前执行下一条指令。 这里的乱序与指令执行无关。几乎不可能像(几乎)所有高端架构这样的多问题架构。这是关于加载/存储。但是你不会在这里利用你的 coe 来利用任何东西。并且可能不会使用任何 C 代码 - 至少不可靠。后者仅仅是因为您需要一个特定的指令序列,而在使用编译器时您将无法控制它。所以,潜入汇编程序并尝试。祝你好运。 您正在寻找preshing.com/20120515/memory-reordering-caught-in-the-act。 Jeff Preshing 的代码将演示 x86 上的 StoreLoad 重新排序(x86 上唯一可能的类型)。您永远不会在 x86 上看到 StoreStore 重新排序,因为它是强排序的。每个商店都有发布语义,每个加载都有获取语义。阅读 Preshing 博客上的其他帖子,了解这意味着什么。 在弱排序架构(如 ARM 或 PPC)上,您的代码可以观察 f=1 而没有 x=42,但您应该循环测试。只测试一次是极不可能找到任何东西的。在第二个线程启动之前,这两个商店可能都是全局可见的。 (无论如何,线程启动可能会在某个时候运行内存屏障指令!) 【参考方案1】:

您将竞态条件与乱序执行范例混合在一起。不幸的是,我很确定您不能“暴露”无序执行,因为它是明确设计和实现的,以保护您(正在运行的程序及其数据)免受其影响。

更具体地说:乱序执行完全发生在 CPU 的“内部”。乱序指令的结果不会直接发布到寄存器文件中,而是排队等候以保持顺序。 因此,即使指令本身是无序执行的(基于主要确保这些指令可以彼此独立运行的各种规则),它们的结果也总是按照外部观察者所期望的正确顺序重新排序.

您的程序所做的是:它尝试(非常粗略地)模拟一个竞争条件,您希望在其中看到f 的分配在x 之前完成同时,您希望在那个时刻恰好发生上下文切换并且您假设新线程将被安排在与另一个线程完全相同的 CPU 内核上。 然而,正如我在上面解释的那样——即使你很幸运地达到了所有列出的条件(在f 分配之后但在x 分配之前安排第二个线程并且有新的线程调度在同一个 CPU 内核上)——这本身就是一个极低概率的事件——即便如此,你真正暴露的只是潜在的竞争条件,而不是乱序执行。

很抱歉让您失望,但您的程序无法帮助您观察乱序执行效果。至少没有足够高的实用性。

您可以在此处阅读更多关于乱序执行的信息: http://courses.cs.washington.edu/courses/csep548/06au/lectures/introOOO.pdf

更新 经过一番思考,我认为您可以即时修改指令,以期暴露无序执行。但即便如此,我担心这种方法会失败,因为新的“更新”指令不会正确反映在 CPU 的管道中。我的意思是:CPU 很可能已经获取并解析了您将要修改的指令,因此将执行的内容将不再匹配内存字的内容(即使是 CPU 的 L1 缓存中的内容)。 但是,如果这项技术可以帮助您,则需要直接在 Assembly 中进行一些高级编程,并且需要您的代码以最高权限级别(环 0)运行。我建议在编写自修改代码时要格外小心,因为它有很大的潜在副作用。

【讨论】:

@Yephlck,非常感谢您的详细解释和指正!我现在明白你的意思了:CPU 将在其管道内进行无序处理,但是当它将结果暴露给外部时,它总是被重新排序为原始序列。基于这个事实,我认为我的程序将“永远不会”,而不是极小概率地暴露乱序效应,因为 f 总是在 x 被分配到 handler2() 之后被分配。我说的对吗?【参考方案2】:

请注意:以下仅解决 MEMORY 重新排序。据我所知,您无法观察到管道之外的乱序执行,因为这将构成 CPU 无法遵守其接口的故障。 (例如:你应该告诉英特尔,这将是一个错误)。具体来说,重新排序缓冲区和指令报废簿记必须出现故障。

根据Intel's documentation(特别是第 3A 卷,第 8.2.3.4 节):

Intel-64 内存排序模型允许将加载重新排序,并将较早的存储存储到不同的位置。

它还指定(我在总结,但所有这些都可以在 8.2.3 中的示例的第 8.2 节内存排序中获得)加载永远不会随着加载重新排序,存储永远不会随着存储重新排序,并且存储永远不会重新排序与较早的负载。这意味着在 Intel 64 中这些操作之间存在 隐式 栅栏(3 个弱类型)。

要观察内存重新排序,您只需要非常小心地实施该示例以实际观察效果。 Here is a link 到我所做的完整实现来证明这一点。 (我将在随附的帖子here 中跟进更多详细信息)。

基本上是第一个线程(示例中的 processor_0)执行此操作:

    x = 1;
#if CPU_FENCE
    __cpu_fence();
#endif
    r1 = y;

在自己的线程中的while 循环内部(使用SCHED_FIFO:99 固定到CPU)。

第二个(观察者,在我的演示中)这样做:

    y = 1;
#if CPU_FENCE
    __cpu_fence();
#endif
    r2 = x;

也在while 循环中使用相同的调度程序设置在其自己的线程中。

这样检查是否重新订购(完全按照示例中的说明):

if (r1 == 0 and r2 == 0)
++reorders;

禁用CPU_FENCE,这就是我所看到的:

[  0][myles][~/projects/...](master) sudo ./build/ooo
after 100000 attempts, 754 reorders observed

启用CPU_FENCE(使用“重量级”mfence 指令)我明白了:

[  0][myles][~/projects/...](master) sudo ./build/ooo
after 100000 attempts, 0 reorders observed

我希望这可以为您澄清事情!

【讨论】:

非常感谢您的细心和有用的解释,尤其是代码!我在我的电脑上运行它,它工作!非常感谢您的帮助! 很高兴你发现它有用!

以上是关于CPU乱序效果测试程序的主要内容,如果未能解决你的问题,请参考以下文章

c++11 内存模型解读

Linux性能优化从入门到实战:07 CPU篇:CPU性能优化方法

Arthas性能测试调优实践

关于内存屏蔽 和内存泄漏的解决

linux单进程如何实现多核cpu多线程分配?

OpenGL中的乱序Z缓冲区