在一个非常大的阵列上是否有任何可能的随机访问优化(我目前使用uint8_t
,我问的是什么更好)
uint8_t MyArray[10000000];
Run Code Online (Sandbox Code Playgroud)
当数组中任何位置的值为
那么,有没有什么比uint8_t
用于此的数组更好?它应该尽可能快地以随机顺序循环遍历整个阵列,并且这对RAM带宽非常重,因此当有多个线程同时为不同的阵列执行时,当前整个RAM带宽很快就饱和了.
我问,因为实际上已知除了5%之外几乎所有的值都是0或1时,拥有如此大的数组(10 MB)效率非常低效.因此当数组中所有值的95%时实际上只需要1位而不是8位,这会将内存使用量减少几乎一个数量级.感觉必须有一个更节省内存的解决方案,这将大大减少为此所需的RAM带宽,因此随机访问也会明显更快.
摘要:
memcpy似乎无法在真实或测试应用程序中在我的系统上传输超过2GB /秒.我该怎么做才能获得更快的内存到内存副本?
详细信息:
作为数据捕获应用程序的一部分(使用一些专用硬件),我需要将大约3 GB /秒的临时缓冲区复制到主内存中.为了获取数据,我为硬件驱动程序提供了一系列缓冲区(每个2MB).硬件将数据DMA数据到每个缓冲区,然后在每个缓冲区已满时通知我的程序.我的程序清空缓冲区(memcpy到另一个更大的RAM块),并将处理后的缓冲区重新发送到卡中再次填充.我遇到了memcpy足够快地移动数据的问题.似乎内存到内存的副本应该足够快,以便在我运行的硬件上支持3GB /秒.Lavalys EVEREST给了我一个9337MB /秒的内存复制基准测试结果,但即使在一个简单的测试程序中,我也无法通过memcpy获得接近这些速度的任何数据.
我通过在缓冲区处理代码中添加/删除memcpy调用来隔离性能问题.没有memcpy,我可以运行全数据速率 - 大约3GB /秒.启用memcpy后,我被限制在大约550Mb /秒(使用当前编译器).
为了在我的系统上对memcpy进行基准测试,我编写了一个单独的测试程序,它只是在某些数据块上调用memcpy.(我已经发布了下面的代码)我在我正在使用的编译器/ IDE(National Instruments CVI)以及Visual Studio 2010中都运行了这个.虽然我目前没有使用Visual Studio,但我愿意如果它将产生必要的性能,则进行切换.然而,在盲目地移动之前,我想确保它能解决我的memcpy性能问题.
Visual C++ 2010:1900 MB /秒
NI CVI 2009:550 MB /秒
虽然我并不感到惊讶,CVI比Visual Studio的显著慢,我很惊讶的是,memcpy的性能是这种低.虽然我不确定这是否可以直接比较,但这远低于EVEREST基准带宽.虽然我不需要那么高的性能水平,但至少需要3GB /秒.当然,标准库的实现不会比EVEREST使用的更糟糕!
在这种情况下,如果有的话,我可以做些什么来更快地使用memcpy?
硬件细节:AMD Magny Cours-4x八核128 GB DDR3 Windows Server 2003 Enterprise X64
测试程序:
#include <windows.h>
#include <stdio.h>
const size_t NUM_ELEMENTS = 2*1024 * 1024;
const size_t ITERATIONS = 10000;
int main (int argc, char *argv[])
{
LARGE_INTEGER start, stop, frequency;
QueryPerformanceFrequency(&frequency);
unsigned short * src = …
Run Code Online (Sandbox Code Playgroud) 我正在测试台式机和服务器上的内存带宽。
Sklyake desktop 4 cores/8 hardware threads
Skylake server Xeon 8168 dual socket 48 cores (24 per socket) / 96 hardware threads
Run Code Online (Sandbox Code Playgroud)
系统的峰值带宽为
Peak bandwidth desktop = 2-channels*8*2400 = 38.4 GB/s
Peak bandwidth server = 6-channels*2-sockets*8*2666 = 255.94 GB/s
Run Code Online (Sandbox Code Playgroud)
我正在使用自己的STREAM三合一函数来测量带宽(稍后将提供完整代码)
void triad(double *a, double *b, double *c, double scalar, size_t n) {
#pragma omp parallel for
for(int i=0; i<n; i++) a[i] = b[i] + scalar*c[i];
}
Run Code Online (Sandbox Code Playgroud)
这是我得到的结果
Bandwidth (GB/s)
threads Desktop Server
1 28 16
2(24) 29 146 …
Run Code Online (Sandbox Code Playgroud) 仅供参考,以下是我从Nvidia获得的规格
http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-680/specifications
http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-titan/specifications
请注意,内存速度/内存时钟在其网站上是相同的,并以Gbps为单位.
谢谢!
I just noticed a pieces of my code exhibit different performance when copying memory. A test showed that a memory copying performance degraded if the address of destination buffer is greater than address of source. Sounds ridiculous, but the following code shows the difference (Delphi):
const MEM_CHUNK = 50 * 1024 * 1024;
ROUNDS_COUNT = 100;
LpSrc := VirtualAlloc(0,MEM_CHUNK,MEM_COMMIT,PAGE_READWRITE);
LpDest := VirtualAlloc(0,MEM_CHUNK,MEM_COMMIT,PAGE_READWRITE);
QueryPerformanceCounter(LTick1);
for i := 0 to ROUNDS_COUNT - 1 do
CopyMemory(LpDest,LpSrc,MEM_CHUNK);
QueryPerformanceCounter(LTick2);
// show timings
QueryPerformanceCounter(LTick1);
for i …
Run Code Online (Sandbox Code Playgroud) 这个问题专门针对现代x86-64缓存一致性架构 - 我很欣赏其他CPU的答案可能会有所不同.
如果我写入内存,MESI协议要求首先将缓存行读入缓存,然后在缓存中进行修改(将值写入缓存行,然后将其标记为脏).在较旧的写入微架构中,这将触发高速缓存行被刷新,在写回期间,被刷新的高速缓存行可能会延迟一段时间,并且一些写入组合可能在两种机制下发生(更可能是回写) .我知道这与访问相同缓存行数据的其他核心如何交互 - 缓存监听等.
我的问题是,如果商店恰好匹配缓存中已有的值,如果没有单个位被翻转,那么任何英特尔微架构都会注意到这一点并且不将该行标记为脏,从而可能将该行标记为独占,以及在某些时候跟随的回写内存开销?
当我向更多的循环进行矢量化时,我的矢量化操作组合基元不会明确地检查值的变化,并且在CPU/ALU中这样做似乎很浪费,但我想知道底层缓存电路是否可以在没有显式编码的情况下完成(例如,商店微操作或缓存逻辑本身).由于跨多个内核的共享内存带宽变得更加成为资源瓶颈,这似乎是一种越来越有用的优化(例如,重复调整相同的内存缓冲区 - 如果它们已经存在,我们不会重新读取RAM中的值在缓存中,但强制写回相同的值似乎很浪费).回写缓存本身就是对这类问题的承认.
我可以礼貌地要求阻止"在理论上"或"它确实无关紧要"的答案 - 我知道记忆模型是如何工作的,我正在寻找的是关于如何写出相同价值的硬性事实(而不是避免一个商店)将影响内存总线的争用你可以安全地假设是一台运行多个工作负载的机器几乎总是受内存带宽限制.另一方面,解释为什么芯片不这样做的确切原因(我悲观地假设他们没有这样做)将具有启发性......
更新: 这里的预期线路上的一些答案https://softwareengineering.stackexchange.com/questions/302705/are-there-cpus-that-perform-this-possible-l1-cache-write-optimization但仍然很多推测"它必须很难,因为它没有完成",并说如何在主CPU核心中这样做会很昂贵(但我仍然想知道为什么它不能成为实际缓存逻辑本身的一部分).
我的CPU的规格说它应该为内存带来5.336GB/s的带宽.为了测试这个,我写了一个简单的程序,在一个大数组上运行memset(或memcpy)并报告时间.我在memset上显示3.8GB/s,在memcpy上显示1.9GB/s. http://en.wikipedia.org/wiki/Intel_Core_(microarchitecture)说我的Q9400应该达到5.336MB/s.怎么了?
我试过用赋值循环替换memset或memcpy.我已经google了一下,试图了解内存对齐情况.我尝试过不同的编译器标志.我花了很多时间在这上面尴尬.感谢您的任何帮助,您可以提供!
我正在使用Ubuntu 12.04和libc-dev版本2.15-0ubuntu10.5和内核3.8.0-37-generic
代码:
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#define numBytes ((long)(1024*1024*1024))
#define numTransfers ((long)(8))
int main(int argc,char**argv){
if(argc!=3){
printf("Usage: %s BLOCK_SIZE_IN_BYTES NUMBER_OF_BLOCKS_TO_TRANSFER\n",argv[0]);
return -1;
}
char*__restrict__ source=(char*)malloc(numBytes);
char*__restrict__ dest=(char*)malloc(numBytes);
struct timespec start,end;
long totalTimeMs;
int i;
clock_gettime(CLOCK_MONOTONIC_RAW,&start);
for(i=0;i<numTransfers;++i)
memset(source,0,numBytes);
clock_gettime(CLOCK_MONOTONIC_RAW,&end);
totalTimeMs=(end.tv_nsec-start.tv_nsec)*.000001+1000*(end.tv_sec-start.tv_sec);
printf("memset %ld bytes %ld times (%.2fGB total) in %ldms (%.3fGB/s). ",numBytes,numTransfers,numBytes/1024.0/1024/1024*numTransfers,totalTimeMs,numBytes/1024.0/1024/1024*1000*numTransfers/totalTimeMs);
clock_gettime(CLOCK_MONOTONIC_RAW,&start);
for(i=0;i<numTransfers;++i)
memcpy( dest, source, numBytes);
clock_gettime(CLOCK_MONOTONIC_RAW,&end);
totalTimeMs=(end.tv_nsec-start.tv_nsec)*.000001+1000*(end.tv_sec-start.tv_sec);
printf("memcpy %ld bytes %ld times (%.2fGB total) in %ldms (%.3fGB/s).\n",numBytes,numTransfers,numBytes/1024.0/1024/1024*numTransfers,totalTimeMs,numBytes/1024.0/1024/1024*1000*numTransfers/totalTimeMs);
free(source);
free(dest);
return …
Run Code Online (Sandbox Code Playgroud) 说我有这样的玩具循环
float x[N];
float y[N];
for (int i = 1; i < N-1; i++)
y[i] = a*(x[i-1] - x[i] + x[i+1])
Run Code Online (Sandbox Code Playgroud)
我假设我的缓存行是64字节(即足够大).然后我将(每帧)基本上2次访问RAM和3 FLOP:
x[i-1], x[i], x[i+1]
y[i]
操作强度是错误的
OI = 3 FLOP /(2*4 BYTE)
如果我做这样的事情会发生什么
float x[N];
for (int i = 1; i < N-1; i++)
x[i] = a*(x[i-1] - x[i] + x[i+1])
Run Code Online (Sandbox Code Playgroud)
请注意,现在已经没有y
了.这是否意味着我现在只有一个RAM访问权限
x[i-1], x[i], x[i+1]
,存储x[i]
或仍然2个RAM访问
x[i-1], x[i], x[i+1]
x[i]
因为在任何一种情况下操作强度OI …
在现代多核平台上,内存带宽有限的应用程序的并行性能通常无法随着核心数量的增加而很好地扩展。通常,在达到一定数量的核心时会观察到加速,但之后性能就会饱和。一个综合的例子是众所周知的STREAM benchmark,它通常用于报告可实现的内存带宽,即饱和点处的内存带宽。
请考虑在峰值内存带宽为 42.7GB/s (DDR3-1333) 的单个 Xeon E5-2680 上进行 STREAM 基准测试 (Triad) 的以下结果:
1 core 16 GB/s
2 cores 30 GB/s
3+ cores 36 GB/s
Run Code Online (Sandbox Code Playgroud)
STREAM 从 1 核扩展到 2 核时可以很好地扩展,但在 3 核以上,性能大致保持不变。
我的问题是:什么决定了单个CPU核心可以达到的内存带宽?由于这个问题肯定太宽泛,所以我将其缩小到上述架构:如何根据 E5-2680 的规格或通过查看硬件计数器等来预测具有 1 个线程的 STREAM 将为我提供 16 GB/s ?
为了测量主存储器的带宽,我提出了以下方法。
代码(针对英特尔编译器)
#include <omp.h>
#include <iostream> // std::cout
#include <limits> // std::numeric_limits
#include <cstdlib> // std::free
#include <unistd.h> // sysconf
#include <stdlib.h> // posix_memalign
#include <random> // std::mt19937
int main()
{
// test-parameters
const auto size = std::size_t{150 * 1024 * 1024} / sizeof(double);
const auto experiment_count = std::size_t{500};
//+/////////////////
// access a data-point 'on a whim'
//+/////////////////
// warm-up
for (auto counter = std::size_t{}; counter < experiment_count / 2; ++counter)
{
// garbage data allocation and memory page loading …
Run Code Online (Sandbox Code Playgroud) c++ benchmarking assembly performance-testing memory-bandwidth
memory-bandwidth ×10
performance ×4
c++ ×3
x86 ×3
assembly ×2
c ×2
memcpy ×2
optimization ×2
arrays ×1
avx512 ×1
benchmarking ×1
cpu-cache ×1
cvi ×1
delphi ×1
gpu ×1
memset ×1
multicore ×1
openmp ×1
x86-64 ×1