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

2023-05-16

在使用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 ++ = *srcp ++;
    
    return dst;
}

一.数据对齐

这通常是一个初学者的实现,满足memcpy的功能,但性能非常低,因为while()每一次循环只能复制一个字节。如果要进一步的优化,就需要用到更多的知识,例如CPU位宽、数据对齐、时钟周期等等,学过计算机原理应该知道CPU字长、寄存器位宽等概念。现在常见的CPU通常为32/64位,今天我们以32位CPU来讲解。32位CPU字长为32BIT,即它的每个通用寄存器包含32个位,占4个字节,一个内存访问周期可以完成4个字节的读写,如果按照low_memcpy()函数的实现,每次while()循环只能复制1个字节,会浪费大量的内存访问周期。那我们能否按照32位CPU位宽,即4个字节为单位进行内存复制呢?CPU从内存取数据的过程,了解的人都知道,对齐存放的数据可加快CPU处理的速度,因为在同一个时钟周期内,CPU访问的数据总是按32位对齐访问的。例如CPU寄存器从内存0x20000001开始取32位数据需要两次访问内存:第一次取3字节0x20000001~0x20000003,第二次取1字节0x20000004。但是,如果从0x20000008开始取32位数据则可以一次性全部取出。

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

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

从0x20000001到0x20000082:因为源和目标无法同时对齐,只能按照字节复制,需要访问内存48次,为什么源和目标必须对齐呢?假设复制到0x20000004时源正好对齐到4字节,但是此时该地址的数据将被复制到0x20000085,但目标地址并不对齐。

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

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

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

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

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

1.如果数据能够对齐,访问内存的次数只有单字节复制的1/3,即性能提升接近3倍。

2.在32位机上源和目标对4取模的结果一致才能对齐。

二.指令流水线

除了数据对齐外,是否能从CPU指令流水线方面作出一些优化呢?指令流水线的作用是将一条指令分割成多个步骤,并由不同的部件顺序完成,在同一时刻,每个部件可以同时执行多个指令的不同步骤,尽可能保证每个时钟周期都能输出一条指令结果。如果你了解指令流水线应该明白,跳转指令将导致控制冒险,C语言中的if{}else{};while();for(;;){};以及switch case都将可能生成跳转指令。那么,如何避免或减少控制冒险呢?增加循环中的代码量可以减少控制冒险。

int a1[ARRAY_COUNT] = { 0xff };
int a2[ARRAY_COUNT] = { 0x00 };

void test1(void)
{
    int i;

    for (i = 0; i < ARRAY_COUNT; i++)
    {
        a2[i] = a1[i];
    }
}

void test2(void)
{
    int i;

    for (i = 0; i < ARRAY_COUNT/8; i+=8)
    {
        a2[i + 0] = a1[i + 0];
        a2[i + 1] = a1[i + 1];
        a2[i + 2] = a1[i + 2];
        a2[i + 3] = a1[i + 3];
        a2[i + 4] = a1[i + 4];
        a2[i + 5] = a1[i + 5];
        a2[i + 6] = a1[i + 6];
        a2[i + 7] = a1[i + 7];
    }
}

例如以上代码,test1和test2的目的都是将a1复制到a2,但是test1会执行64次循环,每次循环都将导致控制冒险,而test2仅有8次循环,控制冒险减少为8次。除此之外,test1执行过程中CPU需要执行的指令数量比test2至少多56条比较和跳转指令,共计112条指令。

3.优化结果

废话少说,直接上代码:

/*
 *    Copyright(C) 2013-2015, Fans-rt development team.
 *
 *    All rights reserved.
 *
 *    This is open source software.
 *    Learning and research can be unrestricted to modification, use and dissemination.
 *    If you need for commercial purposes, you should get the author's permission.
 *
 *    date           author          notes
 *    2014-09-07     JiangYong       new file
 */
#include <config.h>
#include <debug.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>

void *fast_memcpy(void *dst, const void *src, size_t length)
{
    union{
        char *lpstr;
        uint32_t *lpint;
    }s;
    union{
        char *lpstr;
        uint32_t *lpint;
    }d;
    
    char *suffix = (void*)(((uint32_t)src) + length);
    char *prefix = (void*)(((uint32_t)src) & (~(sizeof(uint32_t)-1)));
    uint32_t *middle = (void*)(((uint32_t)suffix) & (~(sizeof(uint32_t)-1)));

    
    s.lpstr = (void*)src;
    d.lpstr = (void*)dst;
    
    if (prefix != src)
    {
        while(s.lpstr < prefix + sizeof(uint32_t))
        {
            *d.lpstr++ = *s.lpstr ++;
        }
    }

    while(s.lpint < middle - (sizeof(uint32_t) * 8))
    {
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
    }

    while(s.lpint < middle - (sizeof(uint32_t) * 4))
    {
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
    }

    while(s.lpint < middle - (sizeof(uint32_t) * 2))
    {
        *d.lpint++ = *s.lpint ++;
        *d.lpint++ = *s.lpint ++;
    }

    while(s.lpint < middle) *d.lpint++ = *s.lpint ++;
    while(s.lpstr < suffix) *d.lpstr++ = *s.lpstr ++;

    return dst;    
}

void *memcpy(void *dst, const void *src, size_t length)
{
    if ((((uint32_t)src) & (~(sizeof(uint32_t)-1))) != (((uint32_t)dst) & (~(sizeof(uint32_t)-1))))
    {
        char *lpSrc = (void*)src;
        char *lpDst = (void*)dst;
        char *lpTail = lpSrc + length;
        
        while(lpSrc < lpTail) *lpDst ++ = *lpSrc ++;
        
        return dst;
    }
    
    return fast_memcpy(dst, src, length);
}

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

谈谈memcpy函数的优化(纯C) 的相关文章

  • 使用说明-Postman-带cookie请求、文件上传

    Postman进行文件上传 选择post方式 xff0c 地址是http 192 168 102 213 7240 foa system upload Headers部分不要填写任何内容 对照后台的接口 xff0c 配置postman 选择
  • CentOS7下使用docker,完成Jenkins镜像、tomcat镜像制作和启动

    最终的目的 xff0c 是为了完成docker环境的Jenkins搭建使用 xff0c 并从gitlab上获取代码 xff0c 打出war包 xff0c war包通过目录挂载的方式 xff0c 在tomcat容器中使用 xff0c 总体思路
  • C# HttpWeb POST请求封装

    用于发送POST请求 xff0c 可以发送各种POST参数 传送文件 xff0c 返回结果 下载文件 说明如下 xff1a span class hljs keyword public span span class hljs keywor
  • [ROS学习笔记]ROS中使用激光雷达(RPLIDAR)

    RPLIDAR是低成本的二维雷达解决方案 xff0c 由SlamTec公司的RoboPeak团队开发 xff0c 本次学习用的是RPLidar A1型号激光雷达 xff0c 它能扫描360 xff0c 6米半径的范围它适合用于构建地图 xf
  • Ubuntu 18.04 配置国内源

    安装Ubuntu 18 04后 xff0c 使用国外源太慢了 xff0c 修改为国内源会快很多 修改阿里源为Ubuntu 18 04默认的源 备份 etc apt sources list 备份 cp etc apt sources lis
  • VS2010 断点失效解决方案 (VC)

    遇到几次断点失效的问题 xff0c 下面的方法都用了一下 xff0c 不清楚是哪个方法起作用了 一 菜单 调试 选项和设置 调试 常规 要求源文件与原始版本完全匹配 取消这个功能 二 菜单 编辑 高级 设置选定内容的格式 把打不上断点的那个
  • stm32学习

    波特率 xff1a 每秒传送的位数 DMA Direct Memory Access xff0c 直接内存存取 是所有现代电脑的重要特色 xff0c 它允许不同速度的硬件装置来沟通 xff0c 而不需要依赖于 CPU 的大量中断负载 否则
  • 经纬高坐标系转到东北天坐标系

    经纬高坐标系转到东北天坐标系 基本思路 xff1a 首先把经纬高 xff08 大地坐标系 lla llh xff09 转到直角坐标系 xff08 地心地固直角坐标系 xff08 ECEF xff09 xyz xff09 然后再转为局部坐标系
  • Ubuntu20.04 配置D435i相机

    文章目录 一 安装使用 InterRealSenseD435i SDK21 注册服务器的公钥2 安装3 安装开发者和调试包4 测试SDK2 二 安装realsense ros1 创建工作空间2 源码安装3 编译 xff1a 三 安装kali
  • java打卡-day3 变量和数据类型

    变量和数据类型 基本数据类型分类 4类8种 整数型 byte 占一个字节 128到127short 占两个字 215 215 1int 占四个字节 231 231 1long 占八个字节 263 263 1 浮点型float 占四个字节 3
  • 【行人惯性导航】关于行人导航中IMU位姿推导的知识点及相关代码

    IMU姿态惯性推导 本文是我上学期间写得 xff0c 之前已经在另一个博客发布过 xff0c 如今转至此发布 最近从事行人惯性导航的研究 xff0c 本人也是一个小白 xff0c 其中看了很多文献 xff0c 有很多个人思考很费时间的地方
  • nuttx操作系统的移植以及下载

    1 在ubuntu根目录下 xff1a root 64 ubuntu apt get update 更新包 2 root 64 ubuntu apt get install gcc arm none eabi 编译器 3 kconfig f
  • MPI集群环境搭建

    我在前面两篇博客中简要介绍了为什么要并行计算以及MPI的一些学习心得 xff0c 接下来我们正式开始MPI的学习之路 我们知道MPI是分布式内存编程 xff0c 所以这篇博客会详细讲解MPI集群环境的搭建过程 一 准备工作 选择Linux版
  • python 练习 tcp 服务器与客户端发、接信息,pycharm

    背景 win8 1 pycharm 2021 3 1 python 3 9 7 自带idle vs code 1 64 2 现象 1 代码不知道如何在idle 和 vs code中运行 xff0c 总是在运行客户端代码后 已先运行服务器代码
  • VSCode配置终端为cmd命令行程序的操作步骤

    步骤1 xff1a 依次点击菜单栏的 终端 新建终端 步骤2 xff1a 按图中红色箭头的方向和方框所示 xff0c 依次点击 步骤3 xff1a 点击图中红框处的command product xff0c 下次再启动时的VSCode终端就
  • VC/C++ 发送post请求

    前面我搭建了一个servlet xff0c 响应get post请求 xff0c 网页端已经实现get post请求 xff0c 这个时候我用c 43 43 编辑的程序也想发送post请求 xff0c 于是 xff0c 有了下面这段代码 s
  • Python subprocess模块解析

    在学习这个模块前 xff0c 我们先用Python的help 函数查看一下subprocess模块是干嘛的 xff1a DESCRIPTION This span class hljs keyword module span allows
  • Java与C/C++的性能对比

    写这个主题是因为若干时间前一时头脑发热 xff0c 写了这个帖子 xff08 http www iteye com topic 857722 xff09 xff0c 现在看来这个帖子很幼稚 xff0c 尤其是二楼 61 61 xff0c 后
  • char数组与char指针的区别与联系

    字符串 xff08 char xff09 与字符数组 xff08 char 区别 在C语言中 xff0c 对字符串的操作主要有两种方式 xff1a 一是字符数组 char xff0c 二是使用字符指针 char 接下来最这两种所使用的情况做
  • Ubuntu系统火狐浏览器无法上网方案解决

    在国内google浏览器无法直接使用 xff0c 我们安装ubuntu系统自带的火狐浏览器在设置中选择的事google上网 xff0c 所有大家网络连接没问题 xff0c 就是无法上网 解决问题如下 xff1a 第一种 xff1a 重新安装

随机推荐

  • 解决ROS常遇到的Couldn’t find executable named报错解决

    解决办法 xff1a 将执行文件打开权限允许作为程序执行文件
  • ROS学习(一)ROS Noetic安装及环境配置

    文章目录 前言0 ROS Installation Options1 Installation1 1 Configure your Ubuntu repositories1 2 Setup your sources list1 3 Set
  • 嵌入式Linux 时间同步 gpsd+chrony

    嵌入式Linux 时间同步 gpsd 43 chrony 嵌入式Linux系统 xff0c 外接GPS设备 xff0c 系统通过NMEA数据和pps进行时间同步 xff0c 同时将本系统作为时间同步服务器 一 基本原理 NMEA中获取UTC
  • ModbusRTU串口读写报文解析

    0 MODBUS速览 Modbus是一种通信协议 xff0c ModubsRTU是它的一种传输模式 xff0c 通过这个协议能实现串口数据通讯 可以用C 控件读写串口 xff0c 也可以直接使用开源的串口助手 为了简化问题 xff0c 本文
  • Types of daTabases数据库类型

    NoSQL databases provide the performance scalability and stability that s required by the modern data driven apps we inte
  • jeston TX1_TX2 ubuntu18.04安装(国内安装源gitee)ROS Melodic

    查看ubunut系统信息 cat proc version uname a lsb release a 我自己的系统信息是 xff1a 由于我自己已经迁移了系统至SD卡 xff0c 所以存储比较富裕 xff1a 通过对应的ubuntu版本安
  • 9 个将改变一切的物联网应用

    无论您是有远见的 CEO 技术驱动型 CEO 还是 IT 领导者 xff0c 您之前都遇到过 IoT 一词 它经常与最高级一起使用 xff0c 表示它将如何彻底改变您的工作 娱乐和生活方式 但这只是另一个流行语 xff0c 还是承诺的技术圣
  • Android aplog是什么?

    aplog 是系统应用层的log xff0c 比如应用程序无响应或强行关闭 xff0c kernel相关的 xff0c 比如driver xff08 相机 蓝牙 usb 启动 xff09 出了开机问题 手机对服务唤醒和休眠的一些记录蓝牙相关
  • android 7.0 加入 android:directBootAware

    Android N引入了一个新特性 xff1a Direct Boot Mode 设备启动后进入的一个新模式 xff0c 直到用户解锁 xff08 unlock xff09 设备此阶段结束 在此 Direct Boot Mode 下 APP
  • Kotlin概述与Java的比较

    Kotlin是JetBrains的一种新的编程语言 它首次出现在2011年 xff0c JetBrains推出了名为 科特林 的项目 Kotlin是开源语言 基本上像Java一样 xff0c C和C 43 43 Kotlin也是 静态类型编
  • armlink 用法详解

    映像文件 image xff1a 是指一个可执行文件 xff0c 在执行的时候被加载到处理器中 一个映像文件有多个线程 它是ELF Executable and linking format 格式的 段 Section xff1a 描述映像
  • 人工智能的五大核心技术

    计算机视觉 机器学习 自然语言处理 机器人和语音识别是人工智能的五大核心技术 xff0c 它们均会成为独立的子产业 计算机视觉 计算机视觉是指计算机从图像中识别出物体 场景和活动的能力 计算机视觉技术运用由图像处理操作及其他技术所组成的序列
  • ubuntu下文件拷贝命令cp命令

    该命令的功能是将给出的文件或目录拷贝到另一文件或目录中 xff0c 就如同DOS下的copy命令一样 xff0c 功能非常强大 语法 xff1a cp xff3b 选项 xff3d 源文件或目录 目标文件或目录 说明 xff1a 该命令把指
  • android camera 照相机/摄像机

    一 camera启动 1 一个activity启动调用流程 xff1a onCreate gt onStart gt onResume onCreate xff1a 1 可添加所需布局文件 xff0c 画界面 2 开启线程 xff0c 启动
  • 正确学习javascript。困惑的指南

    正确学习 javascript 困惑的指南 迷失了 javascript xff1f 这一点都不奇怪 你需要一个能告诉你真正重要的指南 我们只有一个 向左看 有一个框架市场 xff0c 它们相互竞争 xff0c 赞扬它们的能力 向右看 这是
  • 【ROS学习2】创建ROS工作空间及功能包

    目录 创建ROS工作空间及功能包工作空间 workspace src 代码空间 Source Space build 编译空间 Build Space devel 开发空间 Development Space install 安装空间 In
  • 【GStreamer 】5-5-总结USB相机转RTSP网络视频流-推流usb摄像头JPEG

    我们使用gstreamer rtsp server xff0c 实现了USB相机转RTSP网络流的基本功能 xff0c 之前很多篇都讲了如何实现 xff0c 这一次我们集中精力解决之前的一些问题 文章 GStreamer 5 4 USB相机
  • C程序设计语言 5-3

    练习5 3 用指针方式实现第2章中的函数strcat 函数strcat s t 将t指向的字符串复制到s指向的字符串的尾部 include lt stdio h gt int my strcat char char int main cha
  • NuttX 文件系统架构介绍(7.14)

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

    在使用C语言编程时 xff0c 我们常用memcpy来复制内存数据 xff0c 但很少有人会关注到memcpy怎么实现 最简单的memcpy功能实现如下 xff1a void low memcpy void dst const void s