Optimizing Memcpy improves speed

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

The memcpy() routine in every C library moves blocks of memory of arbitrary size. It's used quite a bit in some programs and so is a natural target for optimization. Cross-compiler vendors generally include a precompiled set of standard class libraries, including a basic implementation of memcpy(). Unfortunately, since this same code must run on hardware with a variety of processors and memory architectures, it can't be optimized for any specific architecture. An intimate knowledge of your target hardware and memory-transfer needs can help you write a much more efficient implementation of memcpy().

This article will show you how to find the best algorithm for optimizing the memcpy() library routine on your hardware. I'll discuss three popular algorithms for moving data within memory and some factors that should help you choose the best algorithm for your needs. Although I used an Intel XScale 80200 processor and evaluation board for this study, the results are general and can be applied to any hardware.

A variety of hardware and software factors might affect your decision about a memcpy() algorithm. These include the speed of your processor, the width of your memory bus, the availability and features of a data cache, and the size and alignment of the memory transfers your application will make. I'll show you how each of these factors affects the performance of the three algorithms. But let's first discuss the algorithms themselves.

Three basic memcpy() algorithms
The simplest memory-transfer algorithm just reads one byte at a time and writes that byte before reading the next. We'll call this algorithm byte-by-byte. Listing 1 shows the C code for this algorithm. As you can see, it has the advantage of implementation simplicity. Byte-by-byte, however, may not offer optimal performance, particularly if your memory bus is wider than 8 bits.

Listing 1: The byte-by-byte algorithm

void * memcpy(void * dst, void const * src, size_t len)
{
    char * pDst = (char *) dst;
    char const * pSrc = (char const *) src;
An algorithm that offers better performance on wider memory buses, such as the one on the evaluation board I used, can be found in GNU's newlib source code. I've posted the code here. If the source and destination pointers are both aligned on 4-byte boundaries, my modified-GNU algorithm copies 32 bits at a time rather than 8 bits. Listing 2 shows an implementation of this algorithm.

    while (len--)
    {
        *pDst++ = *pSrc++;
    }

    return (dst);
}

 

Listing 2: The modified-GNU algorithm

void * memcpy(void * dst, void const * src, size_t len)
{
    long * plDst = (long *) dst;
    long const * plSrc = (long const *) src;
A variation of the modified-GNU algorithm uses computation to adjust for address misalignment. I'll call this algorithm the optimized algorithm. The optimized algorithm attempts to access memory efficiently, using 4-byte or larger reads-writes. It operates on the data internally to get the right bytes into the appropriate places. Figure 1 shows a typical step in this algorithm: memory is fetched on naturally aligned boundaries from the source of the block, the appropriate bytes are combined, then written out to the destination's natural alignment.

    if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))
    {
        while (len >= 4)
    {
            *plDst++ = *plSrc++;
            len -= 4;
        }
    }

    char * pcDst = (char *) plDst;
    char const * pcDst = (char const *) plSrc;

    while (len--)
    {
        *pcDst++ = *pcSrc++;
    }

    return (dst);
}

 


Figure 1: The optimized algorithm

Note that the optimized algorithm uses some XScale assembly language. You can download this algorithm here. The preload instruction is a hint to the ARM processor that data at a specified address may be needed soon. Processor-specific opcodes like these can help wring every bit of performance out of a critical routine. Knowing your target machine is a virtue when optimizing memcpy().

Having looked at all three of the algorithms in some detail, we can begin to compare their performance under various conditions.

Block size
What effect does data size have on the performance of our algorithms? To keep things simple, let's assume that there's no data cache (or that it's been disabled) and that all of the source and destination addresses are aligned on 4-byte boundaries.


Figure 2: Algorithm comparison for small data blocks (20 bytes)

As you can see in Figure 2, byte-by-byte does the best when the blocks are small, in this case 20 bytes. Byte-by-byte's main advantage is that it lacks overhead. It just moves bytes, without looking at addresses. The actual copy loop only runs for a small number of iterations (20 in this case), and then the routine is complete.

Note also that the performance of byte-by-byte improves dramatically as the processor clock speed increases. This implies that the routine is CPU-bound. Until we saturate the memory bus with reads and writes, the byte-by-byte algorithm will continue to execute more quickly.

For larger blocks, the situation is different. For example, simply increasing the size of the data blocks to 128 bytes makes byte-by-byte the clear loser at all processor speeds, as shown in Figure 3.


Figure 3: Algorithm comparison for large data blocks (128 bytes)

Here the modified-GNU algorithm has the best performance, as it makes more efficient use of the memory bus. The optimized algorithm has comparable performance, but the effects of its additional computation take their toll in overhead.

Data alignment
What happens if the source and destination are not both aligned on a 4-byte boundary? Figure 4 shows the results on a 128-byte block with unaligned addresses. It's obvious here that byte-by-byte is not affected by the misalignment of data blocks. It just moves a single byte of memory at a time and doesn't really care about addresses.


Figure 4: Algorithm comparison for unaligned data blocks (128 bytes)

The modified-GNU algorithm performs worse than byte-by-byte in this situation, largely because it defaults to byte-by-byte (after a fixed overhead).

The big overhead in the GNU algorithm comes from register save-restore in its prologue-epilogue. The algorithm saves off four registers, where the byte-by-byte routine saves none. So, as memory speed decreases in relation to processor speed, the GNU algorithm suffers accordingly. By the way, "optimized" memcpy saves off nine registers, which is part of the reason it becomes less compelling at high core and bus speeds. This overhead matters less when the stack is cached (probably the normal case).

The optimized algorithm handles unaligned addresses the best outperforming byte-by-byte. At slower clock speeds, the overhead of dealing with alignment is amortized by the cost of actually moving the memory four times as quickly. As CPU performance improves (with respect to the memory system), the elegance of an algorithm like optimized becomes less helpful.

Caching
Everything changes if your processor has a data cache. Let's try the same memcpy tests we've already run, but with the data already in cache. Figures 5 and 6 show the results. The memory is no longer the bottleneck, so algorithm efficiency becomes the limiting factor. If your data is likely to reside mainly in the cache, use something other than byte-by-byte.


Figure 5: Data cache effect on memcpy throughput (333MHz)


Figure 6: Data cache effect on memcpy throughput (733MHz)

Note that the bar charts in Figures 5 and 6 look about the same, although the y-axis scales differ. This y-axis difference supports the contention that we're limited by processing speed, not by memory. Also, the data for unaligned memcpy shows that the GNU memcpy performance degrades to that of byte-by-byte performance when addresses are not aligned. You may see severe degradation in memcpy performance if your data is not always aligned in memory.

Write policy
A write-through cache is one that updates both the cache and the memory behind it whenever the processor writes. This sort of cache tries to satisfy reads without going to memory.

A write-back cache, on the other hand, tries to satisfy both reads and writes without going to memory. Only when the cache needs storage will it evict some of its data to memory; this is called variously a write back, a cast out, or an eviction. Write-back caches tend to use less memory bandwidth than write-through caches.

The processor I used allows the cache to be configured using either policy. What effect does this have on memcpy? It depends. Figure 7 shows what happens when the cache is cold (no data in it). Figure 8 shows what happens if the cache contains only garbage data (data from other addresses).


Figure 7: Cache-policy effect (cold cache, 128 bytes, 333MHz)


Figure 8: Cache-policy effect (garbage cache, 128 bytes, 333MHz)

With a cold cache, optimized memcpy with write-back cache works best because the cache doesn't have to write to memory and so avoids any delays on the bus.

For a garbage-filled cache, write-through caches work slightly better, because the cache doesn't need to spend extra cycles evicting irrelevant data to memory. As usual, the more you know about your system"such as the likelihood of having certain data in the cache"the better you can judge the efficacy of one cache policy over another.


Figure 9: Performance of 4KB memcpy

Special situations
If you know all about the data you're copying as well as the environment in which memcpy runs, you may be able to create a specialized version that runs very fast. Figure 9 shows the performance gain we accrue by writing a memcpy that handles only 4KB-aligned pages when the cache is in write-through mode. This example shows that writing a very specific algorithm may double the speed of a memcpy-rich program. I've posted one of these algorithms here.

Optimize away
Some applications spend significant processor time transferring data within memory; by choosing the optimal algorithm, you could improve overall program performance significantly. The moral of the story: know your target hardware and the characteristics of your application. Armed with this knowledge, you can easily find the optimal algorithm.

Android Targeting R+ requires the resources.arsc of installed APKs to be stored uncompressed and al 一、问题描述: 最近Apk适配了Android 11版本,在使用完乐固线上加固后,通过adb命令安装测试,安装失败,报出如下错误: Failure [-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte bou 阅读详情

相关推荐

C2668 'memcpy': ambiguous call to overloaded function

在原有的项目中新加入了一个模块,需要借助OpenCV库打开摄像头,于是重新写了一个h/cpp文件组合,作为一个单独的模块。结果莫名其妙的,编译出错了—— // open_camera.h #pragma once #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namesp...

大山喵写博客的地方 1679

android 11安装apk 报错installed APKs to be stored uncompressed and aligned on a 4-byte boundary

合作开发的三方apk, 用我们的platform keystore签名后,无法在android 11的设备上安装成功,一直提示安装错误 Failure [-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte bou

jeephao的博客 1万+

攻克TypeError: Cannot read properties of undefined (reading ‘NormalModule‘)的四种实战策略

本文详细解析了`TypeError: Cannot read properties of undefined (reading 'NormalModule')`错误的四种实战解决策略。从版本一致性检查、包管理器优化到依赖替换和构建环境配置,帮助开发者快速定位和解决Webpack构建过程中的常见问题,提升开发效率。

weixin_42523670的博客 225

thinking of memcpy()

Optimizing Memcpy improves speedBy Michael Morrow, Courtesy of Embedded Systems Design ? 29 2004 (17:00 H)URL: http://www.embedded.com/showArticle.jhtml?articleID=19205567  Knowing a few details abo

Hands-off 811

android killer 回编译apk的问题记录

修改 apktools.yml的版本为30 ,如果不改 可能出现的错误提示为。

qq_36535153的博客 798

ARM伪操作ALIGN

ALIGN The ALIGN directive aligns the current location to a specified boundary by padding with zeros or NOP instructions. Syntax ALIGN {expr{,offset{,pad{,padsize}}}} where: exp

sunshineyy85的专栏 1619

性能优化之:Optimizing Memcpy improves speed

Optimizing Memcpy improves speed <!-- function launcher(art_id) { uri = "/shared/article/emailBox.jhtml?articleID=" + art_id; window.open(uri,"","toolbar=no,scrollbars=auto,l

2450

安卓R安装apk 报错Targeting R+ (version 30 and above) requires the...aligned on a 4-byte boundary

Failed parse during ins tallPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and ali gned on a 4-byte boundary

brave_1999的博客 3342

memcpy()函数

表头文件: #include 定义函数: void *memcpy(void *dest, const void *src, size_t n) 函数说明: memcpy()用来拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束'\0'而结束。在下面程序中,strcpy只复制hi,因为/0结束

飘过的小牛 1225

龙芯版 memcpy 的实现

当前版本: 0.1完成日期: 2007-6-15作者: Dajie Tan memcpy 是为最常用之函数,多媒体编解码过程中调用频繁,属调用密集型函数,对其性能优化很有意义。1. 概述memcpy 所做的操作是把内存中的一块数据复制到内存的另一个地方,也就是内存到内存的数据拷贝,这个过程需要CPU的参与,即:先从内存取数据到CPU的寄存器,然后再从寄存器写到内存中。可以用类似如下C 代码实现:c

四度空间 2122

Newlib 与 Newlib-Nano区别

newlib与newlib-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...

易得者易失 7265

Newlib编译

对于嵌入式开发者,newlib并不陌生,Newlib是一个面向嵌入式系统的C运行库。最初是由Cygnus Solutions收集组装的一个源代码集合,取名为newlib,现在由Red Hat维护。 newlib官网:https://sourceware.org/newlib/ git 下载:git clone git://sourceware.org/git/...

weixin_33766168的博客 926

Linux环境下使用memcpy函数遇到段错误问题

Linux 环境下编写以下程序,会出现段错误:#include "stdio.h" #include &lt;stdlib.h&gt; #include "string.h" void main() { char Data[] = "qwertyuiop"; char *data ; memcpy (data,Data,sizeof(Data) ); print...

m0_37682734的博客 8658

[armv9]-ARM最新架构为memcpy/memset底层的实现提供新的指令

CPY CPYM CPYP CPYE SET SETP SETM SETE,memcpy,memset 思考 1、memcpy/memset的底层是如何实现的?一个一个字节的操作吗? 可不可以四个四个字节操作呢? 2、若干年后再来回答问题1,是不是有新的方法了呢? 在大多数的[操作]系统中,memcpy()、memset()等函数的实现,其实都一个字节一个字节的处理。翻译成汇编后无非就算循环执行ldr、str指令 memcpy的底层实现: _PTR _DEFUN(memcpy, (dst0, src0

baron-周贺贺-代码改变世界ctw 2567

Clang中的属性

  介绍 功能属性 #pragma omp declare simd #pragma omp声明目标 _Noreturn abi_tag(gnu :: abi_tag) acquire_capability(acquire_shared_capability,clang :: acquire_capability,clang :: acquire_shared_capabil...

fishmai的专栏 5591

Object-c __attribute__((overloadable))) 用法

http://nshipster.com/__attribute__/ A recurring theme of this publication has been the importance of a healthy relationship with the compiler. Like any craft, one’s effectiveness as a practitioner is

jeffasd的专栏 1992

[转]oracle8i中4G以上内存的使用方法_se7en3_新浪博客

oracle中4G以上内存的使用方法,使用至强CPU,内存为8GB,系统为windows2003,数据库为oracle8i所以一直在查找如何在32位系统中使oracle使用超过4G内存的问题,baidu了n篇文章,发现在细节上写的都不够详细,又到微软和oracle网站上查找,终于对原理和方法有了一个大致了解,在此写出来供大家参考,如有错误还请指正。 1、由于32位系统内存寻址只能到...

stoco的专栏 184

善用GDB 调试一些函数栈被毁坏的问题

最近差一些问题,这些问题的现象一开始难以解释,函数的参数地址在函数内部被传递给另外的函数,然后发现地址发生了改变,这样的情况称之为函数的栈被毁坏,导致无法重入。 然后被调用的函数里面,访问了非法的地址导致了segment fault,产生core dump文件。问题比较棘手 查了一些文件,准备从gdb的栈保护设置开始着手。 1) 编译的时候添加编译选项 -fstack-pr

ontheline的专栏 2万+

使用sprintf 的常见问题

一个程序debug无错,而release有错。最终定位于某sprintf函数缓冲区溢出,在网上找了一些相关内容。使用sprintf 的常见问题sprintf 是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf 误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。1,缓冲区溢出

hi 1万+
上一篇: orca 创建select file browse 选择文件自定对话框
下一篇: oracle存储过程的调试
Splendour
博客等级 码龄20年 8粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值