避免不必要的內存拷貝和清0

對於網絡編程中,一般都喜歡使用memset清0和memcpy拷貝操作,舉個例子:

char buffer[1024];
memset(buffer, 0, 1024);
memcpy(buffer, proxy_hdr, IPC_HEADER_SZ);
memcpy(buffer, trans_hdr, TRANMIT_HEADER_SZ);
memcpy(buffer, buf, len);
send(fd, buffer, IPC_HEADER_SZ+TRANMIT_HEADER_SZ+len, 0);

要寫出高性能的後臺服務程序,必須避免不必要的內存操作:
1. 對於這裏的memset,完全沒有必要,就算buffer裏面有髒數據,我們在拷貝的過程中也把髒數據都覆蓋了,然後調用send的時候也給了實際數據的長度。
2. 對於這裏的memcpy,使用了3次拷貝構造一個完整的包然後發送,我們可以使用sendmsg接口進行優化:

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

這裏看下msghdr的結構

struct msghdr 
{
    void         *msg_name;       /* optional address */
    socklen_t     msg_namelen;    /* size of address */
    struct iovec *msg_iov;        /* scatter/gather array */
    size_t        msg_iovlen;     /* # elements in msg_iov */
    void         *msg_control;    /* ancillary data, see below */
    size_t        msg_controllen; /* ancillary data buffer len */
    int           msg_flags;      /* flags (unused) */
};

重點就在msg_iov了,我們可以直接把3塊內存的地址給到iov並告訴它每塊內存的長度

struct iovec iov[3] = 
{
    {.iov_base=(void*)proxy_hdr, .iov_len= IPC_HEADER_SZ},
    {.iov_base=(void*)&trans_hdr, .iov_len= TRANMIT_HEADER_SZ},
    {.iov_base=(void*)buf, .iov_len=len }
};

最後直接調用sendmsg就完成了,這樣3次memcpy也是完全沒必要了。這裏是使用的系統提供的優化函數接口,這種類似接口還有sendfile,writev,readv等等。
這裏我們再說一下writev

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

舉個例子,現在有3塊數據10字節,20字節,30字節要寫入文件,我們有幾種方案:
1. 調用3次write寫入文件,消耗是3次系統調用,0次內存拷貝
2. 調用3次memcpy把數據拷貝到一個塊,然後調用1次write寫入文件,消耗是1次系統調用,3次內存拷貝
3. 調用1次writev寫入文件,消耗是1次系統調用,0次內存拷貝
不管是減少系統調用還是減少內存拷貝,我們可以發現iov結構對於操作多個數據塊是最優的解決辦法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章