对于网络编程中,一般都喜欢使用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结构对于操作多个数据块是最优的解决办法。