對於網絡編程中,一般都喜歡使用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結構對於操作多個數據塊是最優的解決辦法。