龙芯版 memcpy 的实现

MIPS指令集:汇编源程序(.S文件)编写 我们学会了MIPS汇编语言后,编写汇编程序还需要一个“框架”,用“框架”的目的是让我们更加容易的编写汇编程序,把更复杂的任务,比如符号解析、地址重定向、对齐等工作交给工具链去完成。这个框架有2种方式,汇编源代码文件(以.S为后缀)和内嵌汇编(在.c文件中嵌入汇编语言的方式)。本篇介绍.S为后缀的汇编源程序的编写格式。 我们可能经常遇到以.S后缀结尾和.s后缀结尾的... 阅读详情

作者: comcat 发表日期: 2007-06-15 17:50      http://comcat.blog.openrays.org/blog.php?do=showone&tid=423

 

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 看输出是否正确
【计算机组成】MIPS指令集介绍和MIPS模拟器使用 文章目录MIPSsim下载和使用Mars下载和使用 学习计算机组成,不学汇编语言,等于白学。对我来说,相当于把以后学的汇编课程移到现在开始学。 学习汇编的好处在于: 有助于深刻理解计算机的软硬件接口,理解编程语言的底层; 汇编的助记语法差不多,因此学了一门,其他种类就很容易吸收了。 我的目标是学3门汇编语言,分别是Computer Organization and Design: The Ha... 阅读详情

相关推荐

【优化系列】汇编优化技术(十一):MIPS架构MSA Intrinsic优化及demo

DATE: 2021.11.17 文章目录1、简介2、msa寄存器类型2.1、矢量寄存器类型2.2、立即数类型3、MSA Intrinsic函数3.1、load/store3.2、Logic Shift3.3、Arithmetic3.4、Saturate3.5、Shuffle3.6、Interleave3.7、Copy/Splat4、注意事项5、mips msa intrinsic优化demo6、龙芯3A4000优化建议7、更多资料 1、简介 GCC提供了intrinsics来获取MSA(MIPS SIM

优快云明星博主,认证博客专家,视频、Matlab领域优质创作者。 1309

写出高性能的C代码---编写拷贝函数

在上一章《写出高性能的C代码---深入理解编译器和硬件平台》中,我写道:如果有标准的库函数可以调用,就不要自己写,绝大多数的人水平远远达不到编写标准库大牛的水平。 这章为边把memcpy这个标准库函数拉出来述说这个观点。 如果观众老爷们自己写个关于memcpy函数会是怎么样的呢? 首先我们来看下memcpy的描述   void *memcpy(void *dest, co...

wjb123sw99的博客 724

VM与欧姆龙PLC通讯设置实战:Fins TCP/UDP协议详解

本文详细解析了VM(VisionMaster)与欧姆龙PLC通过Fins TCP/UDP协议进行通讯的实战设置。内容涵盖网络配置、协议选择(TCP可靠性与UDP实时性)、VM端地址映射与数据点添加、以及数据接收解析流程,并提供了常见错误排查与高级调试技巧,帮助视觉与自动化工程师快速实现稳定可靠的工业通讯集成。

weixin_29172963的博客 328

c语言中memcpy是字节大小,使用memcpy函数时要注意拷贝数据的长度

memcpy函数简介memcpy函数是C/C++语言中的一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)。其原型是:void *memcpy(void *destin, void *source, unsigned n);作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。函数有三个参数,第一个是目标地址,第二...

weixin_42298373的博客 8167

FPGA新手必看:Create HDL Wrapper和Generating Output Products到底该先用哪个?

本文深入解析了FPGA设计中Create HDL Wrapper与Generating Output Products的正确使用顺序,帮助新手避免常见错误。详细介绍了两者的功能差异、执行顺序的底层逻辑以及工程管理的最佳实践,特别适合使用Vivado进行FPGA开发的初学者。

xk7n92h3p5m的博客 1065

基于大模型的癫痫手术预测与决策系统技术方案文档

基于大模型的癫痫手术预测与决策系统技术方案文档

嵌入式毕设/课设资料清单(速查手册·文档模板·题库,持续更新)→ 见最新文章 975

memcpy的效率问题

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

wo__chenjing的专栏 8190

Matlab Robotics Toolbox实战:四自由度机械臂建模与仿真全流程(附代码)

本文详细介绍了使用Matlab Robotics Toolbox进行四自由度机械臂建模与仿真的全流程,包括DH参数定义、模型构建、可视化、运动学仿真及交互控制。通过实战案例和代码示例,帮助读者快速掌握机械臂建模技巧,提升机器人学习效率。

weixin_42538752的博客 415

mips1处理器内存操作优化总结

mips1处理器内存操作优化总结1.目前的uClibc库函数中的memcpy、memset、bzero基本上都是采用汇编的单字节操作;2.可以用四字节操作大内存提高这些函数效率,效果比较明显,基本上可以提高3~4,当然达不到4倍;3.充分利用MIPS处理器流水线特性-延迟槽,减少主循环中指令的个数。          基本上减少一条主循环中的指令就可以提高1/N的效率(原循环中有N

zengwh的专栏 2033

【温室气体数据集】CAMS 发布的温室气体(GHG)数据集总结

【温室气体数据集】CAMS 发布的温室气体(GHG)数据集总结

WW、forever的博客 555

A100 NVLink集群极致优化指南

《A100 NVLink集群通信优化指南》摘要:针对A100 80G SXM机型NVLink 3.0集群,提供硬件配置检查方法(nvidia-smi topo -m)和强制启用指令。重点介绍NCCL参数调优(算法选择、低延迟协议配置)、GPUDirect RDMA启用步骤,以及单机/多机场景下的通信模式选择。包含PyTorch all_reduce基准测试方法,并给出带宽异常排查方案(dmesg检查、NCCL缓存重置)。通过CUDA Graph通信重叠和混合精度优化,可实现90%+带宽利用率。附猿界算力监控

2401_89353317的博客 587

memcpy实现

版权声明:本文为博主原创文章,未经博主允许不得转载。    https://blog.youkuaiyun.com/x30465947/article/details/80493069 void *memcpy(void *dest, const void *src, int count) {     void *ptr = dest;       if (NULL == dest || NULL == ...

csshuke的专栏 599

玩转大语言模型——使用华为官方MindIE-Server镜像方式部署DeepSeek模型

本文将详细介绍在国产Linux系统欧拉中使用华为官方MindIE-Server镜像方式部署DeepSeek模型。一般情况下华为昇腾800I服务器可以推理DeepSeek-R1-70B,4张300I-Duo服务器可以推理DeepSeek-R1-32B。在配置中尽量贴合官方文档,包含用到的一些其他的技术细节,便于零基础入门使用。

艾醒的博客 1289

memcpy_s 参数问题

memcpy_s 这个函数极易用错。它的函数申明是这样的:errno_t memcpy_s( void *dest, size_t numberOfElements, const void *src, size_t count ); 这是由 MS 提供的一个内存拷贝函数。对于各个参数的解释是这样的:destNew buffer.numberOfElementsSize o...

lizhihaoweiwei的专栏 2万+

Unreal5从入门到精通之如何在VR中使用3DUI

前面我们讲过《Unreal5从入门到精通之UMG的使用》一般制作游戏时,UI都是添加到屏幕视窗上面的,但是在制作VR时,我们往往需要制作一个3D的UI,放置在场景中,然后使用手柄的射线来和UI进行交互。今天我们就来讲解下,如何在VR中创作和交互3DUI首次操作会感觉好复杂,多多练习,当你熟悉了UE蓝图的操作后会发现,这只是一个常规的蓝图操作而已。祝大家在虚幻的学习过程中非常顺利。希望这篇博客对你有所帮助。

TxNet 1985

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

内存拷贝函数memcpy函数深入剖析

memcpy(拷贝内存内容)   定义函数:void * memcpy( void * dest, const void *src, size_t n ); memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束'/0'而结束。memcpy()函数可以拷贝任意类型的数据。memc

前端产品工程师 9976

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

memcpy优化比较

测试: #include #include #include #include #include typedef unsigned int u_int; #define N 1024*1024*16 #define M 1000 void movsb_cpy1(void* Dst,void* Src,size_t Maxsize) ; void movsd_cpy(void* Ds

纯,属虚构 2787

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

linux mips启动分析

linux-mips启动分析 (1) 系统加电起动后,MIPS 处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是 0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将 Linux 内核映像拷贝到  RAM  中某个空闲地址处,然后一般有个内存移动操作,目

SdustLiYang的专栏 9629

MIPS汇编的HolleWorld

概要 我们学会了MIPS汇编语言后,编写汇编程序还需要一个“框架”,用“框架”的目的是让我们更加容易的编写汇编程序,把更复杂的任务,比如符号解析、地址重定向、对齐等工作交给工具链去完成。这个框架有2种方式,汇编源代码文件(以.S为后缀)和内嵌汇编(在.c文件中嵌入汇编语言的方式)。本篇介绍.S为后缀的汇编源程序的编写格式。 我们可能经常遇到以.S后缀结尾和.s后缀结尾的文件。它们都是汇编源文件(可以作为gcc as汇编器的输入)。区别在于.S是GCC编译的汇编源代码文件。编译后生成的输出文件就是.s。之前介

极客驿站 792

memcpy_chk.c

__memcpy_chk memcpy

xkdlzy的专栏 1421

mips下非对齐访问问题分析

1. 问题 RISC 下使用访存指令读取或写入数据单元时,目标地址必须是所访问之数据单元字节数的整数倍,这个叫做地址对齐。 比 如在 MIPS 平台上,lh 读取一个半字时,存储器的地址必须是 2 的整数倍; lw 读取一个字时,存储器的地址必须是 4的整数倍; sd 写入一个双字时,存储器的地址必须是 8 的整数倍。倘若访存时,目标地址不对齐,则会引起异常,典型的是系统提示“总线错误

做一个有技术追求的人 7566
上一篇: 龙芯下汇编语言编程(2)
下一篇: Linux 上下文切换分析笔记 (MIPS)
LonlyGod
博客等级 码龄15年 32粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值