我想知道简单地用 sse/sse2 编译我的 msvc 项目是否会有任何效果。例如,我做向量归一化和点积,但我用数学来做这些,而不是任何特定的函数。是否有类似 sse_dot() 和 sse_normalize() 我应该使用的来实际利用,或者编译器会知道?
据我了解,使用 sse2 编译器选项将导致编译器使用标量而不是向量 sse2 指令来代替正常的 fpu 代码。我不认为它会进行任何矢量化。 sse2 标量的东西肯定比 fpu 更快。
要使用向量单元,您需要直接使用内在函数( xmmintrin.h )或使用第三方库。如果您只是做简单的矢量/矩阵渲染,Bullet SDK 有一个 sse 优化的矢量数学库,这还不错。 IIRC DirectX/XNAmath 库也进行了 sse 优化。
如果这些都不符合你的喜好,谷歌应该会提供一些替代方案。
或者您可以通过使用 Eigen、BLAS、Intel MKL 等高性能库来避免显式编写 SSE 内容……除非您正在使用嵌入式系统,否则这些库将比您能想到的任何库都要好得多。
自己编写 SSE 代码(asm 或内在函数),使用第三方 SSE 优化库(例如 IPP、MKL 等),或使用自动矢量化编译器,例如 Intel 的 ICC。
并非所有编译器都像您想象的那么聪明。即使 gcc 也可能并不总是优化最明显的代码。请参阅以下示例并自行尝试。 Icc 似乎能够优化内部循环,但 gcc,因为我尝试了几种设置,不能。必要时,必须使用SSE函数手动调用SSE/SSE2指令。人们告诉我这是一个很好的教程。
编辑:以下示例适用于 Mac/Linux gcc。但在linux上icc失败了。我不知道为什么。顺便说一句,icc 被认为在矢量化方面比 gcc 更好。
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <emmintrin.h>
float **mm_init(int n)
{
float **m;
int i;
m = (float**)malloc(n * sizeof(void*));
for (i = 0; i < n; ++i)
m[i] = calloc(n, sizeof(float));
return m;
}
void mm_destroy(int n, float **m)
{
int i;
for (i = 0; i < n; ++i) free(m[i]);
free(m);
}
float **mm_gen(int n)
{
float **m;
int i, j;
m = mm_init(n);
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
m[i][j] = 2 * drand48() - 1.0;
return m;
}
// better cache performance by transposing the second matrix
float **mm_mul2(int n, float *const *a, float *const *b)
{
int i, j, k;
float **m, **c;
m = mm_init(n); c = mm_init(n);
for (i = 0; i < n; ++i) // transpose
for (j = 0; j < n; ++j)
c[i][j] = b[j][i];
for (i = 0; i < n; ++i) {
float *p = a[i], *q = m[i];
for (j = 0; j < n; ++j) {
float t = 0.0, *r = c[j];
for (k = 0; k < n; ++k)
t += p[k] * r[k];
q[j] = t;
}
}
mm_destroy(n, c);
return m;
}
// explicit SSE optimization for the inner loop
float **mm_mul3(int n, float *const *a, float *const *b)
{
int i, j, k;
float **m, **c, x[4];
m = mm_init(n); c = mm_init(n);
for (i = 0; i < n; ++i) // transpose
for (j = 0; j < n; ++j)
c[i][j] = b[j][i];
for (i = 0; i < n; ++i) {
float *p = a[i], *q = m[i];
for (j = 0; j < n; ++j) {
__m128 t = _mm_setzero_ps();
float *r = c[j];
for (k = 0; k < n; k += 4) // four operations in one CPU cycle
t = _mm_add_ps(t, _mm_mul_ps(_mm_load_ps(p+k), _mm_load_ps(r+k)));
_mm_store_ps(x, t);
q[j] = x[0] + x[1] + x[2] + x[3];
}
}
mm_destroy(n, c);
return m;
}
int main(int argc, char *argv[])
{
int n = 100;
float **a, **b, **m;
clock_t t;
if (argc > 1) n = atoi(argv[1]);
n = (n + 3) / 4 * 4; // for simplicity, n can be divided by 4
srand48(11);
a = mm_gen(n); b = mm_gen(n);
t = clock();
m = mm_mul2(n, a, b);
fprintf(stderr, "cache: %lf sec; M[%d][%d]=%f\n", (double)(clock() - t) / CLOCKS_PER_SEC, n/2, n/2, m[n/2][n/2]);
t = clock();
m = mm_mul3(n, a, b);
fprintf(stderr, "SSE: %lf sec; M[%d][%d]=%f\n", (double)(clock() - t) / CLOCKS_PER_SEC, n/2, n/2, m[n/2][n/2]);
mm_destroy(n, a); mm_destroy(n, b); mm_destroy(n, m);
return 0;
}
如果你启用了SSE2,那么编译器会在你背后使用它。您永远不会注意到也不需要知道,除非您打算支持不带 SSE2 的 CPU。这与任何其他底层 CPU 指令相同。