一、背景与目的

优化算法结构,提高芯片的使用效率,挖掘芯片的潜在能力,对提高产品质量,降低产品成本有着重要意义,在性能受限的嵌入式设备更加重要。
在使用C语言编程时,我们常用memcpy来复制内存数据,最简单的memcpy功能实现如下:

void *memcpy(void *desc,const void * src,size_t size)
{if((desc == NULL) && (src == NULL)){return NULL;}unsigned char desc1 = (unsigned char)desc;unsigned char src1 = (unsigned char)src;while(size-- >0){*desc1 = *src1;desc1++;src1++;}return desc;
}

这是一个标准通用的memcpy函数的实现,满足memcpy的功能,但性能非常低,因为while每一次循环只能复制一个字节。
如果要进一步的优化,就需要用到更多的知识,例如CPU位宽、数据对齐、汇编指令等等,学过计算机原理应该知道CPU字长、寄存器位宽等概念。
现在常见的CPU通常为32/64位,今天我们以32位Arm Cortex -M4来讲解。

二、数据对齐

32位CPU字长为32Bit,即它的每个通用寄存器包含32个位,占4个字节,一个内存访问周期可以完成4个字节的读写。
如果按照标准memcpy函数的实现,每次while循环只能复制1个字节,会浪费大量的内存访问周期。那我们能否按照32位CPU位宽,即4个字节为单位进行内存复制呢?CPU从内存取数据的过程,对齐存放的数据可加快CPU处理的速度,因为在同一个时钟周期内,CPU访问的数据总是按32位对齐访问的。(一些CPU能够非对齐访问,如Arm Cortex -M4支持半字的非对齐访问,和双字的字节对齐访问Arm Cortex -M4内存对齐问题)
例如:

  1. CPU寄存器从内存0x20000001开始取32位数据需要两次访问内存:第一次取3字节0x20000001~0x20000003,第二次取1字节0x20000004。
  2. CPU寄存器从内存0x20000002开始取32位数据需要两次访问内存:第一次取3字节0x20000002~0x20000003,第二次取1字节0x20000004~0x20000005。
  3. CPU寄存器从内存0x20000004开始取32位数据需要一次访问内存:一次取4字节0x20000004~0x20000007。

参考上图,如果需要按对齐方式将0x20000001到0x2000002C中总共44字节的数据拷贝到0x20000041、0x20000082,0x200000C3,0x20000104这几个目标位置,最少的复制过程包括哪些步骤呢?

  • 0x20000001拷贝到0x20000041:四字节对齐。先按字节复制前3个字符,需要循环3次,再按4字节对齐复制0x20000004到0x2000002B之间的数据,共需要循环10次,最后一个字节复制1次,共计14次内存访问。

  • 0x20000001拷贝到0x20000082:因为源和目标无法同时对齐,只能按照字节复制,需要访问内存44次。

  • 0x20000001拷贝到0x200000C3:两字节对齐。先按字节复制前1个字符,需要循环1次,再按2字节对齐复制0x20000002到0x2000002B之间的数据,共需要循环21次,最后一个字节复制1次,共计23次内存访问。

  • 0x20000001拷贝到0x20000100:因为源和目标无法同时对齐,只能按照字节复制,需要访问内存44次。

通过上述分析,我们发现:

  1. 32位CPU中,4字节对齐的数据拷贝能够对齐,性能提升接近4倍;2字节对齐的数据拷贝能够对齐,性能提升接近2倍。

  2. 32位CPU中,源地址和目标地址对4取模的结果一致时为,4字节对齐;源地址和目标地址对2取模的结果一致时为,2字节对齐。

void* memcpy1(void* dst, const void* src, size_t len) {int* d = (int*) dst;const int* s = (const int*) src;for (size_t i=0; i < len; i += 4) {*d++ = *s++;}return d;
}

例如以上代码示例,按int的字长进行拷贝以提高效率。(使用时dst和src都需要4字节对齐,len需要被4整除)

三、指令流水线

指令流水线的作用是将一条指令分割成多个步骤,并由不同的部件顺序完成,
在同一时刻,每个部件可以同时执行多个指令的不同步骤,尽可能保证每个时钟周期都能输出一条指令结果。利用CPU流水线处理的指令的能力,解除数据依赖,(原理是前一条指令在译码,后一条语句在取指,充分压榨CPU处理器)。

void *memcpy2(void *desc,const void * src,size_t size)
{if((desc == NULL) && (src == NULL)){return NULL;}unsigned char desc1 = (unsigned char)desc;unsigned char src1 = (unsigned char)src;unsigned char count = size/4;while(count -- >0){*desc1++ = *src1++;*desc1++ = *src1++;*desc1++ = *src1++;*desc1++ = *src1++;}count = size%4; while(count --){*desc1++ = *src1++;}return desc;
}

例如以上代码,将拷贝循环展开的跳转次数大幅减少,跳转次数最多减少到原来的1/4。

四、汇编版本优化

  1. 利用Arm的汇编指令集中的 LDMIA STMIA指令,通过这两条指令,一次就可以读取/写入多个字节(一次最大读取40字节),极大的加速了拷贝过程。rt_memcpy Cortex-M 汇编加速版
  2. 加载读取的汇编流水线优化

四、DMA

DMA:全称Direct Memory Access(直接存储器访问),把一个地址空间的值“复制”到另一个地址空间,使用DMA传输方式无需CPU直接控制传输,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
本文使用STM32L4R9在主频120M的情况下进行对比实验。其DMA可以配置为传输的类型大小(字节、半字、字)。传输长度32K。

五、对比测试及结果


通过上述测试结果,我们发现:

  1. 按字节、半字、字传输,其效率也随单次拷贝字宽成倍增加。
  2. 汇编的memcpy效率比DMA32也要高2.65倍(后续研究了一下,应该时LDM指令对AHB总线的突发访问长度的配置了较大的数,大幅提高了效率比DMA的还要高。)

memcpy函数优化及DMA对比相关推荐

  1. R语言optim函数进行函数优化实战(和lm函数进行对比)

    R语言optim函数进行函数优化实战(和lm函数进行对比) 目录 R语言optim函数进行函数优化实战(和lm函数进行对比) #仿真数据

  2. 论文: 贝叶斯优化方法和应用综述(1)--------陈述设计类问题举例子,与 model-free优化计算的对比

    陈述:     就是想看一下贝叶斯学派的陈述,从不同的学派的对比,看看有什么优缺点,然后自己思考下.  摘要: 通过设计恰当的概率代理模型和采集函数,贝叶斯优化框架只需经过少数次目标函数评估即可获得理 ...

  3. memcpy函数_[PART][BUG][MSVCRT][C][CCF NOI1097] 关于memcpy的坑

    [Incompleted] CCF NOI1097 试题,本人的源码: Ubuntu Pastebin​paste.ubuntu.com Ubuntu Pastebin Ubuntu Pastebin ...

  4. 计算智能——遗传算法的多维函数优化

    遗传算法函数优化的代码验证学习 一.遗传算法简介 二.遗传算法组成 2.1编码与解码 2.2个体与种群 2.3适应度函数 2.4遗传算子 2.5算法流程图 三.代码实现 3.1Griewank函数 3 ...

  5. 海思IVE函数使用-1 (DMA使用 HI_MPI_IVE_DMA)

    海思IVE函数使用1 (DMA使用 HI_MPI_IVE_DMA) 1 结构体定义 //灰度图像定义 typedef struct {unsigned char *pu8Data; //虚拟地址uns ...

  6. memcpy函数与memmove函数

    1.1 memcpy函数是C语言内存拷贝函数,功能是从原内存地址的起始位置开始依次拷贝若干个字节到从目标地址的起始位置开始依次向后的若干个字节中. void* memcpy(void* dest,co ...

  7. memcpy 函数实现

    memcpy函数的作用: 将由src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内,函数返回一个指向dest的指针. 想必大多数人在面试时被要求写 memcpy的实现 ...

  8. C++实现memcpy函数

    实现memcpy函数 首先是函数接口的实现: void mymemcpy(void* dst, const void* src, size_t num) 这里使用了void*作为指针类型,接口更加通用 ...

  9. memcpy 内存优化方法

    内存拷贝的优化方法 http://www.blogcn.com/blog/cool/main.asp?uid=flier_lu&id=1577430 http://www.blogcn.com ...

最新文章

  1. Windows程序设计------字体不等宽引出的问题及其细节知识
  2. Apache2.2安装图解
  3. 薛宇 AI boom
  4. Know more about CBO Index Cost
  5. 图像条纹检测 python_【连载4.5】特征检测技术研究面向强反射表面的多传感器三维检测技术研究...
  6. java 不同分辨率_java9新特性-14-多分辨率图像 API
  7. schedule php,PHP Laravel定时任务Schedule【干货】
  8. [转载]C#多线程学习(一) 多线程的相关概念
  9. hadoop2.2.0 centos6.4 编译安装详解
  10. python3 介绍
  11. 前端程序员:月薪 5K 到 5 万
  12. Kafka配置3--Windows下配置Kafka集群
  13. java面向对象的基本概念
  14. Linux服务器初步配置JDK+Tomcat+redis
  15. 四级高频词汇360个
  16. python-opencv下读取影像释放内存
  17. 2020 年的风口是什么?
  18. excel日期转换为周数_VBA将日期转换为周数
  19. 26、backtrader的一些基本概念-市价止损单(stop_order)与限价止损单(stop limit order)的创建和撮合逻辑
  20. 使用Python对Dicom文件进行读取与写入的实现(pydicom 和 SimpleITK)

热门文章

  1. 判断模式分解是否为无损连接的方法
  2. Swift - 微信聊天群头像实现 (群聊的组合头像)
  3. 【JPA】记录JPA批量处理的优化
  4. Windows命令行下常用网络命令解释大全
  5. 怎样拿下SUN公司的SCJP认证?
  6. 引力波是怎样产生的?
  7. JavaScript中递归函数
  8. Windows漏洞修复服务器,Windows Server 2008相关系统漏洞修复
  9. Linux C编程 itoa()函数 atoi()函数
  10. 1024专场回顾 | 企业级开源数据库openGauss论坛