【交易技术前沿】访存延迟对微秒级应用开发的影响 / 毛银杰

网友投稿 791 2023-03-21

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

【交易技术前沿】访存延迟对微秒级应用开发的影响 / 毛银杰

毛银杰:恒生电子股份有限公司摘 要:本文简单地阐述了访存延迟的形成原因,列出各种存储类型的典型延迟值,并对CPU的缓存机制做了概要性的介绍;通过一个实例,形象地展示内存访问的具体延迟以加深读者的印象。最后,对开发微秒级的应用提出一些建议性的意见,以降低应用的延迟。关键词:访存延迟;高速缓存;缓存命中;预读;工作集

1 前沿

对于一个毫秒级延迟的应用而言,程序员应该更多的考虑应用的整体逻辑架构的调整,减少进程间的交互次数,减少或者优化数据库的访问;调整系统的物理部署,尽量将不同应用部署在同一主机或者增加网络带宽以求减少网络的延迟因素,如有可能改变系统间的应用层交互协议以求加快协议报文的处理等。通常来说,上述的几种调整方案具有立竿见影的效果,一般只需耗费程序员几天的功夫,且调整是可控的,但带来的收益是巨大的,可以达到减少数百微秒甚至数毫秒访存延迟的效果。本文探讨的内容并不适合于毫秒级应用的性能调优。对于微秒级延迟的应用(例如典型的20us左右延迟的应用)来说,其逻辑架构和物理部署一般都已经达到或者接近最优化,难以在这方面再取得突破。此时,程序员应该更多地审视自己的代码,以求发现瓶颈之所在,尽可能地再次降低几个微秒甚至十几微秒的延迟,以达到个位数的微秒级延迟的目标。考虑到我们的前提是逻辑架构和物理部署已经最优化,因此这里可以忽略所有I/O相关的因素。考虑如下事实:

证券业务一般不太涉及大量的复杂的逻辑运算CPU基本以摩尔定律的速度在发展:每18个月其运算性能就提升一倍,现在CPU的主频已经超过4GHz;但是内存的速度提高却不大;更为关键的是,几十年来内存的延迟几乎没怎么降低。由此导致了高速的CPU和低速的内存之间的矛盾。

根据以上两点,我们基本可以确定:在一个微秒级的应用中,访存延迟将成为整个应用延迟的主要瓶颈;解决了访存延迟就等于解决了应用延迟。

2 访存延迟分析

本文从内存访问入手分析纳秒级的延迟(多个纳秒级延迟累积到微秒级延迟)。绝大多数的程序员都知道访存(内存访问)是一个比较慢的操作(相对于CPU运算速度来说),但是,究竟慢到什么程度?为什么会慢?如何尽可能地降低访存延迟?可能大多数的程序员都没有一个定量的概念,哪怕是对于访存延迟究竟达到一个怎样的数量级(是几纳秒?几十纳秒?几百纳秒?)都没有一个直观的认识。究其原因,是因为访存延迟的不直观,我们一般无法直接观察到访存延迟,由于CPU高速缓存的存在,CPU高效的缓存机制和预读机制将大量的访存成本隐藏了,导致在一般情况下我们都可以享受到低延迟的访存操作,从而忽视了访存的巨大成本。看不到的东西总是虚无缥缈的,难以被认识的,为了让我们加深访存对微秒级应用的重大影响的认识,我们首先必须观察到它们,因此本文的主要工作就是把访存延迟“揪”出来示众。在揪出访存延迟之前,我们先来科普一下访存延迟的来龙去脉,再大致了解一下CPU的缓存机制。从理论上来分析,访存为什么会慢呢?当前我们使用的大容量内存均是DRAM内存,而DRAM是使用电容器来保持状态的,内存的访问牵扯到电容器的充放电,所以DRAM 的延迟是由其物理属性所决定的,除非在原理和技术上有革命性的突破,否则访存延迟不可减少。这里我们简单介绍一下一次内存访问在电气层上会发生哪些动作(一些主要动作),以及这些动作的时序:

激活并等待一段时间才能预充电。这段等待时间为t_RAS个总线周期发出预充电指令后,等待t_RP个总线周期发出行选信号,给出行地址,等待t_RCD个总线周期发出列选信号,给出列地址,等待t_CL个总线周期

3 实例分析

备注:

@ 刚好是一条缓存线长度的步长,这样使得缓存线整体加载的优势无法体现。@@ L1缓存尺寸(这样使得CPU的预读在L1层面无法体现优势)@@@ L2缓存尺寸(这样使得CPU的预读在L2层面无法体现优势)@@@@ L3缓存尺寸(这样使得CPU的预读在L3层面无法体现优势)上述测试数据表明,该测试环境内存的真正访存延迟,约为200个CPU周期(大约为100ns)。

4 结论

经过上述的分析,我们的微秒级应用,可以从以下几方面来考虑降低访存延迟:尽可能降低工作集内存大小:当某个业务逻辑处理时,需要遍历访问的数据集合越小越好,哪怕只是完整装载进L3也是好的,当然,最好是能完整装载进L1。示例:在证券业务中,我们在处理某个客户的订单信息时,订单信息按机构全局存放在一个连续的内存空间和按客户连续存放,其处理的延迟将是一个数量级甚至两个数量级的差异。(因为对于一个机构而言,其订单信息是连续分布在数以几G的内存空间中,即其工作集是几个G,而当订单信息按客户存放,那么其工作集可能只是几十KB,完全可以完整装在进L1)在一个数据结构中,有业务相关性的字段紧密定义,以充分利用缓存线的优势。在一个数据结构中,最先需要被访问的字段放置在第一位如果可能,关键数据结构尽量定义成按缓存线对齐针对业务特色组织数据结构经常成对访问的数据紧密布局数字列表项目常用数据和不常用数据分离布局只读数据和读写数据分离布局

5 附录

以下测试代码大致逻辑:

int main(intiArgc, void** Argv)  {int  iStep=atoi( (char*)Argv[1]);int  iIntCounts=1024*1024*1024*1;       //  1G个int,4G内存int* lpiValue=(int*)malloc(sizeof(int)*iIntCounts);register int i;for(i=0; i<iIntCounts; i++)  //  分配4G内存且使用他,使其加载到物理内存{    lpiValue[i]=i;}//  分配26M内存并赋值,将前面的内存空间逐出缓存int  iTmpSize=26*1024*1024/sizeof(int);int* lpiTemp=(int*)malloc(sizeof(int)*iTmpSize);for(i=0; i<iTmpSize; i++){    lpiTemp[i]=i;}int  iLoopCounts;iLoopCounts=iIntCounts/iStep;   //  尽可能多访问,减少噪声污染iLoopCounts--;int  j=0;  __u64 tmbegin=get_rdtsc();for(i=0; i<iLoopCounts; i++){    if(lpiValue[j]!=2000000000) //  永远不可能满足    {        j+=iStep;        continue;    }    break;  }  __u64 dwDiff=GetDiff(tmbegin);  int iDiff=dwDiff/iLoopCounts;printf("iDiff:%d   iLoopCounts:%d\n", iDiff, iLoopCounts);return 0;  }  

int main(intiArgc, void** Argv) {int iStep=atoi( (char*)Argv[1]);int iIntCounts=1024*1024*1024*1; // 1G个int,4G内存int* lpiValue=(int*)malloc(sizeof(int)*iIntCounts);register int i;for(i=0; i

作者简介

毛银杰,恒生电子研发中心资深技术专家、恒生电子技术专家俱乐部成员,长期从事高性能、大并发消息通信和应用系统的研究。

上一篇:Linux安装mysql——源码安装
下一篇:告警信息处理系统(告警信息处理系统包括)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~