内存拷贝和对齐读取

来源:互联网 发布:windows服务器监控 编辑:程序博客网 时间:2024/06/06 18:18

一 memcpy的思考

在很多的网络开发中,经常会碰到一些内存转换,如下面的场景:

#define PACKAGE_PARSE_ERROR -1  #define PACKAGE_PARSE_OK 0  int parse_package( int* a, int* b, int* c, int* d, char* buf, int buf_len )  {          if( !buf || buf_len < 16 ){                  return PACKAGE_PARSE_ERROR;          }          memcpy( a, buf, 4 );          memcpy( b, buf + 4, 4 );          memcpy( c, buf + 8, 4 );          memcpy( d, buf + 12, 4 );          return PACKAGE_PARSE_OK;  }  

这是网络解包的过程中的一个调用,封包的过程则是逆过程。
像这样的应用其实完全可以用整型强制转换来代替,而且效率会至少提高一倍。
为了说明问题,我们举个简单的例子:

#include <stdio.h>  #include <stdlib.h>  #include <memory.h>  int main()  {          int s;          char buffer[4];          memcpy(&s, buffer, 4 );          s = *(int*)(buffer);          return 0;  }  

这个例子中强制转换比内存复制要少2倍的CPU指令,性能至少可以提高2倍。
因此,我们的开发中应该尽量减少对内存复制的使用,而应该采用强制转换,现在64位服务器上,我们甚至可以用8个字节的long,就像下面这样:

long lv;  char buffer[ 8 ];  memcpy( &lv, buffer, 8 );  lv = *(long*)(buffer);  

但是在服务器中涉及到内存的对齐问题,如果你的cpu不支持不对齐内存访问的话请在网络解析中使用memcpy。
如果你的支持不对齐内存访问,建议在构建内存池的时候加入内存对齐的管理,提高效率

一对内存对齐的深一步理解

内存对齐只是指数据存储在内存时的起始地址是否是某个值的整数倍。如果只是放在内存中,是否对齐本身并没有什么问题。问题是读取、写入的时候。访问一个不对齐的数据(unaligned memory access)可能会导致程序运行效率慢,结果出错,甚至是程序当掉。那这些情况是怎么出现的呢?

我们都知道,程序最终都是以CPU指令来运行的。参考:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html,我们知道ARM CPU有下面几条指令:

LDRB/STRB          - address must be byte alignedLDRH/STRH          - address must be 2-byte aligned LDR/STR            - address must be 4-byte aligned

也就是说,当我们从内存中存取数据时,要调用上面的指令。而这些指令在设计时,较老的CPU由于考虑了硬件、效率等等问题,要求访问的内存必须是对齐的。现在假如我声明了一个内存缓冲区char *buffer[1024],系统给它分配的地址是0x00001000,可以看到,这个地址都是符合1、2、4字节对齐的。接着我从网络接收了一段数据,放到这个缓冲区里。现在要从缓冲区里依次取出char、int两个类型的数据:

char ch = *buffer;int i = *reinterpret_cast<int *>(buffer+1);

运行ch = *buffer时,由于char类型的大小是1字节,CPU将调用LDRB指令,这时将检测buffer是否按1byte对齐。这里当然是对齐的,所以指令运行正常。
运行i = *reinterpret_cast

memcpy( &ch,buffer,1 );memcpy( &i,buffer+1,4 );

你可能会问,使用memcpy,buffer+1的地址也是不对齐的,为什么就安全了呢?就像我上面所说的,数据在内存中存放时,是否对齐并不重要,重要的是你怎样去访问它。memcpy的实现本身并不简单(你在源码里看到的通过while每次拷贝一个char的只是一个例子,并不是真实的memcpy),它考虑了是否对齐。当检测到内存是对齐时,memcpy调用合适的指令(比较这里拷贝一个int,就调用LDR),一次拷贝多个字节,以提高效率。当检测到不对齐时,先调用LDRB遂个字节拷贝,直到对齐部分后再调用合适的指令拷贝。因此,在上面的例子中,它是先调用LDRB的,因为LDRB是按1byte对齐(所有的内存都按这个对齐),所以不会触发报错。但效率就要慢一点了,毕竟要拷贝几次。

当我们把变量强制按1byte对齐时,编译器不会在结构体中加入任何内容来使得这个结构体符合内存对齐,而是产生一些额外的指令来让他满足当前平台的内存对齐,当然,效率还是受影响的。

原创粉丝点击