`
netcome
  • 浏览: 466312 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

RunTime: 块内存复制

阅读更多

内存复制

在计算机中,内存复制经常而普遍。它们出现在联网应用、数据库应用、科学应用以及几乎您能想得到的其它任何应用和服务中。因为它们是如此的通用,所以程序员对于内存复制有点满不在乎,而且还采用了各种各样的编程技巧来完成复制。内存复制可以是整块内存的简单移动,也可以根本不是一个复制而类似一个访问模式。

一个 访问模式的形成是通过访问一个矩阵中的各栏。比如您需要 12x12 矩阵中所有第 12 个词。这 12 个词将代表矩阵的第一栏。通常,一栏访问只需要每栏元素的 32 或 64 字节;很少多于 64 字节。复杂矩阵每栏元素可能需要两个 64 字节值。访问间的字节数被称为一个 跨距,内存访问以一个跨距值作为参数。





回页首


测量块内存的复制速度

在这个部分中,我们只观察块内存复制,这即使在第一年编程课程中也是非常普通的。测量内存复制速需要技巧,因为计算机有一级、二级和三级高速缓存(有时候有),所以测试必须考虑高速缓存的大小。我们只看最终结果,即数据移动有多快。关键在于理解代码路径长度而非系统缓冲或高速缓存的问题。这两个问题将在以后的文章中谈到。代码路径很重要,它显示字节能多快地被移动以及移动涉及的系统开销。

内存转移只需简单的编程技巧。简单的 for 循环和 memcpy() 库例程是最常用的机制。而结构分配和文件 IO 技巧则相对使用较少。

转移内存在整个性能图中只占据一小部分。当真的是占据一部分时,最好清楚操作系统和硬件作为一个组可以提供什么。

在一个栏中有太多方面要调查和涉及,因此,有必要缩小范围。这也就意味着要得到有用的结论,必须再扩大范围。一些检测参数包括:

  • 跨距 -- 如上所述,常与矩阵访问有关
  • 总体转移大小 -- 移动数据的总量
  • 块尺寸 -- 在一次单独操作中移动的数据量(与下一参数紧密有关)
  • 编程技巧 -- 如 memcpy、(char *, double *) 指针
  • 系统页大小
  • 翻译后备缓冲器 (TLB) 的大小

这里的目标是将总转移量定在 16 MB,同时将跨距定为 0,即作简单的块内存转移。块尺寸随编程技巧变化而变化。Windows 2000 下系统页大小为 4K,Linux 也是 4K。如果是相同的计算机,TLB 的大小也相同。因为只使用一个线程移动内存,所以无线程问题。





回页首


测试程序

我们使用的程序叫做 memxfer5b.cpp,它已有过几个版本。它的使用信息如下:

memxfer5b 的使用信息

            Usage: memxfer5b.exe [-f] [-w] [-s] [-p] size cnt [method]
        -f flag says to malloc and free of the "cnt" times.
        -w = set process min and max working set size to "size"
        -s = silent; only print averages
        -p = prep; "freshen" cache before; -w disables
        -csv = print output in CSV format
        methods:
         0:     "memcpy (default)"
         1:     "char *"
         2:     "short *"
         3:     "int *"
         4:     "long *"
         5:     "__int64 *"
         6:     "double *"
         



"-p" 选项之所以有用是因为在多数测试中首次复制比接下来的复制要运行地慢。即暗示高速缓存正在被装载。这里的测量法特别注意代码路径而非内存转移速度。因此,我们使用 "-p" 选项来“预先准备”内存。我们的目的是尽可能达到最快(最短时间)的内存转移。现实情况下,程序更有可能碰到的环境是,首次转移时高速缓存未做准备。尽管这样,如果我们在测试中选择能导致最佳性能的代码路径,我们就很有可能找到最优的生产代码性能。

Memxfer5b 能使用 7 种不同的内存转移技巧。“推荐的” memcpy() API 以及 6 个不同的指针类型在 Linux 和 Windows 上都可行。

Memxfer5b.cpp 可方便地编译下列任何一条命令:

gcc -O2 memxfer5b.cpp -o memxfer5b cl -O2 memxfer5b.cpp -o memxfer5b.exe


Memxfer5b.cpp 使用我在 介绍专栏中描述的相同的支持例程。在支持例程的列表中再加入一个名为 Malloc() 的例程。Malloc() 的作用和 malloc() 一样,但当无法分配内存时,它将打印错误消息并退出。就我们的目的来说,这已经足够的。我们不测量内存分配速度;这个测试中任何分配内存的失败只是说明程序里有一个错误,或者我们已经达到系统限制。这两种情况我们都不希望发生。(我的介绍专栏中也提到过 Malloc() 例程,但它不包含于任何源代码中。在这个部分中,也会提到 -- 请参阅 参考资料。)

先前,我们看了微软 C++ 编译器的各种选项,看有没有什么比 "-O2" 更好的。在我们的测试中,我们什么也没找到,于是放弃继续查找。但是,如果哪位读者在 cl.exe 用他或她喜欢的优化参数编译 memxfer5b.cpp,“并”产生更好的性能表现,请在讨论论坛上告诉我们所有人;点击文章顶部或底部的 讨论图标。群策群力会提高查找 cl.exe 参数空间的效率。

memxfer5b.cpp 的主循环是实际移动内存的部分,它通过调用定时例程将移动分类。Memxfer5b.cpp 可去块内存转移的其它区域。以后的版本将增加分离功能,这样我们就可模拟矩阵操作。





回页首


测试系统

我们将在安装了 Windows 2000 Advanced Server Service Pack 1、Linux 2.2.16 和 Linux 2.4.4 的系统编译和运行测试。这些 Linux 在 Red Hat 7.0 环境下运行。在 Linux 上,我们将使用包括在 Red Hat 7.0 发行版中的 gcc。在 Windows 2000 上,我们将使用来自 Visual Studio 6.0 的 Microsoft C++ Version 12.00.8168。我们的测试系统将是 ThinkPad 600X Model 2645-9FU,576 MB 的内存和 12 GB 的硬盘。 这个 600X 在 Windows 2000 上是个 648 MHz 奔腾 III 机器,而在 Linux 上是 647.767 MHz。Windows 2000 和 Linux 决定那信息的“机制”是:

Windows 2000 到 MHz 的浏览路径为:

Start/ Settings/ Control Panel/ Administrative Tools/ Computer Management/ System Tools/ System Information/ System Summary


Linux 显示 MHz 的命令是:

cat /proc/cpuinfo


程序以每秒兆字节为单位打印作为结果的内存速度。如果指定 "-s" 标记,那么运行 "cnt" 计算内存复制速度。否则,每次运行都被打印。我们的测试按如下方式进行:

 

       memxfer5b -p -s -csv 16m 8 0 1 2 3 4 5 6
    memxfer5b -p -s -csv 16m 8 0 1 2 3 4 5 6
    memxfer5b -p -s -csv 16m 8 0 1 2 3 4 5 6
    memxfer5b -p -s -csv 16m 8 0 1 2 3 4 5 6
    



这是我们八次运行中的四次。不要期望看到很多变化;重复的尝试将检验我们的期待,或者给我们所要期待的加一些限定范围。





回页首


测试结果

结果显示在下表中。Linux 的新旧版本在内存转移上比 Windows 2000 显然快得多。还不清楚是怎么一回事,有待进一步研究。

memxfer5b -s -p -csv 16777216 8
方法

每秒兆字节的平均内存速度

  Linux 2.2.16-22 Linux 2.4.4

Windows 2000 AS

       
memcpy 173.644 179.417 132.077
char * 169.683 169.000 93.494
short * 170.065 172.333 96.156
int * 170.136 172.648 102.507
long * 170.066 172.050 123.498
__int64 * 170.094 172.330 123.498
double * 169.778 171.192 123.283

检查 Windows memxfer5b.exe 程序的汇编清单,发现当调用 memcpy API 时,Microsoft C++ 编译器使用 "rep movs" 指令。另外,它尽职地为 "char *" 做字符对字符移动,为 "short *" 做字对字移动,并且奇怪地为 "int *"、"long *"、"__int64 *" 和 "double *" 做双字对双字移动。(Memxfer5b 避免所有错误排列的限定条件。)

一个类似的 Linux 二进制检查显示(几乎)同样的代码。唯一例外是 gcc 实际使用 memcpy() 例程。两个编译器都只移动字节、16-bit 字和 32-bit 字。在 Linux 和 Windows 下生成的汇编代码很相似。

内存复制性能差异的一个可能性是 Windows 在后台比 Linux 做了更多的工作。为了测试这个理论,让我们写一个十分短小但计算密集的程序来计算一个单一的分形点。不带参数, fract2.cpp 重复分形公式,直到重复了 100,000,000 次或分形点“逃逸”。Fract2.cpp 是一个短浮点数(双)循环计算的简单程序。

结果如下:

操作系统 完成时间(秒)
Linux 2.2.16-22 4.065
Linux 2.4.4 4.087
Windows 2000 AS 4.300


完成时间暗示了相同硬件条件下,任何一种 linux 版本可以比 Windows 完成更多的计算。此外,当两个经过优化的编译程序被反编译后,我们发现产生的循环几乎完全一致,实际上,linux 的循环比 Windows 的多包含了两个指令。

fract2.cpp 运行时间不能说明内存复制速度上的巨大差别。而且,Fract2.cpp 可能说明了这些差别的一部分,但不是全部。





回页首


总结

就我们研究的这一点而言,我们没有足够的信息来充分理解内存复制的异常。而且,我们只涉及了块内存转移的一个很小方面。因此,还不能对 Linux 和 Windows 的优劣做出公正的结论。我们可以断定,对于 16M 字节传输,在两种平台上使用 memcpy 都是个好主意。我们也可以断定,正如 fract2.cpp 所显示的,Windows 操作系统只有一小部分的系统额外开销。然而,最好让读者自己来评价这里使用的技术,以及提出如何在这两个小测试中使每种系统运行更高效的建议。

本文提出的问题比回答的问题更多,我们将在以后的文章里更仔细地考察内存性能,在此之前,是否一个系统的块内存移动性比另一个更高仍然是个未知的问题。



参考资料



关于作者

Edward Bradf

分享到:
评论

相关推荐

    内存管理内存管理内存管理

    返回的每块内存的起始处首先要有这个结构: 清单 3. 内存控制块结构定义 struct mem_control_block { int is_available; int size; }; 现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们...

    操作系统(内存管理)

    因此, malloc 返回的每块内存的起始处首先要有这个结构: 清单 3. 内存控制块结构定义 struct mem_control_block { int is_available; int size; }; 现在,您可能会认为当程序调用 malloc 时这会引发...

    Java常见面试问题整理.docx

    Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 2.方法区(元空间):方法区用于存储已被虚拟机加载的类信息、常量、...

    MFC文件操作

    此时要重点注意为此CFileDialog对象的m_ofn.lpstrFile分配一块内存,用于存储多选操作所返回的所有文件路径名,如果不进行分配或分配的内存过小就会导致操作失败。下面这段程序演示了文件打开对话框的使用方法。  ...

    苹果8XPC和手机二合一完整版

    {maccms:runtime} 页面运行时间、查询次数、占用内存 {maccms:date} 当前日期 {maccms:siteaid} 当前所在模块ID {maccms:url} 网站域名 {maccms:name} 网站名称 {maccms:keywords} 网站关键字 {maccms:description...

    【05-面向对象(下)】

    如果代码块只有包含一条语句,Lambda表达式允许省略代码块的花括号,如果省略了代码块的花括 号,这条语句不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略return关键字。 Lambda表达式...

    疯狂JAVA讲义

    我把另一个构造器里的代码复制、粘贴到这个构造器里不就可以了吗? 143 5.6 类的继承 144 5.6.1 继承的特点 144 5.6.2 重写父类的方法 145 5.6.3 父类实例的super引用 146 学生提问:我们只是创建了一个Ostrich...

    新版Android开发教程.rar

    � 采用了对有限内存、电池和 CPU 优化过的虚拟机 Dalvik , Android 的运行速度比想象的要快很多。 � 运营商(中国移动等)的大力支持,产业链条的热捧。 � 良好的盈利模式( 3/7 开),产业链条的各方:运营商、...

    1345个易语言模块

    runtime.ec RUN加减模块1.0+ 名.ec SAVEPIC.EC Sc千寻专用模块.ec SetIEProxy.ec setuser.ec sev.ec shell.ec SHELL32.EC ShutDown.ec ShutDown1.ec SH_RAR.EC SIMIXP.EC simixp1.0.ec simixp1.01.ec simixp1.02.ec ...

    1350多个精品易语言模块

    runtime.ec RUN加减模块1.0+ 名.ec SAVEPIC.EC Sc千寻专用模块.ec SetIEProxy.ec setuser.ec sev.ec shell.ec SHELL32.EC ShutDown.ec ShutDown1.ec SH_RAR.EC SIMIXP.EC simixp1.0.ec simixp1.01.ec simixp1.02.ec ...

    超级有影响力霸气的Java面试题大全文档

     GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收...

    java 面试题 总结

     GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收...

    c#学习笔记.txt

    例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。结构可以声明构造函数,但它们必须带参数。声明结构的默认(无参数)构造函数是错误的。总是提供默认构造函数以将结构成员初始化为...

    最新Java面试宝典pdf版

    2、编写一个程序,将d:\java目录下的所有.java文件复制到d:\jad目录下,并将原来文件的扩展名从.java改为.jad。 62 3、编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串,但要保证...

    Java面试笔试资料大全

    2、编写一个程序,将d:\java目录下的所有.java文件复制到d:\jad目录下,并将原来文件的扩展名从.java改为.jad。 62 3、编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串,但要保证...

    java面试题大全(2012版)

    2、编写一个程序,将d:\java目录下的所有.java文件复制到d:\jad目录下,并将原来文件的扩展名从.java改为.jad。 62 3、编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串,但要保证...

    Java面试宝典2010版

    2、编写一个程序,将d:\java目录下的所有.java文件复制到d:\jad目录下,并将原来文件的扩展名从.java改为.jad。 62 3、编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串,但要保证...

Global site tag (gtag.js) - Google Analytics