CPU乱序效果的测试程序

Test program for CPU out of order effect

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

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

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

#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;
}

您正在将竞争条件与乱序执行范式混合在一起。不幸的是,我很确定你不能 "expose" 乱序执行,因为它是明确设计和实现的,以保护你( 运行ning 程序及其数据)免受其影响.

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

您的程序所做的是:它尝试(非常粗略地)模拟一种竞争条件,在这种情况下您希望看到 f 的分配先于 x and 同时你希望有一个上下文切换发生 exactly 在那个时刻 and 你假设新线程将安排在与另一个相同的 CPU 核心上。 然而,正如我上面所解释的——即使你足够幸运地满足了所有列出的条件(在 f 赋值之后但在 x 赋值之前安排第二个线程 and 将新线程安排在同一个 CPU 核心上)——这本身就是一个概率极低的事件——即便如此,你真正暴露的只是一个潜在的竞争条件,而不是一个 out-of -订单执行。

很抱歉让您失望了,您的程序无法帮助您观察乱序执行的影响。至少没有足够高的概率实用。

您可以在这里阅读更多关于乱序执行的内容: http://courses.cs.washington.edu/courses/csep548/06au/lectures/introOOO.pdf

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

请注意:以下仅涉及 MEMORY 重新排序。据我所知,您无法观察到管道外的乱序执行,因为这将导致 CPU 无法遵守其接口。 (例如:你应该告诉英特尔,这将是一个错误)。具体来说,重新排序缓冲区和指令退休簿记必须失败。

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

The Intel-64 memory-ordering model allows a load to be reordered with an earlier store to a different location.

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

要观察内存重新排序,您只需足够仔细地实施该示例以实际观察效果。 Here is a link to a full implementation I did that demonstrates this. (I will follow up with more details in the accompanying post 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(使用 "heavyweight" mfence 指令)我看到:

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

我希望这能为你澄清事情!