大并发服务器内存转换的灵活运用,memcpy的思考

来自:http://blog.csdn.net/xiaofei_hah0000/article/details/8959167

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

  1. #define PACKAGE_PARSE_ERROR -1   
  2. #define PACKAGE_PARSE_OK 0   
  3.   
  4. int parse_package( int* a, int* b, int* c, int* d, char* buf, int buf_len )  
  5. {  
  6.         if( !buf || buf_len < 16 ){  
  7.                 return PACKAGE_PARSE_ERROR;  
  8.         }  
  9.         memcpy( a, buf, 4 );  
  10.         memcpy( b, buf + 4, 4 );  
  11.         memcpy( c, buf + 8, 4 );  
  12.         memcpy( d, buf + 12, 4 );  
  13.   
  14.         return PACKAGE_PARSE_OK;  
  15. }  
#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;
}



 

这是网络解包的过程中的一个调用,封包的过程则是逆过程。

像这样的应用其实完全可以用整型强制转换来代替,而且效率会至少提高一倍。

为了说明问题,我们举个简单的例子:

  1. #include <stdio.h>   
  2. #include <stdlib.h>   
  3. #include <memory.h>   
  4.   
  5. int main()  
  6. {  
  7.         int s;  
  8.         char buffer[4];  
  9.   
  10.         memcpy(&s, buffer, 4 );  
  11.         s = *(int*)(buffer);  
  12.         return 0;  
  13. }  
#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;
}


第10行和第11行的效果是一样的,10行采用的是内存复制,11行采用的是强制转换,为了方便比较,我们看一下汇编代码:

  1. pushq   %rbp  
  2. .cfi_def_cfa_offset 16  
  3. .cfi_offset 6, -16  
  4. movq    %rsp, %rbp  
  5. .cfi_def_cfa_register 6  
  6. subq    $16, %rsp  
  7. leaq    -16(%rbp), %rcx  
  8. leaq    -4(%rbp), %rax  
  9. movl    $4, %edx  
  10. movq    %rcx, %rsi  
  11. movq    %rax, %rdi  
  12. call    memcpy  
  13. leaq    -16(%rbp), %rax  
  14. movl    (%rax), %eax  
  15. movl    %eax, -4(%rbp)  
  16. movl    $0, %eax  
  17. 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函数实现:

  1. void *memcpy (void *dstpp, const void *srcpp, size_t len )  
  2. {  
  3.   unsigned long int dstp = (long int) dstpp;  
  4.   unsigned long int srcp = (long int) srcpp;  
  5.   
  6.   if (len >= OP_T_THRES)  
  7.     {  
  8.       len -= (-dstp) % OPSIZ;  
  9.       BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);  
  10.       PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);  
  11.       WORD_COPY_FWD (dstp, srcp, len, len);  
  12.     }  
  13.   
  14.   BYTE_COPY_FWD (dstp, srcp, len);  
  15.   
  16.   return dstpp;  
  17. }  
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指令上可以说多了平空多了一倍。

从上面的分析可以看出,强制转换是节省了很大的运算时间,效率上至少提高一倍。不要小看这样的提升,在每秒几万并发的情况下,尤其每个并发都存在解包和封包的过程,这样的处理可以给我们带来相当大的性能提升。

开头中提到的解包过程,我们可以巧秒地运用强制转换,下面列出两种方法:

  1. int parse_package( int* a, int* b, int* c, int* d, char* buf, int buf_len )  
  2. {  
  3.         if( !buf || buf_len < 16 ){  
  4.                 return PACKAGE_PARSE_ERROR;  
  5.         }  
  6.         memcpy( a, buf, 4 );  
  7.         memcpy( b, buf + 4, 4 );  
  8.         memcpy( c, buf + 8, 4 );  
  9.         memcpy( d, buf + 12, 4 );  
  10.   
  11.         return PACKAGE_PARSE_OK;  
  12. }  
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;
}


 

  1. int parse_package2( int* a, int* b, int* c, int* d, char* buf, int buf_len )  
  2. {  
  3.         int* ibuf;  
  4.         if( !buf || buf_len < 16 ){  
  5.                 return PACKAGE_PARSE_ERROR;  
  6.         }  
  7.   
  8.         ibuf = buf;  
  9.         *a = ibuf[0];  
  10.         *b = ibuf[1];  
  11.         *c = ibuf[2];  
  12.         *d = ibuf[3];  
  13.   
  14.         return PACKAGE_PARSE_OK;  
  15. }  
int parse_package2( int* a, int* b, int* c, int* d, char* buf, int buf_len )
{
        int* ibuf;
        if( !buf || buf_len < 16 ){
                return PACKAGE_PARSE_ERROR;
        }

        ibuf = buf;
        *a = ibuf[0];
        *b = ibuf[1];
        *c = ibuf[2];
        *d = ibuf[3];

        return PACKAGE_PARSE_OK;
}


parse_package汇编代码:

  1. parse_package:  
  2. .LFB0:  
  3.         .cfi_startproc  
  4.         pushq   %rbp  
  5.         .cfi_def_cfa_offset 16  
  6.         .cfi_offset 6, -16  
  7.         movq    %rsp, %rbp  
  8.         .cfi_def_cfa_register 6  
  9.         subq    $48, %rsp  
  10.         movq    %rdi, -8(%rbp)  
  11.         movq    %rsi, -16(%rbp)  
  12.         movq    %rdx, -24(%rbp)  
  13.         movq    %rcx, -32(%rbp)  
  14.         movq    %r8, -40(%rbp)  
  15.         movl    %r9d, -44(%rbp)  
  16.         cmpq    $0, -40(%rbp)  
  17.         je      .L2  
  18.         cmpl    $15, -44(%rbp)  
  19.         jg      .L3  
  20. .L2:  
  21.         movl    $-1, %eax  
  22.         jmp     .L4.  
  23. L3:  
  24.         movq    -40(%rbp), %rcx  
  25.         movq    -8(%rbp), %rax  
  26.         movl    $4, %edx  
  27.         movq    %rcx, %rsi  
  28.         movq    %rax, %rdi  
  29.         call    memcpy  
  30.         movq    -40(%rbp), %rax  
  31.         leaq    4(%rax), %rcx  
  32.         movq    -16(%rbp), %rax  
  33.         movl    $4, %edx  
  34.         movq    %rcx, %rsi  
  35.         movq    %rax, %rdi  
  36.         call    memcpy  
  37.         movq    -40(%rbp), %rax  
  38.         leaq    8(%rax), %rcx  
  39.         movq    -24(%rbp), %rax  
  40.         movl    $4, %edx  
  41.         movq    %rcx, %rsi  
  42.         movq    %rax, %rdi  
  43.         call    memcpy  
  44.         movq    -40(%rbp), %rax  
  45.         leaq    12(%rax), %rcx  
  46.         movq    -32(%rbp), %rax  
  47.         movl    $4, %edx  
  48.         movq    %rcx, %rsi  
  49.         movq    %rax, %rdi  
  50.         call    memcpy  
  51.         movl    $0, %eax  
parse_package:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $48, %rsp
        movq    %rdi, -8(%rbp)
        movq    %rsi, -16(%rbp)
        movq    %rdx, -24(%rbp)
        movq    %rcx, -32(%rbp)
        movq    %r8, -40(%rbp)
        movl    %r9d, -44(%rbp)
        cmpq    $0, -40(%rbp)
        je      .L2
        cmpl    $15, -44(%rbp)
        jg      .L3
.L2:
        movl    $-1, %eax
        jmp     .L4.
L3:
        movq    -40(%rbp), %rcx
        movq    -8(%rbp), %rax
        movl    $4, %edx
        movq    %rcx, %rsi
        movq    %rax, %rdi
        call    memcpy
        movq    -40(%rbp), %rax
        leaq    4(%rax), %rcx
        movq    -16(%rbp), %rax
        movl    $4, %edx
        movq    %rcx, %rsi
        movq    %rax, %rdi
        call    memcpy
        movq    -40(%rbp), %rax
        leaq    8(%rax), %rcx
        movq    -24(%rbp), %rax
        movl    $4, %edx
        movq    %rcx, %rsi
        movq    %rax, %rdi
        call    memcpy
        movq    -40(%rbp), %rax
        leaq    12(%rax), %rcx
        movq    -32(%rbp), %rax
        movl    $4, %edx
        movq    %rcx, %rsi
        movq    %rax, %rdi
        call    memcpy
        movl    $0, %eax


L3段是我们的主段落,对a的赋值:

24-28行都是在“压栈”,为了memcpy函数内取出来,加上29行一共是6条,memcpy 解栈指令数>=3, 去处指令数>=4,不加算返回指令,一共指令数>6+3+4=13。


parse_package2汇编代码:

  1. parse_package2:  
  2. .LFB1:  
  3.         .cfi_startproc  
  4.         pushq   %rbp  
  5.         .cfi_def_cfa_offset 16  
  6.         .cfi_offset 6, -16  
  7.         movq    %rsp, %rbp  
  8.         .cfi_def_cfa_register 6  
  9.         movq    %rdi, -24(%rbp)  
  10.         movq    %rsi, -32(%rbp)  
  11.         movq    %rdx, -40(%rbp)  
  12.         movq    %rcx, -48(%rbp)  
  13.         movq    %r8, -56(%rbp)  
  14.         movl    %r9d, -60(%rbp)  
  15.         cmpq    $0, -56(%rbp)  
  16.         je      .L7       
  17.         cmpl    $15, -60(%rbp)  
  18.         jg      .L8       
  19. .L7:  
  20.         movl    $-1, %eax  
  21.         jmp     .L9       
  22.   
  23. .L8:  
  24.         movq    -56(%rbp), %rax  
  25.         movq    %rax, -8(%rbp)  
  26.         movq    -8(%rbp), %rax  
  27.         movl    (%rax), %edx  
  28.         movq    -24(%rbp), %rax  
  29.         movl    %edx, (%rax)  
  30.         movq    -8(%rbp), %rax  
  31.         addq    $4, %rax  
  32.         movl    (%rax), %edx  
  33.         movq    -32(%rbp), %rax  
  34.         movl    %edx, (%rax)  
  35.         movq    -8(%rbp), %rax  
  36.         addq    $8, %rax  
  37.         movl    (%rax), %edx  
  38.         movq    -40(%rbp), %rax  
  39.         movl    %edx, (%rax)  
  40.         movq    -8(%rbp), %rax  
  41.         addq    $12, %rax  
  42.         movl    (%rax), %edx  
  43.         movq    -48(%rbp), %rax  
  44.         movl    %edx, (%rax)  
  45.         movl    $0, %eax  
parse_package2:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movq    %rdi, -24(%rbp)
        movq    %rsi, -32(%rbp)
        movq    %rdx, -40(%rbp)
        movq    %rcx, -48(%rbp)
        movq    %r8, -56(%rbp)
        movl    %r9d, -60(%rbp)
        cmpq    $0, -56(%rbp)
        je      .L7     
        cmpl    $15, -60(%rbp)
        jg      .L8     
.L7:
        movl    $-1, %eax
        jmp     .L9     

.L8:
        movq    -56(%rbp), %rax
        movq    %rax, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    (%rax), %edx
        movq    -24(%rbp), %rax
        movl    %edx, (%rax)
        movq    -8(%rbp), %rax
        addq    $4, %rax
        movl    (%rax), %edx
        movq    -32(%rbp), %rax
        movl    %edx, (%rax)
        movq    -8(%rbp), %rax
        addq    $8, %rax
        movl    (%rax), %edx
        movq    -40(%rbp), %rax
        movl    %edx, (%rax)
        movq    -8(%rbp), %rax
        addq    $12, %rax
        movl    (%rax), %edx
        movq    -48(%rbp), %rax
        movl    %edx, (%rax)
        movl    $0, %eax


L8是主段落,对a的赋值:

26-29行,一共4行解决。

这个例子中强制转换(parse_package2) 比内存复制(parse_package)要少2倍的CPU指令,性能至少可以提高2倍。

因此,我们的开发中应该尽量减少对内存复制的使用,而应该采用强制转换,现在64位服务器上,我们甚至可以用8个字节的long,就像下面这样:

  1. long lv;  
  2. char buffer[ 8 ];  
  3.   
  4. memcpy( &lv, buffer, 8 );  
  5. lv = *(long*)(buffer);  
long lv;
char buffer[ 8 ];

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


这样就能更好的利用CPU的多字节指令提高性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值