在很多的网络开发中,经常会碰到一些内存转换,如下面的场景:
[cpp]
#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;
}
#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;
}
这是网络解包的过程中的一个调用,封包的过程则是逆过程。
像这样的应用其实完全可以用整型强制转换来代替,而且效率会至少提高一倍。
为了说明问题,我们举个简单的例子:
[cpp]
#include
#include
#include
int main()
{
int s;
char buffer[4];
memcpy(&s, buffer, 4 );
s = *(int*)(buffer);
return 0;
}
#include
#include
#include
int main()
{
int s;
char buffer[4];
memcpy(&s, buffer, 4 );
s = *(int*)(buffer);
return 0;
}
第10行和第11行的效果是一样的,10行采用的是内存复制,11行采用的是强制转换,为了方便比较,我们看一下汇编代码:
[cpp]
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
leaq -16(%rbp), %rcx
leaq -4(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
leaq -16(%rbp), %rax
movl (%rax), %eax
movl %eax, -4(%rbp)
movl $0, %eax
leave
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
leaq -16(%rbp), %rcx
leaq -4(%rbp), %rax
movl $4, %edx
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
leaq -16(%rbp), %rax
movl (%rax), %eax
movl %eax, -4(%rbp)
movl $0, %eax
leave
代码中可以看出,内存复制方法占用了7-12行,共6行,强制转换占用了13-15行,共3行,指令上少了一半。
深究一下其实还不止,因为第12行其实是一个函数调用,必然会有栈的迁移,所以强制转换的效率比内存复制起码高一倍。
再看看glibc 的memcpy函数实现:
[cpp]
void *memcpy (void *dstpp, const void *srcpp, size_t len )
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
if (len >= OP_T_THRES)
{
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
WORD_COPY_FWD (dstp, srcp, len, len);
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
void *memcpy (void *dstpp, const void *srcpp, size_t len )
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
if (len >= OP_T_THRES)
{
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
WORD_COPY_FWD (dstp, srcp, len, len);
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
9-11行分别是三种处理方法,取决于 len 与 OP_T_THRES的比较,一般 OP_T_THRES 是8或16,对于len 小于OP_T_THRES的内存复制,glibc采用的是字节方式转换,即遍历每个字节,第个字节都要经过 “内存--寄存器--内存” 这个过程,CPU指令上可以说多了平空多了一倍。
从上面的分析可以看出,强制转换是节省了很大的运算时间,效率上至少提高一倍。不要小看这样的提升,在每秒几万并发的情况下,尤其每个并发都存在解包和封包的过程,这样的处理可以给我们带来相当大的性能提升。
开头中提到的解包过程,我们可以巧秒地运用强制转换,下面列出两种方法:
[cpp]
int parse_package( int* a, int* b, int* c, int*