【问题标题】:Optimzing SSE-code优化 SSE 代码
【发布时间】:2011-12-09 08:16:47
【问题描述】:

我目前正在为需要一些性能改进的 Java 应用程序开发 C 模块(有关背景信息,请参阅 Improving performance of network coding-encoding)。我尝试使用 SSE-intrinsics 优化代码,它的执行速度比 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++;
    }

【问题讨论】:

  • 您查看过编译器生成的程序集吗?那里可能会有一些进一步的收获。如果您不能让编译器执行它们,请手动执行并使用(注释良好的) .s 作为源文件,而不是 .c
  • 虽然在某种程度上扼杀了 Java 的“可移植性”方面。
  • @OrangeDog 我现在已将程序集添加到问题中。
  • 您在生成该程序集时是否对编译器进行了任何优化?
  • @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 的编译器设置方面,我是新手。

标签: java c optimization sse intel-vtune


【解决方案1】:

即使不查看程序集,我也可以立即判断出瓶颈来自 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 收集/分散指令。但那是几年后的事了......

【讨论】:

  • 谢谢,但您的代码会产生以下错误:错误 C2275: '__m128i' : 非法将此类型用作表达式。该错误似乎与覆盖 valuesToXor 有关,因为如果我删除它编译的最后三行(错误消息始终指向设置 valuesToXor 的最后一行之后的表达式)。
  • 您是否包含了 SSE4.1 标头 &lt;smmintrin.h&gt;
  • @Mystical 我什至见过 msvc 实现的 _mm_set_ 函数作为 4 个副本到临时对齐的内存中,然后从内存中加载到寄存器中,看到这个我只能摇头。
  • @Mystical 是的。例如,如果我这样写: __m128i valuesToXor = _mm_cvtsi32_si128(expTable[*(logSumArray++)]); __m128i valuesToXor2 = _mm_insert_epi32(valuesToXor, expTable[*(logSumArray++)], 1);它可以编译,但如果我将其替换为: __m128i valuesToXor = _mm_cvtsi32_si128(expTable[*(logSumArray++)]); valuesToXor = _mm_insert_epi32(valuesToXor, expTable[*(logSumArray++)], 1);编译器创建错误消息之后的行。
  • @Christian Rau:我以前也见过……这很有趣……XD
【解决方案2】:

也许试试http://web.eecs.utk.edu/~plank/plank/papers/CS-07-593/。 名称中带有“区域”的功能据说速度很快。他们似乎没有使用任何类型的特殊指令集,但也许他们已经在其他方面进行了优化......

【讨论】:

  • 谢谢!他们似乎使用了许多与我类似的技巧(比如双日志表以避免额外的模数)。不过我会检查区域代码,看看它们是否还有其他优化。
  • 阅读文档后,我实际上认为他们的代码比较慢。当乘以和异或到一个区域时,它们达到了 ~167 MB/s。对于相同的操作,我实现了大约 1GB/s(我可能有更快的 CPU,但差异仍然很大)。问题是我这样做 >1000/s 所以实际的输出吞吐量只有 ~1Mb/s。
猜你喜欢
  • 2011-12-16
  • 2011-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多