管道与消息队列简单性能测试

Unix/Linux中进程间通信有多种方式,比较典型的有管道,命名管道(FIFO),消息队列,信号量和共享存储区。后面三种也叫POSIX XSI IPC。《Unix环境高级编程》中,进程间通信一章对“消息队列”,“STREAMS管道”和“UNIX域套接字”三种IPC的执行时间在Solaris9上进行了比较。考虑到测试时间距离现在已经比较久远,所以本文按照《APUE》中的思路在Linux2.6上重新进行一次测试。

测试环境

OS:2.6.35-29-generic #51-Ubuntu SMP CPU:Pentium(R) Dual-Core E5800 @ 3.20GHz Glibc:Version 2.12.1

测试方法

方法来源于《APUE》:测试程序先创建IPC通道,调用fork,然后从父进程向子进程发送约100MB数据。对于消息队列,调用10000此msgsnd,每个消息长度为2000字节。对于管道,调用100000次write,每次写2000字节。书中没有给出程序实现,自己写了一个简单的实现。上述方法稍有不同的是,我的实现中引入了磁盘IO(读取和保存文件),但对每一种IPC,磁盘IO时间近似相等,所以对实验结果不会有较大的影响。程序如下,源文件下载

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFSIZE 2000

static void err_exit(const char *msg)
{
    fprintf(stderr, "%s:%s
", msg, strerror(errno));
    exit(-1);
}

void benchmark_pipe(const char *src, const char *dst)
{
    int pipefd[2], ret;
    char buf[2000];
    pid_t pid;
    int fdin, fdout;

    if (pipe(pipefd) < 0) {
        err_exit("pipe");
    }

    if ((pid = fork()) < 0) {
        err_exit("fork");
    } else if (pid > 0) {
        close(pipefd[0]);

        if ((fdin = open(src, O_RDONLY)) < 0) {
            err_exit("parent open");
        }
        while ((ret = read(fdin, buf, BUFSIZE)) >= 0) {
            if (ret == 0) {
                break;
            }

            if (write(pipefd[1], buf, ret) != ret) {
                err_exit("paretn write");
            }
        }

        if (ret < 0) {
            err_exit("parent read");
        }
        close(pipefd[1]);
        close(fdin);
    } else {
        close(pipefd[1]);

        if ((fdout = open(dst, O_WRONLY | O_CREAT | O_TRUNC)) < 0) {
            err_exit("child open");
        }

        while ((ret = read(pipefd[0], buf, BUFSIZE)) >= 0) {
            if (ret == 0) {
                break;
            }

            if (write(fdout, buf, ret) != ret) {
                err_exit("child write");
            }
        }

        if (ret < 0) {
            err_exit("child read");
        }
        close(pipefd[0]);
        close(fdout);
    }
}

struct mymsg {
    long mtype;
    char buf[BUFSIZE];
};


void benchmark_msg(const char *src, const char *dst)
{
    pid_t pid;
    int msgid;
    struct mymsg msg;
    int fdin, fdout, ret;
    struct msqid_ds msqds;

    key_t mskey = 0x12345675;
    if ((msgid = msgget(mskey, IPC_CREAT | IPC_EXCL| 0666)) < 0) {
        err_exit("msgget");
    }

    if ((pid = fork()) < 0) {
        err_exit("fork");
    } else if (pid > 0) {
        if ((fdin = open(src, O_RDONLY)) < 0) {
            err_exit("parent open");
        }

        while ((ret = read(fdin, msg.buf, BUFSIZE)) >= 0) {
            if (ret == 0) {
                break;
            }
            msg.mtype = 0x12345;

            if (msgsnd(msgid, &msg;, ret, 0) < 0) {
                err_exit("msgsnd");
            }
        }

        if (ret < 0) {
            err_exit("parent msgsnd");
        }
        close(fdin);
        /*
        * delete msg queue
        */
        msgctl(msgid, IPC_RMID, &msqds;);

    } else {
        if ((msgid = msgget(mskey, 0)) < 0) {
            err_exit("child msgget");
        }

        if ((fdout = open(dst, O_WRONLY | O_CREAT | O_TRUNC)) < 0) {
            err_exit("child open");
        }

        while ((ret = msgrcv(msgid, &msg;, BUFSIZE, 0, 0)) >= 0) {
            if (write(fdout, msg.buf, ret) != ret) {
                err_exit("child write");
            }
        }

        if (ret < 0) {
            if (errno != EIDRM)
                err_exit("child read");
        }
        close(fdout);
    }
}

int main(int argc, char **argv)
{
    if (argc != 4) {
        fprintf(stderr, "usage: ipc_benchmark [pipe|msg]  source destination
");
        exit(1);
    }

    if (strcasecmp("pipe", argv[1]) == 0) {
        benchmark_pipe(argv[2], argv[3]);
    } else {
        benchmark_msg(argv[2], argv[3]);
    }

    return 0;
}

测试结果

ID Real User Sys

消息队列#1 0m0.472s 0m0.028s 0m0.224s

消息队列#2 0m0.499s 0m0.012s 0m0.236s

消息队列#3 0m0.429s 0m0.020s 0m0.232s

管道#1 0m0.443s 0m0.020s 0m0.228s

管道#2 0m0.424s 0m0.032s 0m0.200s

管道#3 0m0.446s 0m0.016s 0m0.220s

结论

虽然距离书中测试的时间已经很长了,但是结论还是不变。正如作者说的,虽然当初消息队列的引入目的是提供一个比一般IPC更快的进程间通信方法,但是这种优势已经不复存在了,而且考虑到消息队列使用方式复杂以及其他一些问题,所以在新的应用程序中不推荐再使用他们了。

Comments

sagi: 多进程没啥意思,多线程才是王道啊

刺猬: 楼上太绝对了 如果能用进程解决问题 就没有必要上多线程

Published: April 25 2011

blog comments powered by Disqus