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
我希望这能为你澄清事情!
我写了一个多线程程序来演示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
我希望这能为你澄清事情!