编辑 下面的部分解决方案(编辑2),但我还有一个问题(见最后)
我正在尝试使用gcc-4.9.2编译以下C程序,在Windows 7上,32位,在Pentium G3220上运行(根据Windows系统信息).如果我理解正确,这个处理器没有AVX扩展,所以发生一些事情是很自然的,我只是不确定究竟是什么.最初,我正在使用gcc进行优化,我尝试-mavx而不是"意外".
以下程序以字典顺序计算数字0 ... n-1的排列(以n作为参数给出),以及每个排列的排名(按此顺序排列的位置)和"unrank"(从排名中恢复排列) ),并检查所有这些都是正确的.它应该只打印"OK"或"错误".
gcc -O3,程序运行正确,我检查了所有整数输入(1 <= n <= 11).gcc -O3 -mavx,它正确地运行1 <= n <= 7,并且对于n> = 8,它什么都不打印,实际上它什么都不做(退出前几乎没有延迟).我没有收到来自程序或Windows的消息(我原本预计可能会因未知指令崩溃,但事情并未发生).(在另一台带有Windows 7 64位的计算机上,在core-i5上,以及相同的gcc-4.9.2,当编译为32 位或64位时,程序似乎运行正常,没有-mavx )
我不明白为什么它为某些输入值正确运行,而对其他输入值则失败.有没有人对此有所暗示?
这是完整的程序,后面是一个有相同问题的较短程序.
#include <stdlib.h>
#include <stdio.h>
#define SWAP(a,b) {int c; c = a; a = b; b = c;}
int next_perm(int n, int a[n]) {
int i, j, k;
for(i = n - 1; i > 0 && a[i - 1] > a[i]; i--);
for(j = i, k = n - 1; j < k; j++, k--) SWAP(a[j], a[k]);
if(i == 0) return 0;
for(j = i--; a[j] < a[i]; j++);
SWAP(a[i], a[j]);
return 1;
}
#undef SWAP
void copyvec(int n, int dst[n], int src[n]) {
int i;
for(i = 0; i < n; i++) {
dst[i] = src[i];
}
}
int eqvec(int n, int a[n], int b[n]) {
int i;
for(i = 0; i < n; i++) {
if(a[i] != b[i]) return 0;
}
return 1;
}
int rank(int n, int a[n]) {
int v[n], i, j, r;
v[n - 1] = 1;
for(j = n - 2; j >= 0; j--) v[j] = v[j + 1]*(n - 1 - j);
for(r = i = 0; ; i++) {
for(j = i; j < n; j++) {
if(a[j] > j) goto cont;
}
return r;
cont:
i = j;
r += v[i]*(a[i] - i);
for(j = i + 1; j < n; j++) {
if(a[j] < a[i]) a[j]++;
}
}
}
void unrank(int n, int a[n], int p) {
int v[n], i, j, r, s;
v[n - 1] = 1;
for(i = n - 2; i >= 0; i--) v[i] = v[i + 1]*(n - 1 - i);
p %= n*v[0];
for(i = 0; i < n; i++) a[i] = i;
for(i = 0; p > 0; i++) {
for(; v[i] > p; i++);
r = p/v[i];
p %= v[i];
for(s = a[j = i + r]; j >= i; j--) a[j] = a[j - 1];
a[i] = s;
}
}
int main(int argc, char **argv) {
int n, i, r, s = 0, q = 0;
int *a = NULL, *b = NULL, *c = NULL;
if(argc == 2 && (n = strtol(argv[1], NULL, 0)) > 0) {
a = malloc(n*sizeof(int));
b = malloc(n*sizeof(int));
c = malloc(n*sizeof(int));
if(!a || !b || !c) {
puts("Unable to allocate memory");
goto end;
} else {
for(i = 0; i < n; i++) a[i] = i;
do {
copyvec(n, b, a);
r = rank(n, b);
unrank(n, c, r);
q |= s++ != r || !eqvec(n, a, c);
} while(next_perm(n, a));
puts(q?"Error":"OK");
}
} else {
puts("perm n - Check all permutations of {0 ... n - 1}, with n > 0");
}
end:
if(a) free(a);
if(b) free(b);
if(c) free(c);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编辑
继Brian Cain的评论之后,这是一个具有相同问题的较短程序.我删除了对输入值的所有检查,所有rank/unrank的东西,我用一个大小为20的数组替换了malloc/free(现在只有一个,因为b和c不再使用了).现在程序只计算while(next_perm(n, a));循环的排列,并且不对它们做任何事情.尽管如此,它仍然应该打印"OK",因为q的值在初始q = 0之后不会改变.
#include <stdlib.h>
#include <stdio.h>
#define SWAP(a,b) {int c; c = a; a = b; b = c;}
int next_perm(int n, int a[n]) {
int i, j, k;
for(i = n - 1; i > 0 && a[i - 1] > a[i]; i--);
for(j = i, k = n - 1; j < k; j++, k--) SWAP(a[j], a[k]);
if(i == 0) return 0;
for(j = i--; a[j] < a[i]; j++);
SWAP(a[i], a[j]);
return 1;
}
#undef SWAP
int main(int argc, char **argv) {
int n, i, r, s = 0, q = 0, a[20];
n = strtol(argv[1], NULL, 0);
for(i = 0; i < n; i++) a[i] = i;
while(next_perm(n, a));
puts(q?"Error":"OK");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编辑2:装配输出的说明
我还添加了gcc的反汇编输出(在Intel语法中),找到了gcc -O3 -mavx -S -masm=intel和gcc-4.9.2(参见上面的链接,了解编译器的实际二进制文件).但是,它需要一些工作,因为gcc会内联对next_perm的调用,并且它的可读性较差.我还删除了CFI指令和对齐以及所有其他指令,以提高可读性:
_next_perm:
LFB0:
push ebp
push edi
push esi
push ebx
mov ecx, DWORD PTR [esp+20]
mov edx, DWORD PTR [esp+24]
lea eax, [ecx-1]
test eax, eax
jle L12
mov edi, DWORD PTR [edx-4+ecx*4]
cmp DWORD PTR [edx-8+ecx*4], edi
mov ecx, eax
jg L5
jmp L11
L28:
mov esi, DWORD PTR [edx+ecx*4]
cmp DWORD PTR [edx-4+ecx*4], esi
jle L27
L5:
sub ecx, 1
jne L28
L4:
mov ebx, ecx
L7:
mov esi, DWORD PTR [edx+ebx*4]
mov edi, DWORD PTR [edx+eax*4]
mov DWORD PTR [edx+ebx*4], edi
mov DWORD PTR [edx+eax*4], esi
add ebx, 1
sub eax, 1
cmp ebx, eax
jl L7
L2:
xor eax, eax
test ecx, ecx
je L23
L11:
sal ecx, 2
lea esi, [edx+ecx]
lea ebp, [edx-4+ecx]
mov ebx, DWORD PTR [esi]
mov edi, DWORD PTR [ebp+0]
cmp edi, ebx
jle L9
lea eax, [edx+4+ecx]
L10:
mov esi, eax
add eax, 4
mov ebx, DWORD PTR [eax-4]
cmp ebx, edi
jl L10
L9:
mov DWORD PTR [ebp+0], ebx
mov eax, 1
mov DWORD PTR [esi], edi
L23:
pop ebx
pop esi
pop edi
pop ebp
ret
L27:
cmp eax, ecx
jg L4
jmp L11
L12:
mov ecx, eax
jmp L2
Run Code Online (Sandbox Code Playgroud)
除了标签号之外,汇编输出是否与-mavx相同:没有AVX指令,这意味着问题实际存在main.
这可以通过puts在main中添加一些来检查:
int main(int argc, char **argv) {
int n, i, q = 0, a[20];
puts("X");
n = strtol(argv[1], NULL, 0);
puts("Y");
for(i = 0; i < n; i++) a[i] = i;
puts("Z");
while(next_perm(n, a));
puts(q?"Error":"OK");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然后,程序在失败时只打印X和Y,因此问题来自用于在Y和Z之间的for循环中构建'a'的AVX指令.
这是组件输出main,同样没有指令(LC2指向"Y",LC3指向"Z").main的汇编输出中唯一的AVX指令位于这两个之间puts,它们用于for构建初始'a' 的循环,即数组{0,1,...,n-1}.实际上发生的是,AVX指令用于一次构建几个'a'元素(我猜4),如果'a'的长度不是4的倍数,那么还有一个额外的步骤(在L4和L9),在L9调用puts("Z")之前,然后是while(next_perm(n,a)); 在L3.因此,问题非常简单:如果n足够小,那么AVX循环实际上不会运行,并且没有错误.这里最大有效n 是4,但它在gcc的不同运行之间有所不同,它似乎有点随机(我昨天得到8).
LC0和LC4标签指向AVX指令使用的两个4个元素阵列:LC0为{0,1,2,3},LC4为{4,4,4,4}.难怪为什么他们在这里,即使没有AVX的深刻知识,它闻起来像一个展开的循环:-)
_main:
push ebp
mov ebp, esp
push edi
push esi
push ebx
and esp, -16
sub esp, 96
call ___main
mov DWORD PTR [esp], OFFSET FLAT:LC1
call _puts
mov eax, DWORD PTR [ebp+12]
mov DWORD PTR [esp+8], 0
mov DWORD PTR [esp+4], 0
mov eax, DWORD PTR [eax+4]
mov DWORD PTR [esp], eax
call _strtol
mov DWORD PTR [esp], OFFSET FLAT:LC2
mov ebx, eax
call _puts
test ebx, ebx
jle L17
lea edx, [ebx-4]
lea ecx, [ebx-1]
shr edx, 2
add edx, 1
cmp ecx, 3
lea eax, [0+edx*4]
jbe L10
vmovdqa xmm1, XMMWORD PTR LC4
lea esi, [esp+16]
xor ecx, ecx
vmovdqa xmm0, XMMWORD PTR LC0
L5:
mov edi, ecx
add ecx, 1
sal edi, 4
cmp edx, ecx
vmovaps XMMWORD PTR [esi+edi], xmm0
vpaddd xmm0, xmm0, xmm1
ja L5
cmp ebx, eax
je L9
L4:
lea edx, [eax+1]
mov DWORD PTR [esp+16+eax*4], eax
cmp ebx, edx
jle L9
mov DWORD PTR [esp+16+edx*4], edx
lea edx, [eax+2]
cmp ebx, edx
jle L9
add eax, 3
mov DWORD PTR [esp+16+edx*4], edx
cmp ebx, eax
jle L9
mov DWORD PTR [esp+16+eax*4], eax
L9:
mov DWORD PTR [esp], OFFSET FLAT:LC3
call _puts
L3:
mov DWORD PTR [esp+4], esi
mov DWORD PTR [esp], ebx
call _next_perm
test eax, eax
jne L3
mov DWORD PTR [esp], OFFSET FLAT:LC5
call _puts
lea esp, [ebp-12]
xor eax, eax
pop ebx
pop esi
pop edi
pop ebp
ret
L10:
xor eax, eax
lea esi, [esp+16]
jmp L4
L17:
lea esi, [esp+16]
jmp L9
Run Code Online (Sandbox Code Playgroud)
现在,我理解实际发生了什么,但仍有一个问题:当程序试图运行AVX指令时,为什么没有任何错误消息?它只是退出,或它被杀死,但没有任何暗示出现问题.
use*_*249 -2
This code always results in:
where parameter = n
a[] = {0,0,2, 3, ...,n-2,n-1}
b[] = {n-1, n-1, ... , n-1}
c[] = {n-1, n-2, ... , 0}
when it reaches the above conditions,
then it exits with "OK"
the amount of time spent executing the code
climbs at an exponential rate
as the value of the parameter is increased
Run Code Online (Sandbox Code Playgroud)