龙芯版 memcpy 的实现

glibc中strcpy,strcat,strcmp,memcpy等函数的实现和分析 http://www.oschina.net/code/explore/glibc-2.9/string/memcpy.c 可以找到所有glibc的源代码 strcpy 源码 char * strcpy (dest, src) char *dest; const char *src; {     reg_char c;     char *__unbounded s 阅读详情

当前版本: 0.1
完成日期: 2007-6-15

作者: Dajie Tan <jiankemeng@gmail.com>


memcpy 是为最常用之函数,多媒体编解码过程中调用频繁,属调用密集型函数,对其性能优化很有意义。

1. 概述

memcpy 所做的操作是把内存中的一块数据复制到内存的另一个地方,也就是内存到内存的数据拷贝,这个过程需要CPU的参与,即:先从内存取数据到CPU的寄存器,然后再从寄存器写到内存中。可以用类似如下C 代码实现:

char *dest = (char *)to;
char *src = (char *)from;
int i = size -1;

while(i >= 0)
{
    *(dest + i) = *(src + i);
    i--;
}

这个在size 比较小时,性能尚可。倘若size 很大,这种每次取一个字节的方式,远
没有充分发挥CPU的数据带宽。作为一种改进方式,我们可以每次取4个字节进行写入,不足4字节的部分,依然每次取一个字节写入:

int *dest = (int *)to;
int *src = (int *)from;

int word_num = size/4 - 1;

int slice = size%4 - 1;

while(word_num >= 0)
{
    *dest= *src;
    dest += 4;
    src += 4;
    word_num--;
}

while(slice >= 0)
{
    *((char *)dest + slice) = *((char *)src + slice);
    slice--;
}

上面这个就是 memcpy 优化的基本思想。

龙芯2E因为是64位,可以使用ld指令,每次取8个字节写入目标地址。

上面的例子,为了说明问题的方便,没有考虑指针的是否对齐。因为不对齐访问会触发异常,所以使用汇编码写高性能 memcpy 时,对指针是否对齐要分情况予以周密的处理。


2. glibc 对memcpy的优化分析

先看看glibc 对memcpy的实现。



    1      #include  < endian.h >
  
2      #include  < sys / asm.h >
  
3      #include  " regdef.h "


  
4       /* void *memcpy(void *dest, const void *src, size_t n);
  5       a0 <--- dest; a1 <--- src; a2 <--- n    
  6     
*/



  
7          . global      memcpy
  
8          .ent     memcpy

  
9      memcpy:
  
10          . set      noreorder
  
11          . set      mips3

  
12          slti     t0, a2,  16                # 小于16字节,则跳转到last16处,由其进行处理
  
13          bne     t0, zero, last16
  
14          move     v0, a0                # 目标指针作为返回值写入v0,注意此指令位于延迟槽中

  
15          xor     t0, a1, a0              # 都对齐,或者都不对齐(低3位相同)
  
16          andi     t0,  0x7  
  
17          bne     t0, zero, shift          # 低3位不相同,则跳转。则肯定有一个不对齐,或者都不对齐(低3位不同)
  
18          subu     t1, zero, a1

  
19                                          # 都对齐,或者都不对齐(低3位相同)
  
20          andi     t1,  0x7                 # t1 的值实际上为  8   -  (a1  &   0x7 ),即a1 加上 t1 就对齐了
  
21          beq     t1, zero, chk8w        # a1 aligned, then branch

  
22          subu     a2, t1                  
                                             # 不跳转则 a1 不对齐,此时 a0 肯定不对齐,而且他们的低3位相同
  
23          ldr     t0,  0 (a1)               # 把低 t1 个字节,使用非对齐访问指令特别照顾
  
24          addu     a1, t1                # 指针前移,a1 对齐矣    
  
25          sdr     t0,  0 (a0)              # 把低 t1 个字节,写到 a0 开始处的 t1 字节的空间里
  
26          addu     a0, t1                # 指针前移,a0 对齐矣

  
27      chk8w:
  
28          andi     t0, a2,  0x3f           # t0  =  a2  %   64
  
29          beq     t0, a2, chk1w         # 小于  64  字节则跳转到chk1w
  
30          subu     a3, a2, t0           # a3  =  a2  /   64
  
31          addu     a3, a1               # a3  =  end address of loop
  
32          move     a2, t0               # a2  =  what will be left after loop,是为不足64字节部分
  
33      lop8w:    
  
34          ld     t0,  0 (a1)               # 每次循环处理64个字节
  
35          ld     t1,  8 (a1)
  
36          ld     t2,  16 (a1)
  
37          ld     t3,  24 (a1)
  
38          ld     ta0,  32 (a1)
  
39          ld     ta1,  40 (a1)
  
40          ld     ta2,  48 (a1)
  
41          ld     ta3,  56 (a1)
  
42          addiu     a0,  64                # 指针前移  64  字节
  
43          addiu     a1,  64                # 同上
  
44          sd     t0,  - 64 (a0)
  
45          sd     t1,  - 56 (a0)
  
46          sd     t2,  - 48 (a0)
  
47          sd     t3,  - 40 (a0)
  
48          sd     ta0,  - 32 (a0)
  
49          sd     ta1,  - 24 (a0)
  
50          sd     ta2,  - 16 (a0)
  
51          bne     a1, a3, lop8w
  
52          sd     ta3,  - 8 (a0)

  
53      chk1w:
  
54          andi     t0, a2,  0x7          #  8  or more bytes left ?
  
55          beq     t0, a2, last16        # less than  8  bytes, then jump to last16
  
56          subu     a3, a2, t0          # Yes, handle them one dword at a time
  
57          addu     a3, a1             # a3 again end address
  
58          move     a2, t0
  
59      lop1w:
  
60          ld     t0,  0 (a1)
  
61          addiu     a0,  8
  
62          addiu     a1,  8
  
63          bne     a1, a3, lop1w
  
64          sd     t0,  - 8 (a0)

  
65      last16:                           # 可改进
  
66          blez     a2, lst16e           # Handle last  16  bytes, one at a time
  
67          addu     a3, a2, a1         # a3 为终止标志
  
68      lst16l:
  
69          lb     t0,  0 (a1)
  
70          addiu     a0,  1
  
71          addiu     a1,  1
  
72          bne     a1, a3, lst16l
  
73          sb     t0,  - 1 (a0)
  
74      lst16e:
  
75          jr     ra                        # Bye, bye
  
76          nop

  
77      shift:                               # 为何没有考虑 a1 对齐,a0 不对齐的情况??
  
78          subu     a3, zero, a0          # a1 不对齐,a0 可能对齐,可能不对齐
  
79          andi     a3,  0x7                # (unoptimized  case ...)
  
80          beq     a3, zero, shft1        # a0 对齐,a1 不对齐,则跳转到shft1处

  
81                                          # 此时 a0不对齐,a1 未知,且低3位不同
                                             # 当a1对齐时,可否直接对a1使用对齐访问,a0使用不对齐访问
  
82          subu     a2, a3                # a2  =  bytes left
  
83          ldr     t0,  0 (a1)               # Take care of first odd part
  
84          ldl     t0,  7 (a1) 
  
85          addu     a1, a3
  
86          sdr     t0,  0 (a0)
  
87          addu     a0, a3               # 到这里,a0 对齐了,a1 反正也不想使其对齐

  
88      shft1:                              # 此种情况亦可添加  64  字节为单位的处理块
  
89          andi     t0, a2,  0x7
  
90          subu     a3, a2, t0            # a3 为8的倍数
  
91          addu     a3, a1               # a3  =  end address of loop
  
92      shfth:
  
93          ldr     t1,  0 (a1)               # Limp through, dword by dword
  
94          ldl     t1,  7 (a1)
  
95          addiu     a0,  8
  
96          addiu     a1,  8
  
97          bne     a1, a3, shfth
  
98          sd     t1,  - 8 (a0)
  
99          b     last16                    # Handle anything which may be left
  
100          move     a2, t0

  
101          . set      reorder
  
102          .end     memcpy



下面详细分析之:

1. 12-14行,首先对要复制的数据大小进行判断,小于 16 字节的直接跳转到 last16 处进行处理。

2. 15-17行,在于区分 dest 与 src 指针对齐的情况。两者低 3 位相同(异或为0),则要么都对齐,要么都不对齐,这两种情况可以合并起来处理。若两者低 3 位不同(异或不为0),则肯定有一个不对齐,或者都不对齐,这种情况要跳转到 shift 处去处理。

3. 18-21行,对 dest 和 src 都对齐或都不对齐的情况予以区分,因为二者对齐情况相同,故而只判断 a1 的情况即可。如果 a1 对齐,则直接跳转到 chk8w 处,先以 8 字节为单位,取数据写入之。注意 18 行执行完了,t1 中是 -a1 的补码,等价于 ~a1 + 1,只有 a1 低 3 位都为 0 时,t1 的低3 位才都为 0。

4. 22-26行,处理 dest 和 src 都不对齐的情况。注意此时 dest 与 src 的低 3 位是相同的。直接使用非对齐访问指令,获取 t1 个字节,写入目的地。则 dest 与 src 就可以跨到第一个对齐地址处(dest+t1, src+t1),此后的处理方式就和对齐的指针一样了。

5. 27-52行,处理数据块大于64字节的情况,每次循环写入 64 字节的数据。a0,a1,a2 同步移动,该程序块运行后,不足64字节部分交由 chk1w 处理。

6. 53-64行,处理数据块小于64字节的情况,每次循环写入 8字节。a0,a1,a2 同步移动。经其处理后,不足8字节部分,则交由 last16 开始的程序块处理。

7. 65-76行,以字节为单位写入数据。负责处理最后的16字节。可以将循环展开,优化之。

8. 78-80行,判断 a0 是否对齐,对齐则跳转到 shft1 处执行。

9. 81-87行,使a0对齐。

10. 88-100行,以8字节为单位,处理 a0 对齐,a1不对齐的情况。不足 8 字节的部分,交由 last16 处理。

注意 77-100 行这一块,是对a0,a1低3位不同的情况进行的处理。当a0,a1低3位不同时,可能有以下几
种情况:

I. a0 对齐,a1 不对齐
II. a0 不对齐,a1 不对齐(低3位不同)
III. a0 不对齐,a1 对齐

特别留意一下,其对第三种情况的处理,是首先将其转化为第一种情况,然后再对其进行处理的。

对第二种情况的处理也是这样的。



3. 针对龙芯的改进

A. 细化各种情况

81行处,当a0不对齐,a1对齐时,可直接对a1使用对齐访问,a0使用不对齐访问

这样会减少82-87行的6条指令,多一条分支判断语句。

这样,取数据使用对齐访问指令,写数据使用非对齐访问指令对,效率应该和取数据使用非对齐访问指令对(93,94 行),写数据使用对齐访问指令相当(97 行)。


B. 短循环展开

该改进的思想参见《龙芯汇编语言的艺术》

65-73 行负责处理小于16字节的部分,可以展开成如下代码:


     last16:
        blez     a2, 2f
        addiu     a2, a2, 
- 1
        sll         a2, a2, 
2          # a2  <--  a2  *   4

        la         a3, 1f
        subu     a3, a3, a2

        lb         $
15 0 (a1)

        jr         a3
          addiu a3, 2f
- 1f
       
        lb         $
16 15 (a1)
        lb         $
17 14 (a1)
        lb         $
18 13 (a1)
        lb         $
19 12 (a1)
        lb         $
20 11 (a1)
        lb         $
21 10 (a1)
        lb         $
22 9 (a1)
        lb         $
23 8 (a1)
        lb         $
8 7 (a1)
        lb         $
9 6 (a1)
        lb         $
10 5 (a1)
        lb         $
11 4 (a1)
        lb         $
12 3 (a1)
        lb         $
13 2 (a1)
        lb         $
14 1 (a1)
    
1 :     jr         a3
          sw   $
15 0 (a0)
       
        sb         $
16 15 (a0)
        sb         $
17 14 (a0)
        sb         $
18 13 (a0)
        sb         $
19 12 (a0)
        sb         $
20 11 (a0)
        sb         $
21 10 (a0)
        sb         $
22 9 (a0)
        sb         $
23 8 (a0)
        sb         $
8 7 (a0)
        sb         $
9 6 (a0)
        sb         $
10 5 (a0)
        sb         $
11 4 (a0)
        sb         $
12 3 (a0)
        sb         $
13 2 (a0)
        sb         $
14 1 (a0)
    
2 : jr         ra
        nop


注意:16~23 号寄存器,使用前需保存,完了要恢复。


C. 提升不对齐时大块数据复制的效率

原有实现当检测到a0、a1都不对齐时(低3位不同),将a0整对齐后,直接以8字节为单位,复制数据(见92-98行)这个应该亦可以引入先以64字节为单位复制数据,然后再进入以8字节为单位的处理流程,这个应该可以提升大快数据复制时的效率,目前这个也是猜想,需要进一步的大量测试。


注: 目前只测试了last16处短循环展开的改进,功能正确,效率尚未评测。测试程序见 http://people.openrays.org/~comcat/misc/memcpy.tar,内有 2 个文件 godson_memcpy.S mem_test.c,如下命令编译之:

gcc godson_memcpy.S mem_test.c -o mtest

./mtest 看输出是否正确


 
glibc memcpy函数的一些研究 测试内存(AEP,6*256GB interleaved dax)性能的时候,发现通过8B循环写的带宽大概是4GB/s,然后无意间用了一下memcpy,发现带宽达到了10GB/s,就顺便研究了一下memcpy函数,做个记录如下: glibc的memcpy函数实现如下 void * memcpy (void *dstpp, const void *srcpp, size_t len) { un... 阅读详情

相关推荐

谈谈memcpy函数的优化(纯C)

在使用C语言编程时,我们常用memcpy来复制内存数据,但很少有人会关注到memcpy怎么实现。最简单的memcpy功能实现如下: void *low_memcpy(void *dst, const void *src, size_t length) { char *srcp = (void*)src; char *dstp = (void*)dst; char *tail = srcp + length; while(srcp < tail) *dstp

sevek82的博客 7321

MIPS 字节对齐问题

一,概述 在MIPS 体系结构中,正常的加载和存储必须对齐,半字必须从2个字节的边界加载,字必须从4个字节的边界取出,加载一个非对齐的地址会导致CPU 陷入异常。例如,lh 读取一个半字时,存储器的地址必须是2 的整数倍,lw读取一个字时,存储器的地址必须是4的整数倍,sd 写入一个双字时,存储器的地址必须是8 的整数倍。 Mips 提供非对齐访问的伪指令,uld, 在非对齐的地址上加载64位的数据。 Ulh 在非对齐的地址上加载半字。ulhu, ulw, usd, ush, usw。一条伪指...

u013076893的专栏 3242

遭遇内存对齐问题

先说理论的:为何要内存对齐? 1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 2、 性能原因:经过内存对齐后,CPU的内存访问速度大大提升。 图一: 这是

Foru 2910

glibc中memcpy和memmove函数的实现

两三年前的一篇笔记,挺有意思的。 [ glibc-2.12.2 ]中memcpy()的源码,加上个别中文注释: // 位于 string/memcpy.c #include <string.h> #include <memcopy.h> #include <pagecopy.h> #undef memcpy void * memcpy (dstpp, srcpp, len) void *dstpp; const void *srcpp; ...

rover 1430

非对齐内存访问

========================= 非对齐内存访问 :作者: Daniel Drake dsd@gentoo.org, :作者: Johannes Berg johannes@sipsolutions.net :以及和来自他们的帮助: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt, Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz, Vad

sjc2870的博客 3661

闲着没事测了一下memcpy

前阵的时候领导提出要对memcpy做一个测试。也没有交代具体的细节。我就直接自已写了个常规memcpy,然后再把内核的__memcpy和glibc的memcpy整出来,对三者进行比较,不比不知道,一比吓一大跳。测试结论是:kernel和glibc的memcpy性能良好,glibc比内核的略高。自已写的就惨不忍睹了。测试程序大概是先定义两块1G的全局内存。然后先用memset将其全置

1万+

memcpy的效率问题

最近又听到有人在讲memcpy的效率问题,实在是忍不住了,基本上很多人对memcpy的原理都是一知半解,大部分认为memcpy是一个char到char的拷贝的循环,担心它的效率。实际上,memcpy是一个效率最高的内存拷贝函数,他不会那么傻,来做一个一个字节的内存拷贝,在地址不对齐的情况下,他是一个字节一个字节的拷,地址对齐以后,就会使用CPU字长来拷(和dma类似),32bit或

wo__chenjing的专栏 8188

Linux内存uncache区域拷贝优化

1.概述 内存非cache区域拷贝速度很慢,严重影响了系统性能,因此采用多种方法进行优化,主要有对齐拷贝、批量拷贝、减少循环次数、NEON拷贝方法。 2.高级SIMD和浮点寄存器介绍 2.NEON指令 2.1 VLDR VLDR指令可从内存中将数据加载到扩展寄存器中。 VLDR{<c>}{<q>}{.64} <Dd>, [<Rn> {, #+/-<imm>}] Encoding T1/A1, immediate form VLDR{<c

非专业业余程序员 2875

glibc memcpy() 源码浅谈

其实我本来只是想搞懂为什么memcpy()函数的参数类型是void *的:我以为会在memcpy()源码中能找到答案,其实并没有,void *只是在传递参数的时候起了作用,可以让memcpy()接受不同的指针类型,比如char *,double *,struct stu *等等,没错,只是这样,至于memcpy()内部的工作原理,请继续往下看。没办法,我只好去找找memcpy的源码。代码如下。 备

杨博东的博客 7304

Arm平台下各种memcpy优化对比<一>

Arm平台下各种memcpy优化对比

xiaozhi 2535

NewlibNewlib-Nano区别

newlibnewlib-nano的区别 Newlib-Nano is optimized for size. The printf and scanf family of routines have been re-implemented in Newlib-Nano to remove a direct dependency o...

易得者易失 7266

memcpy函数优化及DMA对比

优化算法结构,提高芯片的使用效率,挖掘芯片的潜在能力,对提高产品质量,降低产品成本有着重要意义,在性能受限的嵌入式设备更加重要。

qq_40044462的博客 7747

glibc 中内存拷贝函数memcpy()研究

glibc内存拷贝函数memcpy()研究。memcpy()函数源代码:void * memcpy (void *dstpp, const void *srcpp, size_t len) { unsigned long int dstp = (long int) dstpp; unsigned long int srcp = (long int) srcpp; /* Copy from

codeMonkey的专栏 1409

memcpy函数与结构体对齐

memcpy函数与结构体对齐

Ztt19990421的博客 2110

程序调试之非法指令(龙芯平台)

更换内核后运行 qrcreator 报非法指令之调试过程 背景: 在中标5.0系统上qtcreator 运行正常,由于中标5.0 自带内核为3.10比较旧,故采用自己移植的4.19。结果换了内核之后qtcreator 运行报非法指令。 主要思路为通过gdb 找出产生非法指令的语句。 具体过程如下: [sancog@localhost ~]$ qtcreator 非法指令 [sancog@localhost ~]$ whereis qtcreator qtcreator: /usr/bin/qtcreato

sense的专栏 1791

glibc--memcpy源码分析

以下是glibc-2.10.1中memcpy函数的源码 #include #include #include #undef memcpy void * memcpy (dstpp, srcpp, len) void *dstpp; const void *srcpp; size_t len; { unsigned long in

astrotycoon 1万+
上一篇: oprofile for godson2e 移植手记
下一篇: Linker script 链接器脚本
Jack Tan
博客等级 码龄21年 28粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值