优化SSE代码

7
我正在为一个需要提高性能的Java应用程序开发一个C模块(请参见Improving performance of network coding-encoding),这需要一些性能改进。我尝试使用SSE指令集来优化代码,它比Java版本快了大约20%。然而,它仍然不够快。
不幸的是,我对优化C代码的经验有限。因此,我希望能够得到一些关于如何改进当前实现的想法。
构成热点的内部循环如下:
for (i = 0; i < numberOfGFVectorsInFragment; i++)   {

        // Load the 4 GF-elements from the message-fragment and add the log of the coefficeint to them.
        __m128i currentMessageFragmentVector = _mm_load_si128 (currentMessageFragmentPtr);
        __m128i currentEncodedResult = _mm_load_si128(encodedFragmentResultArray);

        __m128i logSumVector = _mm_add_epi32(coefficientLogValueVector, currentMessageFragmentVector);

        __m128i updatedResultVector = _mm_xor_si128(currentEncodedResult, valuesToXor);
        _mm_store_si128(encodedFragmentResultArray, updatedResultVector);

        encodedFragmentResultArray++;
        currentMessageFragmentPtr++;
    }

2
你看过编译器生成的汇编代码了吗?那里可能还有一些进一步的优化空间。如果你无法让编译器执行它们,就手动实现并使用(注释良好的).s文件作为源文件,而不是.c文件。 - OrangeDog
这确实会削弱Java的“可移植性”方面。 - OrangeDog
@OrangeDog 我现在已经将汇编代码添加到问题中了。 - Yrlec
@OrangeDog 是的。我使用了以下构建标志: cl /c /Zi /nologo- /Wall /WX- /Ox /Ob2 /Oi /Ot /Oy- /D WIN32 /D NDEBUG /D _WINDLL /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /arch:SSE2 /fp:precise /Zc:wchar_t /Zc:forScope /Yc"StdAfx.h" /Fp"Release\NetworkCodingAccelerator.pch" /Fo"Release\" /Fd"Release\vc100.pdb" /Gd /TC /analyze- /errorReport:prompt Stdafx.c你认为我需要添加什么吗?对于 MSVC 的编译器设置,我是新手。 - Yrlec
我建議使用純C編寫並與Intel編譯器編譯。這將使用sse / avx或其他技術,展開循環等等。最好使用概要反饋完成。 - Chris
显示剩余2条评论
2个回答

7
即使我没有查看汇编代码,我也能立即看出瓶颈来自于4个元素的收集内存访问和_mm_set_epi32打包操作。在内部,_mm_set_epi32,在您的情况下可能会被实现为一系列的unpacklo/hi指令。
这个循环中大部分的“工作”来自于打包这些4个内存访问。如果没有SSE4.1,我敢说这个循环在未向量化但展开的情况下可能会更快。
如果您愿意使用SSE4.1,可以尝试这样做。它可能会更快,也可能不会:
    int* logSumArray = (int*)(&logSumVector);

    __m128i valuesToXor = _mm_cvtsi32_si128(expTable[*(logSumArray++)]);
    valuesToXor = _mm_insert_epi32(valuesToXor, expTable[*(logSumArray++)], 1);
    valuesToXor = _mm_insert_epi32(valuesToXor, expTable[*(logSumArray++)], 2);
    valuesToXor = _mm_insert_epi32(valuesToXor, expTable[*(logSumArray++)], 3);

我建议展开循环至少4次,并交错所有指令,以使该代码有机会表现良好。
你真正需要的是英特尔的AVX2 gather/scatter指令。但那还要几年时间...

谢谢,但是你的代码产生了以下错误:Error C2275: '__m128i' : illegal use of this type as an expression。这个错误似乎与覆盖valuesToXor的值有关,因为如果我删除最后三行,它就可以编译(错误消息总是指向设置valuesToXor的最后一行表达式之后)。 - Yrlec
你是否包含了SSE4.1头文件<smmintrin.h> - Mysticial
1
@Mystical,我甚至看到过MSVC实现_mm_set_函数的方式是将其复制4次到临时对齐内存中,然后从内存中加载到寄存器中,当我看到这种情况时只能摇头。 - Christian Rau
@Christian Rau:我也看过那个...太搞笑了...XD - Mysticial
@Mysticial,我现在解决了。我上面的一些代码搞砸了,导致出现了这个问题。不过性能还是没有提高。:( 无论如何,还是谢谢! - Yrlec
显示剩余5条评论

1

谢谢!他们似乎使用了许多我也用过的类似技巧(例如双重对数表来避免额外的模数运算)。不过我会检查区域代码,看看他们是否还有其他优化。 - Yrlec
阅读文档后,我认为他们的代码实际上更慢。当将乘法和异或运算应用于一个区域时,他们只能达到约167 MB/s的速度。而我在相同操作中可以达到约1GB/s的速度(可能是因为我的CPU更快,但差距仍然很大)。问题在于我每秒要执行这个操作超过1000次,所以实际输出吞吐量只有约1Mb/s。 - Yrlec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接