質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.95%

システムコールが遅い言われる所以について

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 5,310

score 7

前提

C言語でシステムコールが遅いと言われる所以について調べました。
write,readなどのシステムコールではバッファリングを行わないためカーネルのコンテキストスイッチが増えオーバーヘッドになっているということがわかりました(一部の要因なのかもしれません)。そこで自前でバッファを確保してそこに入れる様にしてやればオーバーヘッドを減らせるのでは?と思い実験プログラムを書いてみたのですが一部が予想と反した結果になりました。

環境
macOS Sierra
バージョン 10.12.1
xcodeインストール済み

システムコール回数を調べるために使ったコマンド
dtruss -c ./a.out

該当のソースコード

▼512バイトのバッファ有り

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <fcntl.h>

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

    ssize_t n;
    char buf[512];
    int fd;
    int df;

    if(argc != 3){
        fprintf(stderr,"usage:%s [srcfile] [dstfile]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    //読み                                                                                          
    if((fd = open(argv[1], O_RDONLY)) < 0) perror(argv[1]);

    //書き                                                                                          
    if((df = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) perror(argv[2]);

    size_t count; //ループの回数                                                                    

    /*データがなくなるまでループ*/
    while((n = read(fd, buf, sizeof(buf))) > 0){
        write(df, buf, n);
        count++;
    }

    printf("ループカウンタ:%zu\n", count);

    close(fd);
    close(df);
    return 0;
}

▼バッファなし

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <fcntl.h>

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

    ssize_t n;
    char c;
    int fd;
    int df;

    if(argc != 3){
        fprintf(stderr,"usage:%s [srcfile] [dstfile]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    //読み                                                                                          
    if((fd = open(argv[1], O_RDONLY)) < 0) perror(argv[1]);

    //書き                                                                                          
    if((df = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) perror(argv[2]);

    size_t count; //ループの回数                                                                    

    /*データがなくなるまでループ*/
    while((n = read(fd, &c, sizeof(c))) > 0){
        write(df, &c, n);
        count++;
    }

    printf("ループカウンタ:%zu\n", count);

    close(fd);
    close(df);
    return 0;
}

実験プログラムの結果

512kバイトの元データをプログラムに与えた場合

------512バイトのバッファ有り-------
・標準出力結果
ループカウンタ:1000

・システムコール回数
write    1000
read     1001

・実行速度
real    0m0.036s
user    0m0.003s
sys    0m0.018s

・コピー先ファイルのサイズ
512kバイト コピーできている

-----バッファなし(1バイトずつ処理)-----
・標準出力結果
ループカウンタ:512000

・システムコール回数
write     8075
read      8076

・実行速度
real    0m0.653s
user    0m0.094s
sys    0m0.552s

・コピー先のファイルサイズ
512kバイト コピーできている

疑問点

512kバイトのデータを512バイトのバッファに格納して処理する場合、システムコールを1000回呼んで1001回目にデータがもうないのでループを抜けるのはわかります。結果もそのようになったので納得です。

一方、バッファなしで1バイトずつ処理する場合は、readを512000回呼んで512001回目にデータがもうないのでループを抜けると思うし、ループカウンタも512000回になっているのですが、システムコールは8075回という値が示されてしまいます。自分はシステムコール回数の結果も512000回だと思うのですがなぜこの様な結果になってしまうのでしょうか?
コピー先のファイルサイズはちゃんと512kバイトあるのでプログラムとしてはちゃんと動いていそうなのですが...
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

dtruss (というかそれが呼び出しているdtrace) は、プローブ結果をメモリ上のバッファに保持し、一定のタイミングでまとめて出力します。結果が多すぎてバッファがあふれると、単にバッファの内容を捨てます。次のようなメッセージが出力されていれば、バッファがあふれたために出力できなかった情報があります。

dtrace: 12345 drops on CPU 0

主バッファのサイズは、dtruss -b サイズとすれば変えられます。サイズの初期値は4mです。

プローブ結果が多いと、バッファサイズを増やしただけでは追いつかないかもしれません (ご質問の例については、私の環境では1gまで増やしても記録落ちしました)。

dtrussの実体はシェルスクリプトで、内部でdtraceを起動して結果を出力しています。その際にdtraceの設定もしているので、これを書き換えればもっと細かいチューニングができます (おそらく、スクリプト中に「#pragma D ...」などと記述してある箇所がそうではないかと思います)。dtraceのチューニングパラメータについては次のような資料を参考にして下さい。

Solaris 動的トレースガイド:

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/13 11:16

    記録落ちしていたのですね!詳しい回答ありがとうございます!

    キャンセル

0

Macをもっておらずなぜかを知ってもいないのですが・・・

Cのwriteが実際にはシステムコールに展開されないという可能性はないのでしょうか。オブジェクトファイルを逆アセンブルするか、もしくはCコンパイラーの-Sオプション(?)でアセンブリーソースが出力できるのであればそれを観察してみるとwriteの呼び出しが直接システムコールとして展開されるか観察できる気がします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/10 07:28

    回答有難うございます。
    なるほどアセンブラソースの観察は怠っていました、観察をしてみたいと思います。

    キャンセル

0

回答では無いですが、Linuxで、strace -cで調べると下記になりました。

ループカウンタ:512000
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 55.36    2.558412           5    512001           write
 44.64    2.062902           4    512002           read
  0.00    0.000000           0         8         4 open
  0.00    0.000000           0         4           close
  0.00    0.000000           0         4         3 stat
  0.00    0.000000           0         3           fstat
  0.00    0.000000           0         9           mmap
  0.00    0.000000           0         3           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         1           brk
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    4.621314               1024039         8 total

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/10 22:50

    なるほどLinuxだとwrite()の呼び出しはそのままsystem callになるのですね・・・

    キャンセル

  • 2016/12/11 11:56

    なるほどlinuxだとなるんですね...有難うございます

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.95%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る