上证所累计求和
Posted
技术标签:
【中文标题】上证所累计求和【英文标题】:SSE cumulative summing 【发布时间】:2012-10-19 09:20:24 【问题描述】:我有一个简单的问题。具有起始 uint_32 值(例如 125)和要添加的操作数 __m128i,例如 (+5,+10,-1,-5)。我想尽快得到一个向量(125 + 5、125 + 5 + 10、125 + 5 + 10 - 1、125 + 5 + 10 - 1 - 5),即从操作数中累积添加值到起始值。到目前为止,我能想到的唯一解决方案是添加 4 个 __m128i 变量。例如,他们将是
/* pseudoSSE code... */
__m128i src = (125,125,125,125)
__m128i operands =(5,10,-1,-5)
/* Here I omit the partitioning of operands into add1,..add4 for brevity */
__m128i add1 = (+05,+05,+05,+05)
__m128i add2 = (+00,+10,+10,+10)
__m128i add3 = (+00,+00,-01,-01)
__m128i add4 = (+00,+00,+00,-05)
__m128i res1 = _mm_add_epu32( add1, add2 )
__m128i res2 = _mm_add_epu32( add3, add4 )
__m128i res3 = _mm_add_epu32( res1, add2 )
__m128i res = _mm_add_epu32( res3, src )
这样,我得到了我想要的。对于这个解决方案,我需要设置所有 add_ 变量,然后执行 4 次加法。我真正要问的是这是否可以更快地完成。通过一些不同的算法或者使用一些我还不知道的专门的 SSE 函数(比如 _mm_cumulative_sum())。非常感谢。
【问题讨论】:
你怎么可能得出使用 SSE 会很慢的结论???你测试过吗? 我不明白。你说你有一个 32 位的值,但你显示有四个 8 位的值?那 5 行“代码”应该表示是什么意思?为什么要加载 4 次值? @jalf:这 5 行只是一个例子,我希望它尽可能具有指导性,因此我用小数字写了它。一般来说,真的会有32b个u_integers。 @jalf: 这些是我想要做的一个例子..如何获得 (125+5, 125+5+10, 125+5+10-1, 125 +5+10-1-5)。 我的提示:查看 Intel C++ Intrinsic Reference 以获得适合您需求的指令:software.intel.com/sites/default/files/m/9/4/c/8/e/… 【参考方案1】:您可以添加更多并行性并使用 3 个加法而不是 4 个:
const __m128i src = _mm_set1_epi32(125);
const __m128i operands = _mm_set_epi32(5,10,-1,-5);
const __m128i shift1 =
_mm_add_epi32(operands,
_mm_and_si128(_mm_shuffle_epi32(operands, 0xF9),
_mm_set_epi32(0,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF)));
const __m128i shift2 =
_mm_add_epi32(shift1,
_mm_and_si128(_mm_shuffle_epi32(shift1, 0xFE),
_mm_set_epi32(0,0,0xFFFFFFFF,0xFFFFFFFF)));
const __m128i res = _mm_add_epi32(src, shift2);
这里使用的是 SSE2 指令集。使用较新的指令集,您可以将 _mm_and_si128/_mm_shuffle_epi32 替换为单个指令,例如 _mm_shuffle_epi8。
累计和计算如下所示:2次加法:
a b c d
+ a b c
------------------
a a+b b+c c+d
+ a a+b
------------------
a a+b a+b+c a+b+c+d
SSE 不适合这样的任务。它的性能只对“垂直”操作有好处,但是对于“水平”操作,这里需要做很多额外的工作。
【讨论】:
叶夫根尼,谢谢!我编写了一个应用程序,试图分析哪个版本运行速度最快,但是 MSVC 2010 似乎为我删除了所有代码(“优化”)。我想发布结果,但为此我需要先问:当我关闭优化时,每个版本的运行时结果是否告诉我效率?我的意思是,当我关闭优化时,它不会以某种方式损害 SSE 功能的效率吗?因为在关闭优化的情况下,nonSSE 的运行速度比 SSE 快 4 倍。但是,我不知道如何告诉编译器不要删掉我的“无效”代码(重复 1M 次)。非常感谢。 @DanBencik:如果关闭优化,它会损害 SSE 和非 SSE 函数的效率。所以在没有优化的情况下测试性能并不是一个好主意。要告诉编译器不要剪掉“非生产性”代码,您可以将一些计算结果累积到一个变量中,然后将其打印到控制台,或将其发送到其他外部函数,或写入 volatile 变量。跨度> 【参考方案2】:感谢大家的帮助。为了找出最快的版本,我编写了一个测试应用程序。
1/ 非 SSE 版本的功能完全符合您的预期。
int iRep;
int iCycle;
int iVal = 25;
int a1, a2, a3, a4;
int dst1 [4];
for ( iCycle = 0; iCycle < CYCLE_COUNT; iCycle++ )
for ( iRep = 0; iRep < REP_COUNT; iRep++ )
a1 = a2 = a3 = a4 = iRep;
dst1[0] = iVal + a1;
dst1[1] = dst1[0] + a2;
dst1[2] = dst1[1] + a3;
dst1[3] = dst1[2] + a4;
2/ SSE-4 的添加符合我的建议,即
__m128i _a1, _a2, _a3, _a4;
__m128i _res1, _res2, _res3;
__m128i _val;
__m128i _res;
for ( iCycle = 0; iCycle < CYCLE_COUNT; iCycle++ )
for ( iRep = 0; iRep < REP_COUNT; iRep++ )
a1 = a2 = a3 = a4 = iRep;
_val = _mm_set1_epi32( iVal );
_a1 = _mm_set_epi32 (a1, a1, a1, a1 );
_a2 = _mm_set_epi32 (a2, a2, a2, 0 );
_a3 = _mm_set_epi32 (a3, a3, 0 , 0 );
_a4 = _mm_set_epi32 (a4, 0 , 0 , 0 );
_res1 = _mm_add_epi32( _a1, _a2 );
_res2 = _mm_add_epi32( _a3, _a4 );
_res3 = _mm_add_epi32( _val, _res1 );
_res = _mm_add_epi32( _res3, _res2 );
3/ SSE-3 的添加符合 Evgeny 的建议,即
__m128i shift1, shift2, operands ;
for ( iCycle = 0; iCycle < CYCLE_COUNT; iCycle++ )
for ( iRep = 0; iRep < REP_COUNT; iRep++ )
a1 = a2 = a3 = a4 = iRep;
_val = _mm_set1_epi32( iVal );
operands = _mm_set_epi32(a1,a2,a3,a4);
shift1 = _mm_add_epi32( operands,
_mm_and_si128(_mm_shuffle_epi32(operands, 0xF9), _mm_set_epi32(0,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF) ));
shift2 = _mm_add_epi32( shift1,
_mm_and_si128(_mm_shuffle_epi32(shift1, 0xFE), _mm_set_epi32(0,0,0xFFFFFFFF,0xFFFFFFFF) ));
_res = _mm_add_epi32(_val, shift2);
结果
#define REP_COUNT 100000
#define CYCLE_COUNT 100000
是
non-SSE -> 6.118s
SSE-4additions -> 20.775s
SSE-3additions -> 14.873s
相当令人惊讶...
【讨论】:
以上是关于上证所累计求和的主要内容,如果未能解决你的问题,请参考以下文章
数据可视化之DAX篇(二十三)ALLEXCEPT应用示例:更灵活的累计求和