| 
  • If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

31_Benchmarking_IO_system_call

Page history last edited by Makoto Inoue 13 years, 9 months ago

入出力系システムコールの基礎的な負荷を可視化

ID: 31

creation date: 2009/10/19 15:49

modification date: 2009/10/19 15:49

owner: mikio

 

Benchmarking IO system call 

 

openとかreadとかのシステムコールを呼ぶだけの簡単なお仕事に携わる皆様、こんにちは。

普段自分が使っているシステムコールって、他にオーバーヘッドがない理想的な状態だとどれくらいの負荷になるんだろう。そんな漠然とした疑問を持ちつつも、面倒なんで自分で測ることなく日々を過ごしている人も多いかもしれない。それじゃいけない。純粋にそのシステムコールを呼ぶとどれくらいの負荷がかかるのかを知っておかねばならない。

 

For who just doing repetitive work by calling "open" or "read" system calls every day, hello.

 

Have you ever wondered how much overhead system calls are actually generating when there are no other overhead? That's one of the things you think about it, but you tend to be too busy to actuary benchmark, but that's one of the fundamental things you should know.

 

ということで、簡単なテストプログラムを書いてみた。各システムコールの典型的な組み合わせでどれくらい時間がかかるかを見るのだ。「gcc -std=c99 -Wall -O2 -o mytest mytest.c」でビルドできる。結果は環境依存性が強いだろうから、ぜひ自分の環境で動かしてみてほしい。

 

Therefore, I wrote a simple test program to major who much overhead each system calls generates. You can actually build this programme with "gcc -std=c99 -Wall -O2 -o mytest mytest.c」" command. Since the result may vary depending on your environment, I encourage you to run it on your own environment.

 

 

#define _XOPEN_SOURCE 500

 

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/mman.h>

#include <fcntl.h>

#include <sys/time.h>

 

#define LOOPNUM 1000000

#define IOSIZE 8

 

double mytime(void){

  struct timeval tv;

  gettimeofday(&tv, NULL);

  return tv.tv_sec + (double)tv.tv_usec / (1000 * 1000);

}

 

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

 

  // open and close

  double stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);

    close(fd);

  }

  double etime = mytime() - stime;

  printf("open&close: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // open and write and close

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);

    write(fd, "hogehoge", IOSIZE);

    close(fd);

  }

  etime = mytime() - stime;

  printf("open&write&close: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // open and read and close

  char buf[256];

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);

    read(fd, buf, IOSIZE);

    close(fd);

  }

  etime = mytime() - stime;

  printf("open&read&close: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // lseek and write

  int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    lseek(fd, 0, SEEK_SET);

    write(fd, "hogehoge", IOSIZE);

  }

  etime = mytime() - stime;

  printf("lseek&write: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // lseek and read

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    lseek(fd, 0, SEEK_SET);

    read(fd, buf, IOSIZE);

  }

  etime = mytime() - stime;

  printf("lseek&read: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // pwrite

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    pwrite(fd, "hogehoge", IOSIZE, 0);

  }

  etime = mytime() - stime;

  printf("pwrite: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // pwrite

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    pread(fd, buf, IOSIZE, 0);

  }

  etime = mytime() - stime;

  printf("pread: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // mmap and output

  void *map = mmap(NULL, IOSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    memcpy(map, "hogehoge", IOSIZE);

  }

  etime = mytime() - stime;

  printf("mmap&output: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  // mmap and input

  stime = mytime();

  for(int i = 0; i < LOOPNUM; i++){

    memcpy(buf, map, IOSIZE);

  }

  etime = mytime() - stime;

  printf("mmap&input: total=%.8f  qps=%.0f\n", etime, LOOPNUM / etime);

 

  munmap(map, IOSIZE);

  close(fd);

 

  return 0;

}

俺の環境だと、以下のような結果になった。100万回の実行なので、total timeの秒数を1回のマイクロ秒数に読み替えられる。

 

Here is my result using my environment. This is the result of running it 1 million times, so you can replace this figure with micro second per one execution.

 

operations

total time (sec)

throughput (/sec)

open&close

2.47212291

404511

open&write&close

8.41249514

118871

open&read&close

2.87695003

347590

lseek&write

1.94846702

513224

lseek&read

0.72205496

1384936

pwrite

1.76872778

565378

pread

0.53991008

1852160

mmap&output

0.00088000

1136359794

mmap&input

0.00085902

1164114349

 

Before showing my analysis, you have to be aware of few conditions which are specific to this test case

This test case is reading specific part of one file repeatedly. In real use case, you may write multiple files and or you may read/write by seeking different parts of the file. Therefore you may not be able to expect the same throughput in real use case because certain cache or lock overhead are not taken consideration.

 

考察の前に、このテストケースでは、あくまで単一のファイルの特定の部分を読み書きするという特殊性があることに留意されたい。実際のユースケースでは、複数のファイルを読み書きするかもしれないし、おそらくファイル内の特定でない部位をシークして読み書きすることだろう。したがって、各種のレベルのキャッシュ機構やロック機構に起因する負荷が実際には加わることになるので、当然上記のスループットは実際のユースケースでは期待できないだろう。

 

Having said that, I was able to find some interesting observations.

 

その上で、興味深い点がいくつか考察できるので、箇条書きにしてみる。

open&close は意外に早い。両者でたった2.47usしかかからない。ただ、ここでは更新をしていないので、キャッシュのフラッシュ処理がないから早いだけだ。

その証拠に、open&write&close だと一気に遅くなる。おそらくフラッシュ処理のために、closeの時間はwriteの量に強く相関する。

open&write&close は lseek&write に比べると4倍くらい遅い。よって、ファイルディスクリプタは使いまわした方が当然よい。とはいえ、所詮4倍しか変わらず遅くとも11万QPSくらい出るため、要求スループットがそれほどタイトではないユースケースではファイルを毎回開いても実は問題ないとも言える。

open&read&close も lseek&read に比べてやはり4倍くらい遅い。よって、以下同文。

lseek&write に比べて pwrite はちょっと早い。よって、pwriteが使える場合には使うべきである。

lseek&read に比べて pread はちょっと早い。よって、preadが使える場合には使うべきである。

mmap の入出力は桁違いに早い。よって、可能であれば、ひたすらmmapを使うとよい。

 

- open& close operation takes only 2.47us which is a lot faster than I expected. We have to take into consideration the fact that it did not do any update, which does not require to flush any caches.

- The slowness of open&write&close supports the above assumption. It seems that how much data written affects how long it takes to flush the data and close the file. 

- open&write&close is 4 times slower than lseek&write. This means that it's better to reuse file descriptors. Having said that it's only 4 times difference and the former still have 11000QPS, so it could be enough for non performance critical operations.

- open&read&close is also 4 times slower than lseek&read.

-pwrite is slightly faster than lseek&write, so better to use pwrite if possible.

-pread is slightly faster than lseek&read, so better to use pwrite if possible.

-mmap is order of magnitude faster, so keep using mmap wherever possible.

 

総括として言いたいことは、以下の2点である。

カジュアルなユースケースでは、毎回ファイルを開いて閉じても別に構わない。

シリアスなユースケースでは、なるべくmmapを使った方がよい。

繰り返しになるが、この結果はあくまで傾向を見るためのものである。実際のユースケースではもっと複雑な要因が絡み合う。しかし、各システムコールが基本的にどのような負荷を伴うのかを単純なテストケースで確認しておくことは有用である。

 

Here is the summary

 

- You can just open/close files every time for casual use case.

- You should use mmap for serious use case.

 

As I am repeatedly saying, this is just a general use case, and the various other aspects will affect your performance in actual use cases. However, it is good to know the basic performance of each system calls.

 

 

Comments (0)

You don't have permission to comment on this page.