まず、問題を解決するための賢明な試みを行うために、以下のコードの機能を理解する必要はないと思うと言って、これに接頭辞を付けます。これは主に最適化問題です。コードは、何が行われているのかを理解することです。
私は次のやや最適化された畳み込みメインループを持っています(これは機能します):
for(int i=0; i<length-kernel_length; i+=4){
acc = _mm_setzero_ps();
for(int k=0; k<KERNEL_LENGTH; k+=4){
int data_offset = i + k;
for (int l = 0; l < 4; l++){
data_block = _mm_load_ps(in_aligned[l] + data_offset);
prod = _mm_mul_ps(kernel_reverse[k+l], data_block);
acc = _mm_add_ps(acc, prod);
}
}
_mm_storeu_ps(out+i, acc);
}
KERNEL_LENGTH
は4
in_aligned
です。入力配列(畳み込みが実行される)は4回繰り返され、各繰り返しは1つのサンプルを他のサンプルから左にシフトします。これは、すべてのサンプルが16バイトに整列された場所で見つかるようにするためです。
kernel_reverse
は逆カーネルであり、すべてのサンプルが4回繰り返されて、4元ベクトルが満たされ、次のように宣言および定義されます。
float kernel_block[4] __attribute__ ((aligned (16)));
__m128 kernel_reverse[KERNEL_LENGTH] __attribute__ ((aligned (16)));
// Repeat the kernel across the vector
for(int i=0; i<KERNEL_LENGTH; i++){
kernel_block[0] = kernel[kernel_length - i - 1];
kernel_block[1] = kernel[kernel_length - i - 1];
kernel_block[2] = kernel[kernel_length - i - 1];
kernel_block[3] = kernel[kernel_length - i - 1];
kernel_reverse[i] = _mm_load_ps(kernel_block);
}
コードはアルゴリズムを正しくそしてかなり速く計算します。
私はコードをコンパイルしますgcc -std=c99 -Wall -O3 -msse3 -mtune=core2
私の質問はこれです:ループは以下のマシンコードにコンパイルされます。このループ内では、毎回カーネルのロードに重要でない数の命令が費やされます。カーネルはループの反復ごとに変更されないため、原則としてSSEレジスタに保持できます。私が理解しているように、カーネルを簡単に格納するのに十分なレジスタがあります(実際、マシンコードはレジスタの圧力が大きすぎることを示唆していません)。
すべてのループでカーネルをロードしないようにコンパイラを説得するにはどうすればよいですか?
カーネルの長さが一定に設定されている場合、コンパイラがこれを自動的に行うことを期待していました。
testl %edx, %edx
jle .L79
leaq (%rcx,%rcx,2), %rsi
movaps -144(%rbp), %xmm6
xorps %xmm2, %xmm2
leal -1(%rdx), %ecx
movaps -128(%rbp), %xmm5
xorl %eax, %eax
movaps -112(%rbp), %xmm4
leaq 0(%r13,%rsi,4), %rsi
shrl $2, %ecx
addq $1, %rcx
movaps -96(%rbp), %xmm3
salq $4, %rcx
.p2align 4,,10
.p2align 3
.L80:
movaps 0(%r13,%rax), %xmm0
movaps (%r14,%rax), %xmm1
mulps %xmm6, %xmm0
mulps %xmm5, %xmm1
addps %xmm2, %xmm0
addps %xmm1, %xmm0
movaps (%r9,%rax), %xmm1
mulps %xmm4, %xmm1
addps %xmm1, %xmm0
movaps (%rsi,%rax), %xmm1
mulps %xmm3, %xmm1
addps %xmm1, %xmm0
movups %xmm0, (%rbx,%rax)
addq $16, %rax
cmpq %rcx, %rax
jne .L80
.L79:
編集:完全なコードリストは次のとおりです。
#define KERNEL_LENGTH 4
int convolve_sse_in_aligned_fixed_kernel(float* in, float* out, int length,
float* kernel, int kernel_length)
{
float kernel_block[4] __attribute__ ((aligned (16)));
float in_aligned[4][length] __attribute__ ((aligned (16)));
__m128 kernel_reverse[KERNEL_LENGTH] __attribute__ ((aligned (16)));
__m128 data_block __attribute__ ((aligned (16)));
__m128 prod __attribute__ ((aligned (16)));
__m128 acc __attribute__ ((aligned (16)));
// Repeat the kernel across the vector
for(int i=0; i<KERNEL_LENGTH; i++){
int index = kernel_length - i - 1;
kernel_block[0] = kernel[index];
kernel_block[1] = kernel[index];
kernel_block[2] = kernel[index];
kernel_block[3] = kernel[index];
kernel_reverse[i] = _mm_load_ps(kernel_block);
}
/* Create a set of 4 aligned arrays
* Each array is offset by one sample from the one before
*/
for(int i=0; i<4; i++){
memcpy(in_aligned[i], (in+i), (length-i)*sizeof(float));
}
for(int i=0; i<length-kernel_length; i+=4){
acc = _mm_setzero_ps();
for(int k=0; k<KERNEL_LENGTH; k+=4){
int data_offset = i + k;
for (int l = 0; l < 4; l++){
data_block = _mm_load_ps(in_aligned[l] + data_offset);
prod = _mm_mul_ps(kernel_reverse[k+l], data_block);
acc = _mm_add_ps(acc, prod);
}
}
_mm_storeu_ps(out+i, acc);
}
// Need to do the last value as a special case
int i = length - kernel_length;
out[i] = 0.0;
for(int k=0; k<kernel_length; k++){
out[i] += in_aligned[0][i+k] * kernel[kernel_length - k - 1];
}
return 0;
}