原文地址:https://www.cnblogs.com/dragon2012/p/5200698.html
使用SSE指令,首先要了解這一類用於進行初始化加載數據以及將暫存器的數據保存到內存相關的指令,
我們知道,大多數SSE指令是使用的xmm0到xmm8的暫存器,那麼使用之前,就需要將數據從內存加載到這些暫存器。
1. load系列,用於加載數據,從內存到暫存器
__m128 _mm_load_ss (float *p) __m128 _mm_load_ps (float *p) __m128 _mm_load1_ps (float *p) __m128 _mm_loadh_pi (__m128 a, __m64 *p) __m128 _mm_loadl_pi (__m128 a, __m64 *p) __m128 _mm_loadr_ps (float *p) __m128 _mm_loadu_ps (float *p)
上面是從手冊查詢到的load系列的函數。其中,
_mm_load_ss用於scalar的加載,所以,加載一個單精度浮點數到暫存器的低字節,其它三個字節清0,(r0 := *p, r1 := r2 := r3 := 0.0)。
_mm_load_ps用於packed的加載(下面的都是用於packed的),要求p的地址是16字節對齊,否則讀取的結果會出錯,(r0 := p[0], r1 := p[1], r2 := p[2], r3 := p[3])。
_mm_load1_ps表示將p地址的值,加載到暫存器的四個字節,需要多條指令完成,所以,從性能考慮,在內層循環不要使用這類指令。(r0 := r1 := r2 := r3 := *p)。
_mm_loadh_pi和_mm_loadl_pi分別用於從兩個參數高底字節等組合加載。具體參考手冊。
_mm_loadr_ps表示以_mm_load_ps反向的順序加載,需要多條指令完成,當然,也要求地址是16字節對齊。(r0 := p[3], r1 := p[2], r2 := p[1], r3 := p[0])。
_mm_loadu_ps和_mm_load_ps一樣的加載,但是不要求地址是16字節對齊,對應指令爲movups。
2. set系列,用於加載數據,大部分需要多條指令完成,但是可能不需要16字節對齊。
__m128 _mm_set_ss (float w) __m128 _mm_set_ps (float z, float y, float x, float w) __m128 _mm_set1_ps (float w) __m128 _mm_setr_ps (float z, float y, float x, float w) __m128 _mm_setzero_ps ()
這一系列函數主要是類似於load的操作,但是可能會調用多條指令去完成,方便的是可能不需要考慮對齊的問題。
_mm_set_ss對應於_mm_load_ss的功能,不需要字節對齊,需要多條指令。(r0 = w, r1 = r2 = r3 = 0.0)
_mm_set_ps對應於_mm_load_ps的功能,參數是四個單獨的單精度浮點數,所以也不需要字節對齊,需要多條指令。(r0=w, r1 = x, r2 = y, r3 = z,注意順序)
_mm_set1_ps對應於_mm_load1_ps的功能,不需要字節對齊,需要多條指令。(r0 = r1 = r2 = r3 = w)
_mm_setzero_ps是清0操作,只需要一條指令。(r0 = r1 = r2 = r3 = 0.0)
3. store系列,用於將計算結果等SSE暫存器的數據保存到內存中。
void _mm_store_ss (float *p, __m128 a) void _mm_store_ps (float *p, __m128 a) void _mm_store1_ps (float *p, __m128 a) void _mm_storeh_pi (__m64 *p, __m128 a) void _mm_storel_pi (__m64 *p, __m128 a) void _mm_storer_ps (float *p, __m128 a) void _mm_storeu_ps (float *p, __m128 a) void _mm_stream_ps (float *p, __m128 a)
這一系列函數和load系列函數的功能對應,基本上都是一個反向的過程。
_mm_store_ss:一條指令,*p = a0
_mm_store_ps:一條指令,p[i] = a[i]。
_mm_store1_ps:多條指令,p[i] = a0。
_mm_storeh_pi,_mm_storel_pi:值保存其高位或低位。
_mm_storer_ps:反向,多條指令。
_mm_storeu_ps:一條指令,p[i] = a[i],不要求16字節對齊。
_mm_stream_ps:直接寫入內存,不改變cache的數據。
(2)算術指令
SSE提供了大量的浮點運算指令,包括加法、減法、乘法、除法、開方、最大值、最小值、近似求倒數、求開方的倒數等等,可見SSE指令的強大之處。那麼在瞭解了上面的數據加載和數據保存的指令之後,使用這些算術指令就很容易了,下面以加法爲例。
SSE中浮點加法的指令有:
__m128 _mm_add_ss (__m128 a, __m128 b) __m128 _mm_add_ps (__m128 a, __m128 b)
其中,_mm_add_ss表示scalar執行模式,_mm_add_ps表示packed執行模式。
一般而言,使用SSE指令寫代碼,步驟爲:使用load/set函數將數據從內存加載到SSE暫存器;使用相關SSE指令完成計算等;使用store系列函數將結果從暫存器保存到內存,供後面使用。
下面是一個完成加法的例子:
#include <intrin.h> int main(int argc, char* argv[]) { float op1[4] = {1.0, 2.0, 3.0, 4.0}; float op2[4] = {1.0, 2.0, 3.0, 4.0}; float result[4]; __m128 a; __m128 b; __m128 c; // Load a = _mm_loadu_ps(op1); b = _mm_loadu_ps(op2); // Calculate c = _mm_add_ps(a, b); // c = a + b // Store _mm_storeu_ps(result, c); /* // Using the __m128 union to get the result. printf("0: %lf\n", c.m128_f32[0]); printf("1: %lf\n", c.m128_f32[1]); printf("2: %lf\n", c.m128_f32[2]); printf("3: %lf\n", c.m128_f32[3]); */ printf("0: %lf\n", result[0]); printf("1: %lf\n", result[1]); printf("2: %lf\n", result[2]); printf("3: %lf\n", result[3]); return 0; }
這個例子,前面已經寫過類似的加法例子,註釋中的printf部分是利用__m128這個數據類型來獲取相關的值,
這個類型是一個union類型,具體定義可以參考相關頭文件,但是,對於實際使用,有時候這個值是一箇中間值,需要後面計算使用,就得使用store了,效率更高。
上面使用的是_mm_loadu_ps和_mm_storeu_ps,不要求字節對齊,如果使用_mm_load_ps和_mm_store_ps,會發現程序會崩潰或得不到正確結果。下面是指定字節對齊後的一種實現方法:
#include <intrin.h> int main(int argc, char* argv[]) { __declspec(align(16)) float op1[4] = {1.0, 2.0, 3.0, 4.0}; __declspec(align(16)) float op2[4] = {1.0, 2.0, 3.0, 4.0}; _MM_ALIGN16 float result[4]; // A macro, same as "__declspec(align(16))" __m128 a; __m128 b; __m128 c; // Load a = _mm_load_ps(op1); b = _mm_load_ps(op2); // Calculate c = _mm_add_ps(a, b); // c = a + b // Store _mm_store_ps(result, c); /* // Using the __m128 union to get the result. printf("0: %lf\n", c.m128_f32[0]); printf("1: %lf\n", c.m128_f32[1]); printf("2: %lf\n", c.m128_f32[2]); printf("3: %lf\n", c.m128_f32[3]); */ printf("0: %lf\n", result[0]); printf("1: %lf\n", result[1]); printf("2: %lf\n", result[2]); printf("3: %lf\n", result[3]); return 0; }
原文地址:http://blog.csdn.net/gengshenghong/article/details/7011373
原本看sse就是爲給骨骼動畫加速的,但是用起來感覺不快,
下面是矩陣乘以向量的代碼,以及sse版
vec3 operator*(const vec3 &v) const { vec3 ret; ret[0] = mat[0] * v[0] + mat[4] * v[1] + mat[8] * v[2] + mat[12]; ret[1] = mat[1] * v[0] + mat[5] * v[1] + mat[9] * v[2] + mat[13]; ret[2] = mat[2] * v[0] + mat[6] * v[1] + mat[10] * v[2] + mat[14]; return ret; } vec3 MultVec3SSE(const vec3& v) const { vec3 ret; __m128 res1; __m128 res2; __m128 res3; __m128 addres; /* _MM_ALIGN16 float addresf[4] = {mat[12],mat[13],mat[14],1}; _MM_ALIGN16 float multf1[4] = {mat[0],mat[1],mat[2],1}; _MM_ALIGN16 float multf2[4] = {mat[4],mat[5],mat[6],1}; _MM_ALIGN16 float multf3[4] = {mat[8],mat[9],mat[10],1}; _MM_ALIGN16 float mvecf1[4] = {v[0],v[0],v[0],1}; _MM_ALIGN16 float mvecf2[4] = {v[1],v[1],v[1],1}; _MM_ALIGN16 float mvecf3[4] = {v[2],v[2],v[2],1}; addres = _mm_load_ps(addresf); */ addres = _mm_set_ps(1,mat[14],mat[13],mat[12]); /* res1 = _mm_mul_ps(_mm_load_ps(multf1),_mm_load_ps(mvecf1)); res2 = _mm_mul_ps(_mm_load_ps(multf2),_mm_load_ps(mvecf2)); res3 = _mm_mul_ps(_mm_load_ps(multf3),_mm_load_ps(mvecf3)); //_mm_set_ps */ res1 = _mm_mul_ps(_mm_set_ps(1,mat[2],mat[1],mat[0]),_mm_set_ps(1,v[0],v[0],v[0])); res2 = _mm_mul_ps(_mm_set_ps(1,mat[6],mat[5],mat[4]),_mm_set_ps(1,v[1],v[1],v[1])); res3 = _mm_mul_ps(_mm_set_ps(1,mat[10],mat[9],mat[8]),_mm_set_ps(1,v[2],v[2],v[2])); res1 = _mm_add_ps(res1,res2); res3 = _mm_add_ps(res1,res3); res3 = _mm_add_ps(res3,addres); float dest[4]; _mm_storeu_ps(dest,res3); ret.x = dest[0] ; ret.y = dest[1] ; ret.z = dest[2] ; return ret; }
debug模式下fps只提高了個位數,release下 更是跑不過 非sse的。。。。。情何以堪