CPU乱序效应的测试程序

时间:2015-11-21 22:14:23

标签: c x86 synchronization memory-barriers barrier

我写了一个多线程程序来演示英特尔处理器的乱序效果。该计划附在本文末尾。 预期的结果应该是当handler1将x打印为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-通用)上的编译程序。

有谁知道我应该怎么做以查看乱序效果?

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

2 个答案:

答案 0 :(得分:5)

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

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

你的程序做的是:它试图(非常粗暴地)模拟一个竞争条件,你希望在f 之前看到x的分配和同时你希望上次切换发生完全 你认为新线程将安排在与之相同的CPU内核上另外一个。 但是,正如我上面所解释的那样 - 即使你足够幸运地达到所有列出的条件(在f分配之后但x分配和让新线程安排在同一个CPU内核上 - 这本身就是一个非常低概率的事件 - 即使你真正公开的是一个潜在的竞争条件,但不是无序执行。

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

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

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

答案 1 :(得分:3)

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

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

  

Intel-64内存订购模型允许将负载与较早的商店重新排序到不同的位置。

它还指定(我总结,但所有这些都可以在第8.2节内存排序中使用8.2.3中的示例),负载永远不会被加载重新排序,商店永远不会重新排序存储,并且存储从不重新排序早期负荷。这意味着在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

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