SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ファイルディスクリプタについて

ファイルディスクリプタについて(7)
~シグナル駆動I/Oの紹介

第7回

性能

 シグナル駆動I/Oは、ディスクリプタのI/Oをハンドラで行い、主処理では別のことを行いたい時に使用されるのを想定しているので、性能値の比較結果がそのまま優劣になるわけではありませんが、ある種の指標にはなると思います。

 サーバとクライアントで情報のやり取りが行われた場合の処理時間を計測します。クライアントはスレッドで行い、プロトコルはUDPなのでコネクションの確立を行う必要がないためシリアライズする必要もないので、スレッド内でソケットの作成を行います。

 ソケット作成後、クライアント(=スレッド)から1バイトのデータを決められた処理回数回データを送受信し、開始から終了までの時間を計測します。コネクション数、繰り返し送受信数はプログラム引数で指定します。処理終了後にクライアント内の処理時間を計測し、画面に出力します。

 SIGIO用プログラムは100行を超えていますが、下記の通りとなります。

sigio_performance.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <assert.h>
#define TV2SEC(tv) ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0))
int    make_udp_sock( in_port_t * port );
void   regist_signal( int signo, void ( *func )( int sig ));
void   sig_io( int signo );
void * client_threaed( void * arg );
int    loop;
int    sock;

int main( int argc, char ** argv ) {

    if( argc < 3 ) {
        printf( "%s <fork> <loop>\n", argv[0] );
        return( 1 );
    }
    int conn = atoi( argv[1] );
    loop = atoi( argv[2] );

    in_port_t port;
    sock = make_udp_sock( &port );
    regist_signal( SIGIO, sig_io );

    int i;
    pthread_t pid[conn];
    for( i = 0; i < conn; i ++ ) {
        pthread_attr_t attr;
        pthread_attr_init( &attr );
        pthread_attr_setstacksize( &attr, PTHREAD_STACK_MIN );
        pthread_create( &pid[i], &attr, &client_threaed, &port );
    }
    double *  result[conn];
    for( i = 0; i < conn; i ++ ) {
        pthread_join( pid[i], ( void ** )&result[i] );
        printf( "%4d\t%f\n", i + 1, *result[i] );
    }
    close( sock );
    return 0;
}

int make_udp_sock( in_port_t * port ) {

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons( 0 );
    addr.sin_addr.s_addr = INADDR_ANY;
    int sock = socket( AF_INET, SOCK_DGRAM, 0 );
    bind( sock, ( struct sockaddr *)&addr, sizeof( addr ));

    fcntl( sock, F_SETOWN, getpid( ));
    fcntl( sock, F_SETFL,  O_ASYNC | O_NONBLOCK );

    socklen_t len = sizeof( addr );
    getsockname( sock, ( struct sockaddr *)&addr, &len );
    *port = addr.sin_port;
    return sock;
}

void regist_signal( int signo, void ( *func )( int sig )) {

    sigset_t block;

    sigemptyset( &block );
    sigaddset( &block, signo );

    struct sigaction sa = {
        .sa_handler = func,
        .sa_flags   = SA_RESTART,
        .sa_mask    = block,
    };
    sigaction( signo, &sa, 0 );
}

void sig_io( int signo ) {
    ( void )signo;

    char c;
    struct sockaddr_in addr;
    socklen_t len = sizeof( addr );
    int rtn;

    while( 1 ) {
        rtn = recvfrom( sock, &c, 1, 0, ( struct sockaddr * )&addr, &len );
        if( rtn < 0 && errno == EAGAIN ) {
            break;
        }
        rtn = sendto( sock, &c, 1, 0, ( struct sockaddr * )&addr, sizeof( addr ));
        assert( rtn > 0 );
    }
}

void * client_threaed( void * arg ) {
    in_port_t port = *( in_port_t * )arg;
    struct timeval tv1, tv2;
    char c = 0;
    int  i, rtn;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons( ntohs( port ));
    addr.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
    int fd = socket( AF_INET, SOCK_DGRAM, 0 );

    gettimeofday( &tv1, 0 );
    for( i = 0; i < loop; i ++ ) {
        rtn = sendto( fd, &c, 1, 0, ( struct sockaddr *)&addr, sizeof( struct sockaddr_in ));
        assert( rtn > 0 );
        socklen_t len = sizeof( struct sockaddr_in );
        rtn = recvfrom( fd, &c, 1, 0, ( struct sockaddr * )&addr, &len );
        assert( rtn > 0 );
    }
    close( fd );
    gettimeofday( &tv2, 0 );
    double * time_rtn = malloc( sizeof( double ));
    *time_rtn = TV2SEC( tv2 ) - TV2SEC( tv1 );
    return ( void * )time_rtn;
}

 スレッドからデータの送受信を繰り返すので、スレッドの切替などの時間が加わるために実際の処理時間とは異なる可能性が高いですが、傾向くらいはつかめると思います。

 比較用のデータは、poll(2)epoll(7)でほぼ同様の動作をするプログラムを動作させて取得しました(poll_udp.c/epoll_udp.c)。

表1のデータ取得方法
# ./poll_udp 200 1000
# ./epoll_udp 200 1000
# ./sigio_performance 200 1000

 検証数値は、コネクション数を200、処理回数を1,000回としました。その時の傾向を見ます。結果は下記の通りです。

図1
表1

 上記性能結果より、SIGIOの高速性が分かると思います。また、性能測定をしていて気付きましたが、SIGIOはUDPを使用した通信に強いようです。

 UDPプロトコルは送受信データの結果を保証していません。UDPを使用した場合パケット落ちがあっても何ら不思議なことではないのです。

 事実、上記のデータ取得方法にて、コネクション数や処理回数を増やすと、あっという間にサスペンドします。これは今回作成したプログラムにパケット落ち時の考慮がなされておらず、送信したパケットをただ待っているので、1個でも届かなければプログラムがサスペンドしてしまっているからです。

 しかし、SIGIOの性能測定用プログラムでは、かなり大きなパラメータでも、上記性能値とそん色ない数値で動作したことを確認しました。

 UDPプロトコルを使用する際のパケット落ち対策としてシグナル駆動I/Oはかなり使えるかもしれません。

まとめ

 個人的には、シグナル駆動I/Oには相応のメリットを感じています。

 UDPの特徴である高速性を損なわず、(若干かもしれませんが)信頼性を高める手段であると思います。

 実際、時刻同期用デーモンであるNTPDは、シグナル駆動I/Oを利用し、ハンドラ内でディスクリプタを通して情報を受信し、メインプロセスへキューなどを使用して情報を蓄え、メインプロセスではキューから情報を取り出し、加工して送信しています。

 サーバの時刻との差分を極力少なく、かつ高速に送受信しなくてはいけないNPTDならではの実装といえるでしょう。

 ただし、シグナル駆動I/Oは使える局面が限られる上、シグナルハンドラ特有の問題も以前残るため、使用する際は綿密な設計やアルゴリズムが求められるでしょう。また、今回は試していませんが、情報の順序性についても検証が必要でしょう。

 次回は、非同期I/Oについて説明する予定です。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
ファイルディスクリプタについて連載記事一覧

もっと読む

この記事の著者

赤松 エイト(エイト)

(株)DTSに勤てます。WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。mixiもやってまして、こちらは子育てとか日々の日記メインです。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4846 2010/03/12 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー