首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Windows 2000、Windows XP 和 Linux 中的管道 Linux 和 Windows 上的高性能编程技术

Windows 2000、Windows XP 和 Linux 中的管道 Linux 和 Windows 上的高性能编程技术

在开始之前,请注意我们对于市场上两种版本的 Windows 称呼将稍有区别。当不需要区分 Windows 2000 和 Windows XP 时将使用“Windows”。当需要区分时,使用“Windows 2000”或“Windows XP”。
管道管道是一种进程间的通信机制,Windows 和 Linux(以及 UNIX)上都使用的这种机制。 管道源自“贝尔实验室”开发的 UNIX ,并且所有的 UNIX 和 Linux 都继承了这项技术。 管道是通过通常的 IO 接口存取的字节流。 创建管道后,通过使用操作系统的任何读或写 IO 系统调用来读或者写它。在 UNIX 和 Linux 环境中,IO 调用是         read() 和         write() 。 在 Windows 环境中,API 是         ReadFile() 和         WriteFile() 。Windows 管道与 Linux 管道的区别在于 Windows 使用单一句柄(类似于 Linux 文件描述符)支持双向 IO。Linux 管道返回两个文件描述符来实现双向 IO。      
Windows 管道Windows 管道比 Linux 管道复杂得多。Windows 支持命名的和未命名的管道。未命名管道就是接口没有公布其名称的命名管道。Windows 支持管道上的异步 IO,因此单个线程不会阻塞在对管道的 IO 调用。使用异步 IO 这种功能需要不同的 IO 接口。Windows 管道有两种类型:字节型和消息型。字节型管道与 UNIX 管道类似并支持字节流。 在本文中,我不打算研究消息型管道,尽管对于一个完全的比较应该包含它们。Windows 管道用         CreateNamedPipe() API 来创建。一旦创建,         OpenFile() API 将被用来存取新创建的命名管道的另一端。 管道名位于平面的名称空间。例如,         \\.\pipe\anyname 是一个合法的 Windows 命名管道名称。在 C 或 C++ 里名称表示为:      
1
char *pipeAdult = "\\\\.\\pipe\\anyname";




只能指定        anyname部分。 要使用 Windows 管道,必须用一个 API 来创建它,用另一个 API 来打开它。下面的示例代码片段显示了这是如何实现的。      
创建一个 Windows 命名管道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   //
    // Create named pipe in Windows
    // nbytes -- block size from command line arguments.
    //
    int mult = 1;
    int x;
    x = mult*nbytes + 24;
    handleA = CreateNamedPipe(pipeAdult,
                PIPE_ACCESS_DUPLEX,
                PIPE_TYPE_BYTE,
                2,              // two connections
                x,              // input buffer size
                x,              // output buffer size
                INFINITE,       // timeout
                NULL);          // security
    if(handleA == INVALID_HANDLE_VALUE) {
        printf("CreateNamedPipe() FAILED: err=%d
", GetLastError());
        return 1;
    }
    handleB = CreateFile(pipeAdult,
                GENERIC_READ|GENERIC_WRITE,
                FILE_SHARE_READ|FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);
    if(handleB == INVALID_HANDLE_VALUE) {
        printf("CreateFile() FAILED: err=%d
", GetLastError());
        return 1;
    }




在上述代码片段的第一个可执行行中的数字 24 是用实验方法确定的。我发现 SDK 平台中没有提到它。 如果没有它,程序就无法工作。很明显,在每次向管道写的时候,管道设施需要一个 24 字节的头。
Linux 管道Linux 管道的创建和使用都要简单一些,唯一的原因是它需要更少的参数。实现与 Windows 相同的管道创建目标,Linux 和 UNIX 使用下面的代码片段:
创建 Linux 命名管道
1
2
3
4
5
6
   int fd1[2];
    if(pipe(fd1)) {
        printf("pipe() FAILED: errno=%d
",errno);
        return 1;
    }




Linux 管道对阻塞之前一次写操作的大小有限制。 专门为每个管道所使用的内核级缓冲区确切为 4096 字节。 除非阅读器清空管道,否则一次超过 4K 的写操作将被阻塞。 实际上这算不上什么限制,因为读和写操作是在不同的线程中实现的。
单线程进程管道的速度我写了一个程序(pipespeed2.cpp)来测试操作系统管道代码的执行速度。 该程序创建一个管道并读写数据,所有这些都在单一线程中完成。 因为 Linux 在写入器阻塞之前只支持 4K 的写操作,我们的测试以 4K 为块的上限。 生成数的 bash shell 脚本很简单:
生成测试结果的 Bash shell 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
list="1 2 3 4 6 8 10 12 14 16 20 24 28 32 36 40 44 48 52 56 60 64 72 80 88"
list="$list 96 104 112 120 128 144 160 176 192 208 224 240 256 288 320 352"
list="$list 384 416 448 480 512 576 640 704 768 832 896 960 1024 1280 1536"
list="$list 1792 2048 2560 3k 3584 4k"
uname -s -r
for bytes in $list
do
     case $bytes in
     ----|?k ) count=100k;;
     ----?|--k) count=100k;;
     ?|--|--? ) count=500k;;
     esac
     pipespeed2 $count $bytes
done




每次运行的输出被保存到一个文件,并由文本编辑器编辑产生一个可以轻松导入 Microsoft Excel 的文本文件。
编译程序本文中的程序编译如下:
  • Linux: gcc -O2 pipespeed2.cpp -o pipespeed2
  • Windows: cl -O2 pipespeed2.cpp

  • Linux: gcc -O2 pipespeed2t.cpp -lpthread -o pipespeed2t
  • Windows: cl -O2 pipespeed2t.cpp
图 1 显示了在随 Red Hat 7.1 交付的 Linux 2.4.2 内核上运行的结果。
图 2 显示了相同的程序编译后在 Windows 2000 Advanced Server 上运行的结果。多数非实质的服务被禁用。
图 3 显示了在新发布的 Windows XP 上运行的结果。
每个测试运行包括 3 个运行(3 个系列)。 正如图表中所看到的,Linux 管道比 Windows 命名管道快很多。Windows XP 命名管道比 Windows 2000 命名管道慢得多。测试中所用的 Windows XP Professional 版本是已发布的“评估版”。
多线程进程管道的速度Windows 2000 的这些图表示了一种迹象:如果块大小超过 4K,速度还可以进一步提高。为了测试块大于 4K 时的速度,我写了 pipespeed2.cpp 的增强版。 新程序创建了第二个线程。第一个线程写数据,第二个线程读数据。第一个程序的目的是为了理解在支持没有上下文切换开销的环境中与管道的代码路径关联的开销。这是一个人为的环境,也许永远都不会用到。
将 pipespeed2.cpp 的增强版命名为 pipespeed2t.cpp。两个线程将需要来回进行上下文切换以便传输所有数据。因此,第二个程序比第一个多了一项额外的开销 ― 上下文切换。只有在所有数据传输完毕且第二个线程正常终止之后,计时才结束。
图 4、5 和 6 分别显示了 Linux、Windows 2000 和 Windows XP。显然 Windows XP 在命名管道设施上性能大大降低。Linux 轻松地胜过了 Windows 2000,Windows 2000 又轻松地胜过了 Windows XP。
图 4 显示了 pipespeed2t.cpp 的多线程版本在随 Red Hat 7.1 交付的 Linux 2.4.2 内核上运行的结果。它的 IO 速率峰值大约是 700 MB/sec。
图 5 显示了多线程版本在 Windows 2000 上的运行结果。它的 IO 速率峰值大约是 500 MB/sec。
两幅图表显示了相同的形状,但是在块非常大的情况下,Linux 在 100 MB/sec 左右达到了稳定状态。Windows 2000 在同样情况下也达到了稳定状态,但速率只有 80 MB/sec。
图 6 显示了 Windows XP Professional(评估版)上各种线程运行的结果。它的 IO 速率峰值只有 120 MB/sec。
对于所有各种大小的块 Windows XP 都显示了另人失望的结果。也许 上 Microsoft 的某个专家会发表关于如何改进我们的程序以及提高 Windows XP 命名管道性能的有用意见。      
由这些简单程序轻松生成的信息,可以告知程序设计者、软件设计师甚至系统管理员关于操作系统的性能。 要避免关于“现实主义”方案无休止的争论,简单是必需的。“简单”应当代表人们写程序的方式。 在这个测试中我没有针对任何平台作特殊的性能优化。
Windows XP 的低性能是另人困惑的。一个可能的解释也许是因为有更好的数据传输方式 ― “套接字”的存在。 我将在以后的专栏里讨论套接字。
Linux 还支持命名管道。对这些数字的早期评论员建议我,为公平起见,应该比较 Linux 的命名管道和 Windows 的命名管道。我写了另一个在 Linux 上使用命名管道的程序。我发现对于 Linux 上命名的和未命名的管道,结果是没有区别。
另一个区别可能在于 Windows 的管道“特性”,它没有固定的缓冲区大小。对于第一个测试,我们把缓冲区大小限定在 4K 以适应 Linux 缓冲区。Windows 所鼓吹的也许就是推荐与 Windows 命名管道相关的缓冲区可以是任意大小的这一好处。要演示任意大小的 Windows 命名管道缓冲区,可以简单地运行一个使用任意块大小的大型单线程程序。我在 Windows 上运行了 pipespeed2.cpp,为它指定了 256 MB 的缓冲区大小。在         ReadFile() 被发出        之前,Windows 承受了增加缓冲区的大小以容纳 256 MB 数据的压力。系统慢得象爬行,我无心等到该操作完成。 这个“特性”是否有用已经很明显了。 然而,对内部缓冲区进行扩大和缩小需要页面分配和重新分配 ― 这些操作通常与简单字节流无关。
返回列表