避免不必要的内存拷贝和清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结构对于操作多个数据块是最优的解决办法。

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