打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
基于C中内嵌SSE指令的memset函数的改进-信隆论文网

基于C中内嵌SSE指令的memset函数的改进

[日期:2007-12-01] 来源:  作者: [字体: ]
文非易  张代远
(南京邮电大学计算机科学技术系,南京市新模范马路66# 210003)
 
    摘  要   C语言中的memset函数在速度上落后于根据特定CPU写出来的汇编代码。在大量的内存操作中,不得不考虑memset的替代品。基于SSE指令的算法是一个好的选择。
信隆论文网:http://www.xllw.CN,更多免费论文,更多优质服务
    关键字  SSE指令,汇编语言,内存,寄存器
 

    1  前言

    近年来Intel在Pentium处理器中引入了SSE 指令系统(Streaming SIMD Extensions的缩写,单指令多数据流扩展指令系统) ,大大提高了计算机在图形、动画、音频、视频、语音识别等应用领域的速度。
    由于内存的速度相对于CPU来说要慢得多,在一些频繁进行大块内存设置的程序中,内存设置会消耗大量的时间,从而严重影响程序的性能。在现有CPU和内存的条件下解决这个问题通常采用如下方法:一是在CPU空闲时使用后台程序间进行内存操作;二是采用数据缓存提高数据存取速度。这些方法在一定程度上提高了程序的性能,但是在内存操作本身的速度上却没有本质的提高。由于C语言中的memset函数出于兼容性考虑,在速度上落后于根据特定CPU写出来的汇编代码。所以在大量的内存操作中,不得不考虑memset的替代品。这种情况下,基于SSE指令的算法是一个好的选择。
    在这里我将基于SSE指令尝试对memset函数的性能进行优化,并将这种算法与原有的C标准库memset函数的性能进行分析比较并做相应模拟仿真。

    2  SSE与MMX指令系统简介

    新推出的SSE指令集包含70条指令,其中50条为提高3D图形运算效率准备的SIMD(Single InstructionMultiple Data) 浮点运算指令,12条为MMX整数运算增强指令、8条为内存连续数据块传输指令。为了配合SSE指令集,cpu增加了8 个新的128 位单精度寄存器(4 ×32 位) ,能同时处理4 个单精度浮点变量,同时也添加了一个状态/控制字(Status/ Con2trol word) 。为了充分发挥SSE 指令集和这些新寄存器的优势, Intel又引进了新的“处理器分离模式”,这是Intel 继引入386 模式以来又一次推出新的处理器模式。该模式允许并行使用SIMD -FP和MMX或SIMD - FP 和IA - FP 双精度浮点代码。SSE兼容MMX 指令,它可以通过SIMD和单时钟周期并行处理多个浮点数据来有效地提高浮点运算速度。
    SSE在MMX之后,它们的主要的区别是MMX 并没有定义新的寄存器,而SSE 定义了8个全新的128位寄存器XMM0------XMM7。每个寄存器可以同时存放4 个单精度浮点数(每个32 位长) ,SSE的浮点数运算指令就是使用这些寄存器。与之前的MMX不同,这些寄存器并不是原来已有的寄存器(MMX 是使用x87 浮点寄存器),所以不需要像MMX一样,在使用X87 指令之前,需要利用一个EMMS 指令来清除寄存器的状态。因此,与MMX指令不同,SSE的浮点数运算指令,可以很自由地和X87 指令,或是MMX 指令共享,此外SSE还定义了一个新的数据类型,可以用来储存这4个单精度浮点数。SSE 新增的寄存器和这个新的数据类型以及4个单精度浮点数在寄存器中排列顺序如下所示。
    XXM寄存器中的这个128 位SSE 新的二进制数据中包含了4个单精度浮点数DATA0、DA2TA1 、DATA2 和DATA3
    MMX的寄存器排列如下所示。
    SSE 的寄存器排列如下所示。
    在SSE和MMX中提供了许多高速的传输指令,在内存操作方面具有很大的优势。下面是几条常用的传输指令:
    MOVQ mm,r/ m64 MOVQ r. m64 ,mm,说明:将64 位数据从整型寄存器/ 内存移到MMX 寄存器,和反向移动目标操作数和源操作数可为MMX 寄存器,64 位内存操作数。但MOVQ 不能在内存和内存之间进行数据转移
    MOVAPS xmm1 ,xmm2/ m128 ,说明: 对齐紧缩单精度浮点数传输指令。
    MOVUPS xmm1 ,xmm2/ m128,说明: 非对齐紧缩单精度浮点数传输指令。
    此外在使用SSE指令前需要确定cpu是否支持并进行初始化。

    3  C/C++语言中的内嵌汇编与memset函数

    由于在性能比较时所用的环境是在目前十分流行的Win32下,故这里选择了VC作为编译器并在这个编译器下内嵌汇编语言,其他的编译器也是大同小异的。
    在VC++ 中使用汇编语言的最好方法是运用内联方式,即将汇编代码通过一定式嵌入VC++ 代码中,实现无缝连接。在Visual C++ 中使用关键字“ __asm”来表示嵌入标志,有别于标准C的“asm”关键字。通常有如下两种方式内嵌汇编代码:
    1) 标识汇编模块
仿照VC++ 程序格式,由__asm 引导,用“{”、“}”标记一段汇编代码,如下所示:
__asm
{
MOV AX, 1
MOV DL , 0x11
}
    这里“ __asm”模块的“{}”标记不会影响C++ 变量的作用范围。而且“ __asm”块同样可以实现嵌套结构,也不会影响变量的作用范围。
    2) 标识每一条汇编语句
    在每一条汇编语句前添加“__asm”标记,表明这是汇编代码。如:__asm MOV AX , 1
    __asm MOV DL, 0z11
    __asm ADD AX , DX
    由于“__asm”可以看作是语句分隔符,因此可以把多条汇编语句放在同一行,如:
    __asm MOV AX, 1 __asm MOV DL , 0x11 __asm ADD AX , DX
   一般情况下,目前的VC版本基本上支持的IntelX86指令集与MMX指令。某些不支持的指令可以使用“EMIT”伪指令来定义,EMIT的后面接机器码来定义某条指令相当于MASM 中的DB,特别要注意的是每一个EMIT关键字后面只能有一个字节。内嵌汇编可以使用MASM中的表达式。比如:MOV EAX , 1 。需要注意的是,虽然“__asm”块中允许使用C++ 的数据类型和对象,但不能用MASM指示符和操作符定义数据对象。尤其是“__asm”块中不允许MASM 中的定义指示符,如DB、DW、DD、DQ、DT 和DF 等以及DUP和THIS 等操作符。另外,MASM 结构和记录在这里也失效,因为内联汇编不接受STRUC、RECORD、WIDTH 或者MASK等标记。但是, 内联汇编却支持EVEN 和AL IGN ,当需要的时候,这些指示符在汇编代码里面加入NOP(空操作)指令使标号对齐到特定边界。这样可以使某些处理器取指令时具有更高的效率。
    内联汇编也不能使用MASM 宏指示符,如MACRO、REPT、IRC、IRP 和ENDM 等。也不支持宏汇编中的“ <> ”、“ !”、“&”、“%”和“.TYPE”等宏操作符。如果需要使用某个段,则必须使用寄存器来说明。跨越段则必须显式地加以说明,如ES : [AX]。我们可以使用“LENGTH”来取得C++ 数组中的元素个数,如果不是一个数组,则结果为“1”。使用SIZE 来取得C++中变量的大小。而TYPE 也可以用来取得一个变量的大小,但如果是一个数组,它得到的将是数组中的单个元素的大小。在内联汇编中可以使用C++注释“/**/”与“//”, 也可以使用汇编的注释,即“;”号。
    下面是C/C++中的memset函数,memset函数的作用就是将一段内存的值全部设置某个数,它的声明格式是这样的:
    void *memset( void * dest , int c , size_t count );
    其中dest表示这段内存的起始地址,c表示要设置的数值,count表示内存的长度。在这篇论文中我们需要对其进行优化和改进。

    4  算法的改进

    虽然使用SSE指令可以获得较高的性能,但是在编写代码时必须要注意以下几点:
    (1)数据对齐。cpu内存单元以16Byte为边界,跨越边界的访问就是非对齐的,如果数据在运算之前不进行对齐,就会使指令运算产生大量的延时。对于一级缓存中的数据,一次非对齐访问将产生一次访问请求,会消耗8个时钟周期左右,而对于二级缓存的非对齐访问则将消耗更时钟周期,所以数据对齐在执行大量数据存取中是非常必要的。有的时候由于要克服数据对齐的影响,所以在选择数据传输指令时就不应该使用那些仅传输对齐数据的指令如MOVAPS等。
    (2) 数据预取与缓存(Cache)。由于内存读取速度比cpu速度慢得多,为了克服这个缺陷,在CPU中使用了缓存,有的甚至还使用多层的缓存。最近的cpu都采用了两级缓存的设计。CPU在从内存中读取对齐数据时,先查询一级缓存,如果命中,则读取数据,操作没有额外的延时;如果没有命中,则继续查询二级缓存。如果在二级缓存中命中,则在二级缓存中读取数据,这时候大约会有10 个指令周期的延时。如果没有命中,则在内存中读取数据,此时延时达到大约50个指令周期。所以在内存操作中,如果在CPU读取数据之前将数据预先读取到一级缓存和二级缓存中,将可以大大提高命中率,减小读取数据造成的延时。另外SSE 提供了CPU缓存数据预取指令,如prefetchnta 等,这些指令可以告诉CPU将稍后要处理的数据先读到缓存中,而且开销非常小,如执行prefetchnta指令只需一个时钟周期,且没有延时。所以如果在CPU读取内存数据之前先用预取指令将数据预取到缓存中,将大大提高数据读取的速度。
    结合前面所说,新的方法QuickMemset函数的代码如下,这里destmem为目标内存块的首地址,c为要设置的数值,由于考虑字节对齐问题,内存块的长度为length*8.
void Quickmemset(void *destmem, int c, unsigned long length)
{
__asm
{
           movq mm0, c
           punpcklbw mm0, mm0
punpcklwd mm0, mm0
punpckldq mm0, mm0
           mov edi, destmem
 
           mov ecx, length
           lea     edi, [edi + ecx * 8]
           neg ecx
 
           movq         mm1, mm0
           movq         mm2, mm0
           movq         mm3, mm0
           movq         mm4, mm0
           movq         mm5, mm0
           movq         mm6, mm0
           movq         mm7, mm0
 
loopwrite:
           movntq      [edi + ecx * 8     ], mm0
           movntq      [edi + ecx * 8 + 8 ], mm1
           movntq      [edi + ecx * 8 + 16], mm2
           movntq      [edi + ecx * 8 + 24], mm3
           movntq      [edi + ecx * 8 + 32], mm4
           movntq      [edi + ecx * 8 + 40], mm5
           movntq      [edi + ecx * 8 + 48], mm6
           movntq      [edi + ecx * 8 + 56], mm7
 
           add ecx, 8
           jnz loopwrite
 
           emms
}
}
    由于movntq指令出现较晚,所以必须在较新的C编译器上才能编译(比如说VC7.0),对于旧版本的编译器,考虑使用emit伪指令代替。

    5  性能比较

    在Windows XP环境下,512M内存,Celeron 2.66GHz的CPU进行性能的比较。比较包括三个,在对128MB的内存块进行操作时,由于Windows是多进程的操作系统,所以消耗的时间会有一些略微的差别。仿真的结果取平均值。
    用C语言的for循环赋值:821ms                                     
    使用memset函数:320ms
    使用上述的Quickmemset函数:157ms

    6  结语

   从上面的分析可以看出,内嵌SSE指令的函数比用C写的内存设置快400%,比库函数memset快100%。但是由于SSE指令是最近才出现的,许多旧的CPU不支持。对于程序的移植性有一定的影响。一般来说对于速度要求很高的程序诸如游戏或者实时系统可以考虑使用,而对于普通的程序而言,系统提供的memset函数也还是令人满意的。总而言之可以根据情况需要来决定是否使用加速的函数。

    参考文献

[1 ] Intel Corporation1IA - 32 Intel ArchitectureSoftware Developer’s Manual Volume 1 : Basic Architecture.Http :/ /www. Intel . com ,Order Number 245470 -012 20031
[2 ] Intel Corporation1IA - 32 Intel ArchitectureSoftware Developer’s Manual Volume 3 : System Programming Guide . Http:/ / www. Intel  . com , Order Number
245472 - 012 20031
[3] 陈建春. VC++ 高级编程技术[M]北京:电子工业出版社,1999. 91
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
memset 的实现分析
内存频率、内存带宽、CPU外频、FSB、双核、64位技术等基本
关于NEON的一些总结
最全面的笔记本基本硬件参数介绍,常见问题解答处!
这18条背下来没人敢和你忽悠CPU
汇编语言入门教程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服