我有一个执行memcpy的函数,但它占用了大量的周期。有没有比使用memcpy移动内存更快的替代方法?
发布于 2017-07-06 20:20:08
这是对存在AVX2指令集的x86_64的回答。尽管类似的东西可能适用于带有SIMD的ARM/AArch64。
在完全填满单个内存通道(2个插槽,每个插槽16 GB DDR4 )的Ryzen 1800X上,以下代码比MSVC++2017编译器上的memcpy()
快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,最后一次减少1字节。此外,在中间,2-3个__m256i
值的本地数组可以用作来自源的对齐读取和到目标的对齐写入之间的代理。
#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();
}
这段代码的一个关键特性是,它在复制时跳过CPU cache :当涉及CPU cache时(即使用不带_stream_
的AVX指令),在我的系统上复制速度会下降几倍。
我的DDR4内存是2.6 CL13 CL13。因此,当从一个阵列复制8 8GB的数据到另一个阵列时,我获得了以下速度:
memcpy(): 17,208,004,271 bytes/sec.
Stream copy: 26,842,874,528 bytes/sec.
请注意,在这些测量中,输入和输出缓冲区的总大小除以经过的秒数。因为对于数组的每个字节,有两次存储器访问:一次是从输入数组读取字节,另一次是将字节写入输出数组。换句话说,当从一个数组复制8 8GB到另一个数组时,您需要执行16 8GB的内存访问操作。
适度的多线程可以进一步提高大约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
代码是:
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();
}
}
发布于 2010-06-03 16:53:49
请向我们提供更多细节。在i386架构上,memcpy很可能是最快的复制方式。但是在编译器没有优化版本的不同架构上,最好重写memcpy函数。我用汇编语言在一个定制的ARM架构上做了这件事。如果你转移了大量的内存,那么DMA可能就是你想要的答案。
请提供更多细节-架构,操作系统(如果相关)。
发布于 2010-06-03 15:08:15
通常,编译器附带的标准库将以最快的方式为目标平台实现memcpy()
。
https://stackoverflow.com/questions/2963898
复制相似问题