x86 内存排序测试显示在 Intel 手册中说不应该重新排序?
x86 memory ordering test shows reordering where Intel's manual says there shouldn't be?
根据英特尔手册。加载和存储都不会用类似的操作重新排序
根据 8.2.3.2 加载和存储都不会用类似的操作重新排序
在文档 https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html
enter image description here
但我创建了一个简单的案例,我发现 r1=1 和 r2=2 发生了。
#include <thread>
#include <iostream>
using namespace std;
volatile int x;
int b[500];
volatile int y;
volatile int start;
int s1;
int s2;
int s3;
int s0;
int foo()
{
while(start==0);
x=1;
asm volatile("" ::: "memory");
y=1;
return 0;
}
int fool2()
{
int a,b;
while(start==0);
a=x;
asm volatile("" ::: "memory");
b=y;
if(a==0 && b==1)
s0++;
if(a==0 && b==0)
s1++;
if(a==1 && b==0)
s2++;
if(a==1 && b==1)
s3++;
return 0;
}
int main()
{
int i=0;
while(1)
{
x=y=0;
thread t1(foo);
thread t2(fool2);
start = 1;
t1.join();
t2.join();
i++;
if((i&0xFFFF)==0)
{
cout<<s0<<" "<<s1<<" "<<s2<<" "<<s3<<endl;
}
}
}
g++ -O2 -pthread e.cpp
gcc 版本 7.5.0
输出:
69 86538 1 19246512
四种情况(r1和r2与0、1的组合)都是可能的。
仔细看看intel手册的Section 8.2.3.2是什么。在您的示例中,您正在有效地做:
Processor 1
Processor 2
mov [ _x], 1
mov r2, _x
mov [ _y], 1
mov r1, _y
而不是英特尔手册中所说的:
Processor 1
Processor 2
mov [ _x], 1
mov r1, _y
mov [ _y], 1
mov r2, _x
在您的示例中,处理器 2 可能会在处理器 1 设置 _x 之前加载 _x,然后在处理器 1 存储它之后加载 _y,从而允许 (r1=1, r2=0):
Instruction
Processor
mov r2, _x
2
mov [ _x], 1
1
mov [ _y], 1
1
mov r1, _y
2
在 Intel 示例中,处理器 2 只能在加载 _y 之后加载 _x,而处理器 1 只能在设置 _x 之后设置 _y,因此 (r1=1, r2=0) 是不可能的。
下面是一些演示英特尔行为的代码:
#include <thread>
#include <iostream>
#include <stdlib.h>
using namespace std;
volatile int x;
volatile int y;
volatile int start;
constexpr bool flipOrdering = true; //Set this to true to see Intel example, false to see your example
constexpr int jitter = 10000; //Range of random delay inserted between load/stores to make differences more obvious
int s1;
int s2;
int s3;
int s0;
int foo() {
while(start==0);
for(volatile int i = rand()%jitter; i; --i);
x = 1;
for(volatile int i = rand()%jitter; i; --i);
asm volatile("" ::: "memory");
for(volatile int i = rand()%jitter; i; --i);
y = 1;
return 0;
}
int fool2() {
int a, b;
while(start==0);
for(volatile int i = rand()%jitter; i; --i);
if constexpr(flipOrdering) b = y;
else a = x;
for(volatile int i = rand()%jitter; i; --i);
asm volatile("" ::: "memory");
for(volatile int i = rand()%jitter; i; --i);
if constexpr(flipOrdering) a = x;
else b = y;
if(a==0 && b==1)
s0++;
if(a==0 && b==0)
s1++;
if(a==1 && b==0)
s2++;
if(a==1 && b==1)
s3++;
return 0;
}
int main() {
int i=0;
while(i< 1000) {
x=y=0;
thread t1(foo);
thread t2(fool2);
start = 1;
t1.join();
t2.join();
i++;
if((i%100)==0) {
cout<<s0<<" "<<s1<<" "<<s2<<" "<<s3<<endl;
}
}
return 0;
}
这里是编译器资源管理器中相同代码 运行 的 link。
根据英特尔手册。加载和存储都不会用类似的操作重新排序 根据 8.2.3.2 加载和存储都不会用类似的操作重新排序
在文档 https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html enter image description here
但我创建了一个简单的案例,我发现 r1=1 和 r2=2 发生了。
#include <thread>
#include <iostream>
using namespace std;
volatile int x;
int b[500];
volatile int y;
volatile int start;
int s1;
int s2;
int s3;
int s0;
int foo()
{
while(start==0);
x=1;
asm volatile("" ::: "memory");
y=1;
return 0;
}
int fool2()
{
int a,b;
while(start==0);
a=x;
asm volatile("" ::: "memory");
b=y;
if(a==0 && b==1)
s0++;
if(a==0 && b==0)
s1++;
if(a==1 && b==0)
s2++;
if(a==1 && b==1)
s3++;
return 0;
}
int main()
{
int i=0;
while(1)
{
x=y=0;
thread t1(foo);
thread t2(fool2);
start = 1;
t1.join();
t2.join();
i++;
if((i&0xFFFF)==0)
{
cout<<s0<<" "<<s1<<" "<<s2<<" "<<s3<<endl;
}
}
}
g++ -O2 -pthread e.cpp
gcc 版本 7.5.0
输出:
69 86538 1 19246512
四种情况(r1和r2与0、1的组合)都是可能的。
仔细看看intel手册的Section 8.2.3.2是什么。在您的示例中,您正在有效地做:
Processor 1 | Processor 2 |
---|---|
mov [ _x], 1 | mov r2, _x |
mov [ _y], 1 | mov r1, _y |
而不是英特尔手册中所说的:
Processor 1 | Processor 2 |
---|---|
mov [ _x], 1 | mov r1, _y |
mov [ _y], 1 | mov r2, _x |
在您的示例中,处理器 2 可能会在处理器 1 设置 _x 之前加载 _x,然后在处理器 1 存储它之后加载 _y,从而允许 (r1=1, r2=0):
Instruction | Processor |
---|---|
mov r2, _x | 2 |
mov [ _x], 1 | 1 |
mov [ _y], 1 | 1 |
mov r1, _y | 2 |
在 Intel 示例中,处理器 2 只能在加载 _y 之后加载 _x,而处理器 1 只能在设置 _x 之后设置 _y,因此 (r1=1, r2=0) 是不可能的。
下面是一些演示英特尔行为的代码:
#include <thread>
#include <iostream>
#include <stdlib.h>
using namespace std;
volatile int x;
volatile int y;
volatile int start;
constexpr bool flipOrdering = true; //Set this to true to see Intel example, false to see your example
constexpr int jitter = 10000; //Range of random delay inserted between load/stores to make differences more obvious
int s1;
int s2;
int s3;
int s0;
int foo() {
while(start==0);
for(volatile int i = rand()%jitter; i; --i);
x = 1;
for(volatile int i = rand()%jitter; i; --i);
asm volatile("" ::: "memory");
for(volatile int i = rand()%jitter; i; --i);
y = 1;
return 0;
}
int fool2() {
int a, b;
while(start==0);
for(volatile int i = rand()%jitter; i; --i);
if constexpr(flipOrdering) b = y;
else a = x;
for(volatile int i = rand()%jitter; i; --i);
asm volatile("" ::: "memory");
for(volatile int i = rand()%jitter; i; --i);
if constexpr(flipOrdering) a = x;
else b = y;
if(a==0 && b==1)
s0++;
if(a==0 && b==0)
s1++;
if(a==1 && b==0)
s2++;
if(a==1 && b==1)
s3++;
return 0;
}
int main() {
int i=0;
while(i< 1000) {
x=y=0;
thread t1(foo);
thread t2(fool2);
start = 1;
t1.join();
t2.join();
i++;
if((i%100)==0) {
cout<<s0<<" "<<s1<<" "<<s2<<" "<<s3<<endl;
}
}
return 0;
}
这里是编译器资源管理器中相同代码 运行 的 link。