memcpy的优化方案

前不久在腾讯面试的时候,被问到这点。面试官看我博客有一篇介绍标准库函数memcpy,然后问我怎么对标准库函数优化。当时现场没有啥思路,后来面试官让我从汇编角度想想,然后就有了思路。

 

标准库是逐个字节复制,代码如下。

 
void *memmove (void *dest, const void *src, size_t len)
{
  char *d = dest;
  const char *s = src;
  if (d < s)
    while (len--)
      *d++ = *s++;
  else
    {
      char *lasts = s + (len-1);
      char *lastd = d + (len-1);
      while (len--)
        *lastd-- = *lasts--;
    }
  return dest;
}

 标准库代码每次复制一个字节,效率确实有点低。CPU内部寄存器可能是8个字节长度。这表示我们每次最多可以复制8个,理论上提升8倍性能,具体要根据寄存器长度来看。当时给出的方案使用8字节复制,不足8字节则逐个字节复制。代码如下,考虑了地址重叠。

//优化版本
void *myMemmove2(void *dst, const void *src, size_t len)
{
    //8字节长度
	double *srcdb, *dstdb;
	char *srcch, *dstch;
	size_t times, left;

	times = len / 8;
	left = len % 8;
	
	if (!src || !dst)	return dst;

	//从前往后复制
	if (dst < src)
	{
		srcdb = (double *)src;
		dstdb = (double *)dst;
		
		while (times--)
		{
			*dstdb++ = *srcdb++;
			len-=8;
		}
		
		srcch = (char *)srcdb;
		dstch = (char *)dstdb;

		while (left--)
		{
			*dstch++ = *srcch++;
		}
	}
	//从后往前复制
	else if (dst > src)
	{
		if (times > 0)
		{
			dstdb = (double *)(dst+len-8);
			srcdb = (double *)(src+len-8);	
		}
		
		while(times--)
		{
			*dstdb = *srcdb;
			dstdb--;srcdb--;
		}
		
		if (len > 8 && left)
		{
			srcch = (char *)(srcdb+1)-1;
			dstch = (char *)(dstdb+1)-1;
			while(left--)
			{
				*dstch-- = *srcch--;
			}
		}
		else
		{
      		srcch = (char *)src + (len-1);
      		dstch = (char *)dst + (len-1);	
      		while (len--)
	 	       *dstch-- = *srcch--;
		}
	}
	
	return dst;
}

上述代码是优化的一种,其实还有优化的空间。比如,地址对齐问题。

如果地址不对齐,CPU可能需要取两次。因此,最好在进行复制之前进行字节对齐。有些方案就是先进行字节对齐再处理的。面试的时候,面试官提示了字节对齐问题,当时我的想法是,即便地址没对齐,CPU取两次值,然后进行裁剪拼接。也能够运行。不过,对于后面一点,即地址拼接,我不确定。字节对齐问题仅考虑了结构体所占大小这种。参考资料3里给出了内存对齐的相关资料,资料1给出了地址对齐的一种实现。

使用8字节对齐的方案,没有考虑地址对齐问题(编译器默认会分配对齐地址),进行了如下测试,分别使用未优化版本和优化版本对内存进行复制,两个函数各执行10000次,使用gprof进行性能统计,结果如下:

从上图可以看到,未优化版本每次调用耗费2.11ms,优化版本每次耗费0.28ms。因此,优化显然还是有效果。

参考资料:

1. 更快的memcpy (https://www.cnblogs.com/Leo_wl/p/3163057.html)

2. C/C++性能测试工具 GNU gprof (C/C++性能测试工具 GNU gprof - 书亚博园)

3. 内存对齐分配策略(含位域模式) (https://www.cnblogs.com/chaozhu/p/5600496.html)

### 关于 lwIP 中 `memcpy` 函数的使用和相关问题 在嵌入式系统中,`memcpy` 是一个常用的函数,用于在内存块之间复制数据。在 lwIP(Lightweight IP)网络协议栈中,`memcpy` 通常被用来处理数据包的传输、接收和存储等操作[^2]。 lwIP 的实现中,`memcpy` 可能出现在多个模块中,例如网络接口层(`netif`)、TCP/IP 协议栈的核心部分以及应用层协议(如 HTTP、FTP 等)。以下是一些常见的使用场景: 1. **数据包复制** 在 lwIP 中,当数据包从一个缓冲区移动到另一个缓冲区时,会用到 `memcpy`。例如,在网络接口驱动程序中,接收到的数据包可能需要从硬件缓冲区复制到 lwIP 的 PBUF 结构中[^3]。 ```c memcpy(pbuf->payload, hardware_buffer, data_length); ``` 2. **TCP 数据传输** 在 TCP 数据传输过程中,发送方可能会将用户数据复制到发送缓冲区中,而接收方则会将接收到的数据复制到应用层缓冲区中[^4]。 ```c memcpy(tcp_seg->p->payload, user_data, length); ``` 3. **DNS 查询和响应** 在 DNS 模块中,`memcpy` 可能用于复制域名或 IP 地址信息到查询或响应数据包中[^5]。 ```c memcpy(dns_msg->question.name, domain_name, name_length); ``` 需要注意的是,`memcpy` 的性能对嵌入式系统非常重要。某些情况下,可以优化 `memcpy` 的实现以提高效率,例如通过使用硬件加速指令或调整缓存策略[^6]。 #### 常见问题及解决方案 1. **数据对齐问题** 如果目标地址和源地址未正确对齐,可能导致性能下降甚至错误。确保在调用 `memcpy` 时,数据结构符合硬件平台的要求[^7]。 2. **缓冲区溢出** 使用 `memcpy` 时必须确保目标缓冲区有足够的空间容纳要复制的数据,否则会导致缓冲区溢出问题[^8]。 3. **中断上下文中的使用** 在实时操作系统(RTOS)环境中,如果在中断服务例程(ISR)中使用 `memcpy`,需注意中断优先级和任务切换的影响[^9]。 ```c // 示例:确保缓冲区大小足够 if (target_size >= source_size) { memcpy(target_buffer, source_buffer, source_size); } else { // 处理错误 } ``` ### 示例代码 以下是一个简单的 lwIP 数据包复制示例: ```c struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, data_len, PBUF_RAM); if (p != NULL && p->len == data_len) { memcpy(p->payload, data_buffer, data_len); err_t err = netif->input(p, netif); if (err != ERR_OK) { pbuf_free(p); } } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值