Pru*_*ica 0 c++ performance arm simd intrinsics
当 NEON 向量指令在 ARM 设备上可用时,我一直在寻找复制各种数据量的快速方法。
\n我做了一些基准测试,并得到了一些有趣的结果。我试图理解我所看到的东西。
\n我有四个版本来复制数据:
\n逐个元素复制:
\nfor (int i = 0; i < size; ++i)\n{\n copy[i] = orig[i];\n}\nRun Code Online (Sandbox Code Playgroud)\n此代码将四个值加载到临时寄存器中,然后将该寄存器复制到输出。
\n因此,负载数量减少了一半。可能有一种方法可以跳过临时寄存器并将负载减少四分之一,但我还没有找到方法。
\nint32x4_t tmp;\nfor (int i = 0; i < size; i += 4)\n{\n tmp = vld1q_s32(orig + i); // load 4 elements to tmp SIMD register\n vst1q_s32(©2[i], tmp); // copy 4 elements from tmp SIMD register\n}\nRun Code Online (Sandbox Code Playgroud)\nmemcpy,使用memcpy,但一次复制 4 个元素。这是为了与 NEON 版本进行比较。
for (int i = 0; i < size; i+=4)\n{\n memcpy(orig+i, copy3+i, 4);\n}\nRun Code Online (Sandbox Code Playgroud)\nmemcpymemcpy与全量数据一起使用。
memcpy(orig, copy4, size);\nRun Code Online (Sandbox Code Playgroud)\n我使用2^16值进行的基准测试给出了一些令人惊讶的结果:
1. Baseline time = 3443[\xc2\xb5s]\n2. NEON time = 1682[\xc2\xb5s]\n3. memcpy (stepped) time = 1445[\xc2\xb5s]\n4. memcpy time = 81[\xc2\xb5s]\nRun Code Online (Sandbox Code Playgroud)\nNEON 时间的加速是预期的,但更快的步进memcpy时间令我惊讶。时间更是如此4。
为什么memcpy做得这么好?它在引擎盖下使用 NEON 吗?或者是否存在我不知道的有效内存复制指令?
这个问题讨论了 NEON 与memcpy(). 然而,我认为答案并没有充分探讨为什么 ARMmemcpy实现运行得这么好
完整的代码清单如下:
\n#include <arm_neon.h>\n#include <vector>\n#include <cinttypes>\n\n#include <iostream>\n#include <cstdlib>\n#include <chrono>\n#include <cstring>\n\nint main(int argc, char *argv[]) {\n\n int arr_size;\n if (argc==1)\n {\n std::cout << "Please enter an array size" << std::endl;\n exit(1);\n }\n\n int size = atoi(argv[1]); // not very C++, sorry\n std::int32_t* orig = new std::int32_t[size];\n std::int32_t* copy = new std::int32_t[size];\n std::int32_t* copy2 = new std::int32_t[size];\n std::int32_t* copy3 = new std::int32_t[size];\n std::int32_t* copy4 = new std::int32_t[size];\n\n\n // Non-neon version\n std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();\n for (int i = 0; i < size; ++i)\n {\n copy[i] = orig[i];\n }\n std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();\n std::cout << "Baseline time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[\xc2\xb5s]" << std::endl;\n\n // NEON version\n begin = std::chrono::steady_clock::now();\n int32x4_t tmp;\n for (int i = 0; i < size; i += 4)\n {\n tmp = vld1q_s32(orig + i); // load 4 elements to tmp SIMD register\n vst1q_s32(©2[i], tmp); // copy 4 elements from tmp SIMD register\n }\n end = std::chrono::steady_clock::now();\n std::cout << "NEON time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[\xc2\xb5s]" << std::endl;\n\n\n // Memcpy example\n begin = std::chrono::steady_clock::now();\n for (int i = 0; i < size; i+=4)\n {\n memcpy(orig+i, copy3+i, 4);\n }\n end = std::chrono::steady_clock::now();\n std::cout << "memcpy time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[\xc2\xb5s]" << std::endl;\n\n\n // Memcpy example\n begin = std::chrono::steady_clock::now();\n memcpy(orig, copy4, size);\n end = std::chrono::steady_clock::now();\n std::cout << "memcpy time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[\xc2\xb5s]" << std::endl;\n\n return 0;\n}\n\nRun Code Online (Sandbox Code Playgroud)\n