linux中memcpy实现分析,ARM64 的 memcpy 优化与实现

如何优化 memcpy 函数

Linux 内核用到了许多方式来加强性能以及稳定性,本文探讨的 memcpy 的汇编实现方式就是其中的一种,memcpy 的性能是否强大,拷贝延迟是否足够低都直接影响着整个系统性能。通过对拷贝函数的理解可以加深对整个系统设计的一个理解,同时提升自身技术实力。

罗马不是一天建设而成的,Linux 内核的拷贝函数也不是一开始就是那么优秀,在 3.14 之前(具体多少版本忘记了),Linux 尚且没有完善对 ARM64 架构的支持,系统的内存拷贝函数就是一个简单的 c 语言版本,也就是目前内核中的通用拷贝函数。

#ifndef __HAVE_ARCH_MEMCPY

/**

* memcpy - Copy one area of memory to another

* @dest: Where to copy to

* @src: Where to copy from

* @count: The size of the area.

*

* You should not use this function to access IO space, use memcpy_toio()

* or memcpy_fromio() instead.

*/

void *memcpy(void *dest, const void *src, size_t count)

{

char *tmp = dest;

const char *s = src;

while (count--)

*tmp++ = *s++;

return dest;

}

EXPORT_SYMBOL(memcpy);

#endif

在没有定义 __HAVE_ARCH_MEMCPY 之前,内核就会采用最简单的逐字节拷贝,我相信一个刚入学的大学生也能写得出一个这样的代码,完全不需要考虑对齐,不需要考虑性能等等,就是这么直白,这么暴力的拷贝数据。

当然,我们不可能真的采用这样的代码来运转系统,不然再好的硬件能力也会被粗糙的代码毁掉,那么不如一起来做一个简单的优化?

现代计算机已经不再是 20 世纪时代的 16 位机甚至更早的 8 位机,一个寄存器宽度已经达到了惊人的 64 位(32 位机器也会在这两年被主流淘汰掉,大部分的操作系统已经不再提供 32 位支持),既然如此,何不将这个一个特性利用起来。

void *memcpy(void *d, void *s, size_t count)

{

int i;

for (i = 0; i < count / sizeof(int64_t); i++) {

(int64_t *)d++ = (int64_t *)s++;

}

return d;

}

这样是不是舒服多了(代码没有考虑 count 不能被整除的情况,仅仅做一个演示),一条指令下去就可以完成 8 个字节的拷贝,这样整个循环体直接缩减为原来的 1/8,效率是上一版本的 8 倍之多。那么仅此而已吗?

不然,在 CPU 的指令上,跳转指令的耗时是很高的,软件应该尽可能的减少 CPU 跳转,上面的代码没做完一次 8 字节的拷贝之后就需要完成一个跳转,那么是不是可以减少一些跳转呢?当然,那就是循环展开:

void *memcpy(void *d, void *s, size_t count)

{

int i;

for (i = 0; i < count / sizeof(int) / 4; i++) {

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

}

return d;

}

循环展开也做了,有没有其他的方式可以继续优化呢?当然有,尽管 ARM64 的机器指令宽度为 64 位,最多一次能存储 8 个字节,但是他还有更为高级的寄存器,那就是向量寄存器,通过 NEON 指令处理,可以一次性搬移 128 位数据,也就是 16个字节,这样效率又提升一倍,通过代码演示一下:

#include

void *memcpy_128(void *dest, void *src, size_t count)

{

int i;

unsigned long *s = (unsigned long *)src;

unsigned long *d = (unsigned long *)dest;

for (i = 0; i < count / 64; i++) {

vst1q_u64(&d[0], vld1q_u64(&s[0]));

vst1q_u64(&d[2], vld1q_u64(&s[2]));

vst1q_u64(&d[4], vld1q_u64(&s[4]));

vst1q_u64(&d[6], vld1q_u64(&s[6]));

d += 8; s += 8;

}

return dest;

}

上面的代码通过 NEON 改造之后,一次循环体可以处理 64 字节的数据,大大的加快了拷贝效率。还有没有更好的优化方式?当然是有的,那就是用汇编来写

当前 ARM64 构架的实现方式

熟悉 Linux 内核的都知道,Linus 为了让 kernel 跑得更快,更健壮,代码能够重复利用就一定重复利用,不但可以减少生成的二进制 bin 文件大小,而且能减少维护成本,arch/arm64/lib/memcpy.S 就是这样的例子。

ENTRY(__memcpy)

ENTRY(memcpy)

#include "copy_template.S"

ret

ENDPIPROC(memcpy)

ENDPROC(__memcpy)

memcpy.S 直接 include 了一个 copy_template.S 的文件,其实就是直接贴上了这样的一份代码,这个 copy_template.S 不仅仅只是在 memcpy.S 中用到,在其他的类似 copy_to_user.S 和 copy_from_user.S 中也被包含。

既然如此,我们只需要深入分析 copy_template.S 即可。这里不贴代码进行逐行分析,因为也没有什么好分析的,当你完全理解设计思想,再对着代码你主需要理解每一行的汇编是什么意思即可。

53c61adf3b355d0c7a1e64f4618e600c.png

从上图可以看出,拷贝算法将数据分为 3 个大的部分,第一个部分就是不对齐部分,通过对传入的 src 地址进行分析,首先处理掉不能被 16 整除的前面不对齐数据,然后处理对齐的数据。

对齐的数据以 128 为一个界限,每一个 128 字节数据都能通过大块拷贝直接计算完毕,一直循环到最后剩余的尾部 128 以下的字节。

整体设计逻辑流程图如下:

f5f413785a7aa52f133e997499b252e7.png

大体思想很简单,那就是首先处理不对齐,之后处理大拷贝部分,然后细分到最小的各个部分,通过利用寄存器宽度来减少拷贝次数。

比如最后的 120 个字节会被分为:120 = 64 + 32 + 16 + 8,这样处理可以得到最佳的性能。

memcpy 拷贝性能测试

编写一个新的算法当然需要对他进行性能测试,那么该如何做性能测试呢?当然是需要编写一个内核驱动,可以随意百度一个 HelloWorld 的模块,参考其逻辑编写一个简单的模块,在 module_init 的函数中写入这样的一段测试代码,等模块加载完毕之后,会附带打印当前输入的测试的 memcpy 算法的性能。

typedef void *(*memcpy_t)(void *, void *, size_t);

void memcpy_speed_test(memcpy_t __memcpy, void *b1, void *b2)

{

int speed;

unsigned long now, j;

int i, count, max;

preempt_disable();

max = 0;

for (i = 0; i < 5; i++) {

j = jiffies;

count = 0;

while ((now = jiffies) == j)

cpu_relax();

while (time_before(jiffies, now + 1)) {

mb(); /* prevent loop optimzation */

__memcpy(bench_size, b1, b2);

mb();

count++;

mb();

}

if (count > max)

max = count;

}

preempt_enable();

speed = max * (HZ * bench_size / 1024);

printk(KERN_INFO "memcpy_test: %5d.%03d MB/sec\n", speed / 1000, speed % 1000);

}

其实还有一个优化的点就是注意 L1 Cache 的对齐,这个在汇编代码中有体现,C 语言版本就不提及 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/339644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ejb生命周期_EJB 3.x:生命周期和并发模型(第2部分)

ejb生命周期这是两部分系列的第二篇。 第一部分介绍了有状态和无状态EJB的生命周期以及并发行为。 我将在本文中介绍Singleton EJB 。 Singleton模式可以说是最常用&#xff08;有时被滥用&#xff01;&#xff09;的模式。 单吨又爱它&#xff01; Java EE使我们无需编写显…

linux修改文件没有备份文件,linux文件或目录权限修改后如何恢复(备份了权限就能恢复)...

操作系统 RHEL5如果你在linux上执行了如下操作chmod -R 777 / 或者 chmod -R 700 /那么恭喜你&#xff0c;你的系统即将崩溃&#xff0c;重启之后&#xff0c;你进不了图形界面&#xff0c;而且很多服务都起不来为什么呢&#xff1f;因为linux中&#xff0c;系统的有些文件和目…

JDK 14 / JEP 305模式匹配“ Smart Casts”实例

我通常将Java代码中instanceof运算符的存在视为“ 红色标志 ”&#xff0c;这意味着在某些情况下使用instanceof不一定是错误的&#xff0c;但是使用它有时表示可以以一种更干净的方式解决设计问题&#xff0c;如所述本文末尾引用的一些资源中的内容&#xff08;包括有关Java以…

linux美化原理,x-window字体原理及美化

x-window字体原理及美化发布时间:2006-10-07 01:25:15来源:红联作者:caldo1. 简介在我必须处理的一堆讨厌事中&#xff0c;有一项就是没完没了的 X 缺省字体和字体设定 (我专指 XFree86&#xff0c;其它的 X 也许比较好)。有些程序缺省使用固定宽度字体 (fixed width fonts)&am…

易流即时配送_即时大数据流处理=即时风暴

易流即时配送在Ubuntu背后的公司Canonical&#xff0c;每6个月进行一次技术工作&#xff0c;以第一手测试我们的工具并向其他人展示新想法。 这次&#xff0c;我创建了一个即时大数据解决方案&#xff0c;更具体地讲是“即时风暴”。 Storm现在是Apache基金会的一部分&#xf…

c语言有参有类最小公倍数,【C语言】写一个函数,并调用该函数求两个整数的最大公约数和最小公倍数...

程序分析&#xff1a;在数学中&#xff0c;两个数的最小公倍数两个数的乘积/两数的最大公约数。求两个数的最大公约数&#xff0c;运用辗转相除法&#xff1a;已知两个整数M和N&#xff0c;假定M>N&#xff0c;则求M%N。如果余数为0&#xff0c;则N即为所求&#xff1b;如果…

csp真题字符串匹配c语言,CCF CSP认证考试历年真题 模板生成系统 C语言实现

试题编号&#xff1a;201509-3试题名称&#xff1a;日期计算 时间限制&#xff1a;1.0s 内存限制&#xff1a;256.0MB问题描述&#xff1a;成成最近在搭建一个网站&#xff0c;其中一些页面的部分内容来自数据库中不同的数据记录&#xff0c;但是页面的基本结构是相同的。例如&…

osgi架构与linux_OSGi:进入微服务架构的门户

osgi架构与linux在构建可扩展&#xff0c;可靠的分布式系统的背景下&#xff0c;“模块化”和“微服务体系结构”这两个术语如今经常出现。 众所周知&#xff0c;Java平台本身在模块化方面很弱&#xff08; Java 9将通过交付Jigsaw项目来解决此问题&#xff09;&#xff0c;这为…

引入我们全新的YouTube频道进行视频课程编程

嘿&#xff0c;极客们&#xff0c; 收到社区的反馈并紧贴行业发展趋势&#xff0c;我们非常高兴宣布推出全新的Youtube频道 &#xff01; 在我们的频道上&#xff0c;我们将主持与Java编程有关的视频课程&#xff0c;但通常也会进行软件开发。 我们将介绍代码演练以及完整的…

田忌赛马c语言程序设计,还是杭电1052田忌赛马

已结贴√问题点数&#xff1a;20 回复次数&#xff1a;2还是杭电1052田忌赛马//昨天那个算法漏洞挺大&#xff0c;但我重新构思了&#xff0c;但运行到312ms还是wa了。我测试了许多数据&#xff0c;结果是对的&#xff0c;郁闷了&#xff0c;谁能救救我啊&#xff1f;#include&…

ArrayList clone()– ArrayList深拷贝和浅拷贝

示例程序以ArrayList克隆方法为例。 学生对象上的ArrayList深层复制和浅层复制示例。 1.简介 ArrayList clone&#xff08;&#xff09;– ArrayList深复制和浅复制 。 ArrayList clone&#xff08;&#xff09;方法用于创建list的浅表副本 。 在新列表中&#xff0c;仅复制对…

南京邮电大学c语言实验报告4,南京邮电大学算法设计实验报告——动态规划法...

《南京邮电大学算法设计实验报告——动态规划法》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《南京邮电大学算法设计实验报告——动态规划法(12页珍藏版)》请在人人文库网上搜索。1、实 验 报 告(2009/2010学年 第一学期)课程名称算法分析与设计A实验名称动 态 规 …

启动jboss_3种启动JBoss BPM流程的基本方法

启动jboss这一集提示和技巧将帮助您了解根据需要启动流程实例的最佳方法。 规划项目可能包括流程项目&#xff0c;但是您是否考虑过可以启动流程的各种方式&#xff1f; 也许您的JBoss BPM Suite在您的体系结构中本地运行&#xff0c;也许您在云中运行&#xff0c;但是无论它…

单片机控制灯光亮度c语言程序,基于51单片机控制LED灯光亮度并报警

利用pwm控制led灯光亮度大小。可以显示许多亮度等级 到最低或者最高亮度等级会发出报警。设计思路&#xff1a;LED一般是恒流操作的&#xff0c;如何改变LED的亮度呢&#xff1f;答案就是PWM控制。在一定的频率的方波中&#xff0c;调整高电平和低电平的占空比&#xff0c;即可…

将Quarkus应用程序部署到AWS Elastic Beanstalk

Elastic Beanstalk允许在AWS云中部署和管理应用程序&#xff0c;而无需了解运行这些应用程序的基础架构。 使用Elastic Beanstalk&#xff0c;您可以运行可处理HTTP请求的网站&#xff0c;Web应用程序或Web API&#xff0c;但也可以运行辅助应用程序以运行长任务。 Elastic Be…

c语言中rand()%900,c语言 n=rand()%5是什么意思

rand()函数会产生范围为0至32767的随机数&#xff0c;% 让它与5求余&#xff0c;变成0至4的随机数&#xff0c;不过每次启动程序产生的随机数都相等&#xff0c;在用srand(unsigned int)输入种子数后产生的才不一样&#xff0c;一般都用的 srand((unsigned int)time(NULL)) 产生…

gradle 构建应用流程_使用Gradle构建和应用AST转换

gradle 构建应用流程最近&#xff0c;我想在Gradle项目中构建并应用本地ast转换。 虽然我可以找到几个有关如何编写转换的示例&#xff0c;但找不到完整的示例来显示完整的构建过程。 转换必须单独编译然后放在类路径中&#xff0c;因此其源代码不能简单地放在Groovy源代码树的…

c语言malloc calloc,C语言内存管理:malloc、calloc、free的实现

任何一个对C稍稍有了解的人都知道malloc、calloc、free。前面两个是用户态在堆上分配一段连续(虚拟地址)的内存空间&#xff0c;然后可以通过free释放&#xff0c;但是&#xff0c;同时也会有很多人对其背后的实现机制不了解。这篇文章则是通过介绍这三个函数&#xff0c;并简单…

在雅加达EE TCK中使用Arquillian的可能方法

最近&#xff0c;我们讨论了如何创建独立的Jakarta Batch测试套件&#xff08;TCK&#xff09;。 对于大多数提交者而言&#xff0c;使用Arquillian将测试从实现中如何执行抽象化是很自然的。 但是Romain提出了一个有趣的想法&#xff0c;即使用纯JUnit5引起了我的思考。 它并没…

c语言怎么循环输入单个字符,c语言 帮我检查一下 输入一段文字,每行用回车结束,文字输入完毕可以使用某个特殊字符作为结束,...

c语言 帮我检查一下 输入一段文字&#xff0c;每行用回车结束&#xff0c;文字输入完毕可以使用某个特殊字符作为结束&#xff0c;0HIDE152019.04.28浏览4次分享举报c语言输入一段文字&#xff0c;每行用回车结束&#xff0c;文字输入完毕可以使用某个特殊字符作为结束&#xf…