程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 大並發服務器內存轉換的靈活運用,memcpy的思考

大並發服務器內存轉換的靈活運用,memcpy的思考

編輯:C++入門知識

在很多的網絡開發中,經常會碰到一些內存轉換,如下面的場景:


[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 <stdio.h>  
#include <stdlib.h>  
#include <memory.h>  
 
int main() 

        int s; 
        char buffer[4]; 
 
        memcpy(&s, buffer, 4 ); 
        s = *(int*)(buffer); 
        return 0; 

#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行采用的是強制轉換,為了方便比較,我們看一下匯編代碼:


[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* 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; 

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] 
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; 

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匯編代碼:


[cpp] 
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 

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匯編代碼:


[cpp] 
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 

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,下面的例子:


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

long lv;
char buffer[ 8 ];

memcpy( &lv, buffer, 8 );
lv = *(int*)(buffer);
這樣就能更好的利用CPU的多字節指令提高性能。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved