现代cpu的合并写技术对程序的影响

   对于现代cpu而言,性能瓶颈则是对于内存的访问。cpu的速度往往都比主存的高至少两个数量级。因此cpu都引入了L1_cache与L2_cache,更加高端的cpu还加入了L3_cache.很显然,这个技术引起了下一个问题:

如果一个cpu在执行的时候需要访问的内存都不在cache中,cpu必须要通过内存总线到主存中取,那么在数据返回到cpu这段时间内(这段时间大致为cpu执行成百上千条指令的时间,至少两个数据量级)干什么呢? 答案是cpu会继续执行其他的符合条件的指令。比如cpu有一个指令序列 指令1  指令2  指令3 …, 在指令1时需要访问主存,在数据返回前cpu会继续后续的和指令1在逻辑关系上没有依赖的”独立指令”,cpu一般是依赖指令间的内存引用关系来判断的指令间的”独立关系”,具体细节可参见各cpu的文档。这也是导致cpu乱序执行指令的根源之一。

以上方案是cpu对于读取数据延迟所做的性能补救的办法。对于写数据则会显得更加复杂一点:

当cpu执行存储指令时,它会首先试图将数据写到离cpu最近的L1_cache, 如果此时cpu出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和cpu持平,其他的均明显低于cpu,L2_cache的速度大约比cpu慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,cpu就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。在请求L2_cache缓存行的所有权尚未完成时,cpu会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个cache line大小,一般都是64字节。这个缓冲区允许cpu在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了cpu写数据时cache miss时的性能影响。

当后续的写操作需要修改相同的缓存行时,这些缓冲区变得非常有趣。在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。 这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。当然,如果程序读取已被写入到该缓冲区的某些数据,那么在读取缓存数据之前会先去读取本缓冲区的。

经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache).如果我们能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。

从下面这个具体的例子来看吧:

下面一段测试代码,从代码本身就能看出它的基本逻辑。

#include <unistd.h>

#include <stdio.h>

#include <sys/time.h>

#include <stdlib.h>

#include <limits.h>

static const int iterations = INT_MAX;

static const int items = 1<<24;

static int mask;

static int arrayA[1<<24];

static int arrayB[1<<24];

static int arrayC[1<<24];

static int arrayD[1<<24];

static int arrayE[1<<24];

static int arrayF[1<<24];

static int arrayG[1<<24];

static int arrayH[1<<24];

double run_one_case_for_8()

{

double start_time;

double end_time;

struct timeval start;

struct timeval end;

int i = iterations;

gettimeofday(&start, NULL);

while(--i != 0)

{

int slot = i & mask;

int value = i;

arrayA[slot] = value;

arrayB[slot] = value;

arrayC[slot] = value;

arrayD[slot] = value;

arrayE[slot] = value;

arrayF[slot] = value;

arrayG[slot] = value;

arrayH[slot] = value;

}

gettimeofday(&end, NULL);

start_time = (double)start.tv_sec + (double)start.tv_usec/1000000.0;

end_time = (double)end.tv_sec + (double)end.tv_usec/1000000.0;

return end_time - start_time;

}

double run_two_case_for_4()

{

double start_time;

double end_time;

struct timeval start;

struct timeval end;

int i = iterations;

gettimeofday(&start, NULL);

while(--i != 0)

{

int slot = i & mask;

int value = i;

arrayA[slot] = value;

arrayB[slot] = value;

arrayC[slot] = value;

arrayD[slot] = value;

}

i = iterations;

while(--i != 0)

{

int slot = i & mask;

int value = i;

arrayG[slot] = value;

arrayE[slot] = value;

arrayF[slot] = value;

arrayH[slot] = value;

}

gettimeofday(&end, NULL);

start_time = (double)start.tv_sec + (double)start.tv_usec/1000000.0;

end_time = (double)end.tv_sec + (double)end.tv_usec/1000000.0;

return end_time - start_time;

}

int main()

{

mask = items -1;

int i;

printf("test begin---->\n");

for(i=0;i<3;i++)

{

printf(" %d, run_one_case_for_8: %lf\n", i, run_one_case_for_8());

printf(" %d, run_two_case_for_4: %lf\n", i, run_two_case_for_4());

}

printf("test end");

return 0;

}

相信很多人会认为run_two_case_for_4 的运行时间肯定要比run_one_case_for_8的长,因为至少前者多了一遍循环的i++操作。但是事实却不是这样:下面是运行的截图:

测试环境: fedora 20 64bits, 4G DDR3内存,CPU:Inter® Core™ i7-3610QM cpu @2.30GHZ.

结果是令人吃惊的,他们的性能差距居然达到了1倍,太神奇了。

原理:上面提到的合并写存入缓冲区离cpu很近,容量为64字节,很小了,估计很贵。数量也是有限的,我这款cpu它的个数为4。个数时依赖cpu模型的,intel的cpu在同一时刻只能拿到4个。

因此,run_one_case_for_8函数中连续写入8个不同位置的内存,那么当4个数据写满了合并写缓冲时,cpu就要等待合并写缓冲区更新到L2cache中,因此cpu就被强制暂停了。然而在run_two_case_for_4函数中是每次写入4个不同位置的内存,可以很好的利用合并写缓冲区,因合并写缓冲区满到引起的cpu暂停的次数会大大减少,当然如果每次写入的内存位置数目小于4,也是一样的。虽然多了一次循环的i++操作(实际上你可能会问,i++也是会写入内存的啊,其实i这个变量保存在了寄存器上), 但是它们之间的性能差距依然非常大。

从上面的例子可以看出,这些cpu底层特性对程序员并不是透明的。程序的稍微改变会带来显著的性能提升。对于存储密集型的程序,更应当考虑到此到特性。

希望这篇文章能该大家带来一些帮助,也能可做性能优化的同事带来参考。

时间: 2024-10-16 18:53:07

现代cpu的合并写技术对程序的影响的相关文章

CPU硬件辅助虚拟化技术

目前主要有Intel的VT-x和AMD的AMD-V这两种技术.其核心思想都是通过引入新的指令和运行模式,使VMM和Guest OS分别运行在不同模式(ROOT模式和非ROOT模式)下,且Guest OS运行在Ring 0下.通常情况下,Guest OS的核心指令可以直接下达到计算机系统硬件执行,而不需要经过VMM.当Guest OS执行到特殊指令的时候,系统会切换到VMM,让VMM来处理特殊指令. 1.Intel VT-x技术 为弥补x86处理器的虚拟化缺陷,市场的驱动催生了VT-x,Intel

写一个ajax程序就是如此简单

写一个ajax程序就是如此简单 ajax介绍: 1:AJAX全称为Asynchronous JavaScript and XML(异步JavaScript和XML),指一种创建交互式网页应用的网页开发技术.     2:基于web标准XHTML+CSS的表示:     3:使用 DOM进行动态显示及交互:     4:使用 XML 和 XSLT 进行数据交换及相关操作:     5:使用 XMLHttpRequest 进行异步数据查询.检索: 程序员应用ajax的途经: 1:.Net下的Ajax

在android系统上写C语言程序--开机启动该程序不进入安卓系统

今天要写的这篇博文意义重大,也是网上很少有的,这是在我工作中学会的一项技术,当然,它也是由简单的问题组合而来的.如何在安卓中写C语言程序,调试安卓驱动,测试程序的的一项重要技能,下面我就不说废话了,直接说实用的,怎么用这个东西. 关于这个问题,相信很多学Android的都会去关注这个问题,大家普遍会有个这样的疑问:安卓是怎么跑起来的? 最简单的说法,安卓系统是这样加载的: Bootloader------Kernel(对应平台版本的Linux内核)------filesystem文件系统(这个就

Linux内核学习--写一个c程序,并在内核中编译,运行

20140506 今天开始学习伟大的开源代表作:Linux内核.之前的工作流于几个简单命令的应用,因着对Android操作系统的情愫,"忍不住"跟随陈利君老师的步伐,开启OS内核之旅.学习路径之一是直接从代码入手,下面来写一个hello.c内核模块. 说明: 这个路径/usr/src/linux-headers-2.6.32-22/include/linux是引用的头文件. 内核模块固定格式:module_init()/ module_exit(),module函数是从头文件中来的.

写给嵌入式程序员的循环冗余校验(CRC)算法入门引导

写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 http://blog.csdn.net/liyuanbhu/article/details/7882789 前言 CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验.因此,掌握基本的CRC算法应是嵌入式程序员的基本技能.可是,我认识的嵌入式程序员中能真正掌握CRC算法的人却很少,平常在项目中见到的CRC的代码多数都是那种效率非常低下的实现方式. 其实,在网上有一篇介绍CRC

写给 iOS 程序员看的 C++(2)

原文:Introduction to C++ for iOS Developers: Part 2 作者:Matt Galloway 译者:kmyhy 欢迎回到<写给 iOS 程序员看的 C++ 教程系列>第二部分! 在第一部分,你学习了类和内存管理. 在第二部分,你将进一步深入类的学习,以及其他更有意思的特性.你会学习什么是模板以及标准模板库. 最后,你将大致了解 Objectiv-C++--一种将 C++ 混入 Ojective-C 的技术. 准备好了吗?让我们开始吧! 多态 这里的多态不

不要困在自己建造的盒子里——写给.NET程序员(附精彩评论)

转自:http://kb.cnblogs.com/page/92260/ 此文章的主旨是希望过于专注.NET程序员在做好工作.写好.NET程序的同时,能分拨出一点时间接触一下.NET之外的东西(例如10%-20%的时间),而不是鼓动大家什么都去学最后什么都学不精,更不是说.NET不行或劝大家放弃.NET.恕我愚钝,此主旨在文中表达不够清楚,看评论中很多朋友误解了,特此说明. 另外,本文中的观点并不全部是我个人的想法,相当一部分来自我以前聊过天的某些大牛,他们很多来自微软.百度.腾讯等知名企业,并

多核下写高性能并行程序

什么样的程序执行效率高? 程序的数据和指令都在cache中,没有cache miss出现. 所以如何让并行程序性能高基本可以演变成 如何减少cache miss? 尤其是多核下,并行程序cache的问题已经无法回避了,否则并行的效率还没有一个线程高. 写程序的时候MESI协议要时刻浮现在眼前. 借用一句歌词:现在不是从前了,兔子比狐狸狡猾了. 1.绞尽所有的脑汁,避免使用全局变量,尤其是程序运行过程中可能修改的变量.所有线程只读的变量可以放松一丁点儿要求. 2.per thread per co

为什么要写技术博

认知 从小学到大学,我的学习方式都是"听课 + 做笔记 + 读书".整个过程类似于web1.0:我是一个客户端,从服务器(讲师或者书籍)那里"获取"信息,而基本不用产出什么.整个学习的过程蜗牛慢,一个学期就学四五门课.至于学的如何,则要等到期末考试,才能根据答题情况,粗糙的知道自己对知识掌握程度.然而此时考试已经结束,所以懒得再去翻看半年前开始学习的内容了,知识体系上的"瑕疵"就永远的留在那里. 写博可以对自我学习的迅速反馈.通过博客这种&quo