Ton*_*ark 37 c performance memcpy
我有一个正在执行memcpy的功能,但它占用了大量的周期.有没有比使用memcpy移动内存更快的替代/方法?
nos*_*nos 119
memcpy可能是你在内存中复制字节的最快方法.如果你需要更快的东西 - 尝试找出一种不复制东西的方法,例如只交换指针,而不是数据本身.
Ser*_*tch 22
这是带有AVX2指令集的x86_64的答案.虽然类似的东西可能适用于带有SIMD的ARM/AArch64.
在完全填充单个内存通道的Ryzen 1800X上(2个插槽,每个16 GB DDR4),以下代码比memcpy()MSVC++ 2017编译器快1.56倍.如果您使用2个DDR4模块填充两个内存通道,即所有4个DDR4插槽都忙,则可能会使内存复制速度提高2倍.对于三(四)通道存储器系统,如果代码扩展到类似的AVX512代码,则可以进一步加快1.5(2.0)倍的存储器复制.对于AVX2而言,所有插槽都忙的三/四通道系统预计不会更快,因为要完全加载它们需要一次加载/存储超过32个字节(四通道为48字节,四通道为64字节)系统),而AVX2可以一次加载/存储不超过32个字节.虽然某些系统上的多线程可以在没有AVX512甚至AVX2的情况下缓解这种情况.
因此,这里的复制代码假定您正在复制大小为32的倍数的大块内存,并且该块是32字节对齐的.
对于非多重大小和非对齐块,可以编写序言/结尾代码,将块宽和尾部的宽度减小到16(SSE4.1),8,4,2,最后一个字节.同样在中间,__m256i可以使用2-3个值的本地数组作为来自源的对齐读取和对目标的对齐写入之间的代理.
#include <immintrin.h>
#include <cstdint>
/* ... */
void fastMemcpy(void *pvDest, void *pvSrc, size_t nBytes) {
assert(nBytes % 32 == 0);
assert((intptr_t(pvDest) & 31) == 0);
assert((intptr_t(pvSrc) & 31) == 0);
const __m256i *pSrc = reinterpret_cast<const __m256i*>(pvSrc);
__m256i *pDest = reinterpret_cast<__m256i*>(pvDest);
int64_t nVects = nBytes / sizeof(*pSrc);
for (; nVects > 0; nVects--, pSrc++, pDest++) {
const __m256i loaded = _mm256_stream_load_si256(pSrc);
_mm256_stream_si256(pDest, loaded);
}
_mm_sfence();
}
Run Code Online (Sandbox Code Playgroud)
此代码的一个关键特性是它在复制时跳过CPU缓存:当涉及CPU缓存时(即未_stream_使用AVX指令),复制速度在我的系统上会下降几次.
我的DDR4内存是2.6GHz CL13.因此,当将8GB数据从一个阵列复制到另一个阵列时,我获得了以下速度:
memcpy(): 17 208 004 271 bytes/sec.
Stream copy: 26 842 874 528 bytes/sec.
Run Code Online (Sandbox Code Playgroud)
请注意,在这些测量中,输入和输出缓冲区的总大小除以经过的秒数.因为对于数组的每个字节,有2个存储器访问:一个用于从输入数组读取字节,另一个用于将字节写入输出数组.换句话说,当从一个阵列复制8GB到另一个阵列时,您可以进行16GB的内存访问操作.
中等多线程可以进一步提高性能约1.44倍,因此memcpy()在我的机器上总增加超过2.55倍.以下是流复制性能取决于我的机器上使用的线程数的方式:
Stream copy 1 threads: 27114820909.821 bytes/sec
Stream copy 2 threads: 37093291383.193 bytes/sec
Stream copy 3 threads: 39133652655.437 bytes/sec
Stream copy 4 threads: 39087442742.603 bytes/sec
Stream copy 5 threads: 39184708231.360 bytes/sec
Stream copy 6 threads: 38294071248.022 bytes/sec
Stream copy 7 threads: 38015877356.925 bytes/sec
Stream copy 8 threads: 38049387471.070 bytes/sec
Stream copy 9 threads: 38044753158.979 bytes/sec
Stream copy 10 threads: 37261031309.915 bytes/sec
Stream copy 11 threads: 35868511432.914 bytes/sec
Stream copy 12 threads: 36124795895.452 bytes/sec
Stream copy 13 threads: 36321153287.851 bytes/sec
Stream copy 14 threads: 36211294266.431 bytes/sec
Stream copy 15 threads: 35032645421.251 bytes/sec
Stream copy 16 threads: 33590712593.876 bytes/sec
Run Code Online (Sandbox Code Playgroud)
代码是:
void AsyncStreamCopy(__m256i *pDest, const __m256i *pSrc, int64_t nVects) {
for (; nVects > 0; nVects--, pSrc++, pDest++) {
const __m256i loaded = _mm256_stream_load_si256(pSrc);
_mm256_stream_si256(pDest, loaded);
}
}
void BenchmarkMultithreadStreamCopy(double *gpdOutput, const double *gpdInput, const int64_t cnDoubles) {
assert((cnDoubles * sizeof(double)) % sizeof(__m256i) == 0);
const uint32_t maxThreads = std::thread::hardware_concurrency();
std::vector<std::thread> thrs;
thrs.reserve(maxThreads + 1);
const __m256i *pSrc = reinterpret_cast<const __m256i*>(gpdInput);
__m256i *pDest = reinterpret_cast<__m256i*>(gpdOutput);
const int64_t nVects = cnDoubles * sizeof(*gpdInput) / sizeof(*pSrc);
for (uint32_t nThreads = 1; nThreads <= maxThreads; nThreads++) {
auto start = std::chrono::high_resolution_clock::now();
lldiv_t perWorker = div((long long)nVects, (long long)nThreads);
int64_t nextStart = 0;
for (uint32_t i = 0; i < nThreads; i++) {
const int64_t curStart = nextStart;
nextStart += perWorker.quot;
if ((long long)i < perWorker.rem) {
nextStart++;
}
thrs.emplace_back(AsyncStreamCopy, pDest + curStart, pSrc+curStart, nextStart-curStart);
}
for (uint32_t i = 0; i < nThreads; i++) {
thrs[i].join();
}
_mm_sfence();
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("Stream copy %d threads: %.3lf bytes/sec\n", (int)nThreads, cnDoubles * 2 * sizeof(double) / nSec);
thrs.clear();
}
}
Run Code Online (Sandbox Code Playgroud)
INS*_*INS 11
请提供更多详情.在i386架构上,memcpy很可能是最快的复制方式.但是在编译器没有优化版本的不同架构上,最好重写memcpy函数.我是使用汇编语言在自定义ARM架构上完成的.如果您传输大块内存,那么DMA可能就是您正在寻找的答案.
请提供更多详细信息 - 架构,操作系统(如果相关).
小智 6
实际上,memcpy不是最快的方式,特别是如果你多次调用它.我还有一些我真正需要加速的代码,而memcpy很慢,因为它有太多不必要的检查.例如,它检查目标和源内存块是否重叠,以及是否应该从块后面而不是前面开始复制.如果你不关心这些考虑因素,你当然可以做得更好.我有一些代码,但这里可能是一个更好的版本:
如果搜索,您也可以找到其他实现.但是对于真正的速度,你需要一个装配版本.