連載
» 2012年09月07日 00時00分 UPDATE

知ってトクするシステムコール(8):システムコールの特性を知る pipe(2)編 (1/2)

これまで数回にわたって紹介してきたmmap(2)に続き、今度はpipe(2)による処理の高速化について考察していく。やりとりするデータのサイズを工夫することで、うまく効率化を図ることができる(編集部)

[後藤大地,GLOBIS.JP]

システムコールの特性を知ろう

 UNIX系OSにおける基本機能にパイプがある。シェルでは|で表現され、コマンドの標準出力を次のコマンドの標準入力に結びつける処理をする。

 実際に使われるシステムコールはpipe(2)で、pipe(2)で生成されたファイルディスクリプタに対してwrite(2)およびread(2)でデータの読み書きを行う仕組みになっている。

 これまで数回にわたって、mmap(2)を使うことで処理の高速化が実現できることを示してきたが、read(2)/write(2)を使っても、やりとりするデータのサイズを工夫することで、処理速度の改善を図ることができる。ここではOSの提供するいくつかの機能を追いながら、どういった調整をすると処理速度を高速化できるかを紹介する。

パイプと読み書きのサイズ

 論より実物ということで、まず実験に使うソースコードを次に示す。これは10GBのデータをpipe(2)で接続した2つのプロセス間で流すというプログラムだ。

 read(2)/write(2)で指定するバッファのサイズを、1KB、4KB、16KB、64KB、128KB、256KB、512KB、1MB、2MB、4MB、8MB、16MB、32MB、64MB、128MB、256MBと変えながら、それぞれの場合の性能差を調べていく。下のソースコードは読み書きの単位としてページと同じ4KBを指定しているものだ。

#include <unistd.h>
#include <stdio.h>
#define DATASIZE        (1024l*1024l*1024l*10l)
#define BUFSIZE         (1024l*4l)
int
main(void)
{
        long i, j = DATASIZE / BUFSIZE;
        char buf[BUFSIZE];
        int fd[2], t;
        pipe(fd);
        if (0 == fork()) {
                for (i = 0; i < j; i++) {
                        t = 0;
                        while (t < BUFSIZE)
                                t += read(fd[0], buf, BUFSIZE);
                }
        }
        else {
                for (i = 0; i < BUFSIZE; i++)
                        buf[i] = 'a';
                for (i = 0; i < j; i++)
                        write(fd[1], buf, BUFSIZE);
        }
}

 実験に使用するほかのソースコードはBUFSIZEのサイズを変更するだけなので、変更部分だけを次に掲載しておく。

#define BUFSIZE         (1024l)
#define BUFSIZE         (1024l*4l)
#define BUFSIZE         (1024l*16l)
#define BUFSIZE         (1024l*64l)
#define BUFSIZE         (1024l*128l)
#define BUFSIZE         (1024l*256l)
#define BUFSIZE         (1024l*512l)
#define BUFSIZE         (1024l*1024l)
#define BUFSIZE         (1024l*1024l*2l)
#define BUFSIZE         (1024l*1024l*4l)
#define BUFSIZE         (1024l*1024l*8l)
#define BUFSIZE         (1024l*1024l*16l)
#define BUFSIZE         (1024l*1024l*32l)
#define BUFSIZE         (1024l*1024l*64l)
#define BUFSIZE         (1024l*1024l*128l)
#define BUFSIZE         (1024l*1024l*256l)

 ベンチマークは、例えば次のようなスクリプトを作成して実行すればよい。

#!/bin/sh
for i in \
        readwrite_001Kb.c \
        readwrite_004Kb.c \
        readwrite_016Kb.c \
        readwrite_064Kb.c \
        readwrite_128Kb.c \
        readwrite_256Kb.c \
        readwrite_512Kb.c \
        readwrite_001Mb.c \
        readwrite_002Mb.c \
        readwrite_004Mb.c \
        readwrite_008Mb.c \
        readwrite_016Mb.c \
        readwrite_032Mb.c \
        readwrite_064Mb.c \
        readwrite_128Mb.c \
        readwrite_256Mb.c
do
        clang $i
        echo $i
        /usr/bin/time -lph ./a.out
done

pipe(2)の実装:FreeBSD 10-CURRENTの場合

 この実験結果はOSの種類やバージョンごとに異なる可能性がある。カーネルでの実装に依存しているからだ。

 例えばFreeBSD 10-CURRENTでは、パイプで使われるページ可能なカーネルメモリを使ったパイプのバッファサイズは、4KBから64KBの間で可変になっている。最初は16KBが割り当てられ、状況に応じて64KBまで増加する。逆に割り当てるバッファを小さくしなければならない状況ではページのサイズと同じ4KBまで下がる仕組みになっている。

 実験のために用意した読み書きのサイズが1KB、4KB、16KB、64KB、128KB、256KB、512KB、1MB、2MB、4MB、8MB、16MB、32MB、64MB、128MB、256MBとなっているのは、こうした内部の仕組みを知った上で、動作にどのような違いが生じるかを調べたいためだ。例えば

  • 4KBはページと同じサイズ
  • 64KBはパイプバッファの最大数
  • 1KBはページよりサイズが小さい場合の特性
  • 128KB以上は読み書きのサイズをパイプバッファの上限サイズよりも増やした場合

にどのような性能の劣化が見られるかを調べる目的がある。

 パイプで使われるバッファのサイズが可変になっているのは、ページ可能なカーネルメモリを節約するためだ。バッファサイズを引き上げた方が処理性能の向上は期待できるが、引き上げた分だけカーネルメモリを使い切る可能性が高くなる。汎用的にさまざまな処理ができ、かつ、速度も期待できるというところを勘案して、4KB〜64KBの間での可変長というところに落とし込まれている。

       1|2 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。