2008年06月09日 15:45 [Edit]

unix - atimeはいつ更新される?

以下に対して、

革命の日々! ITProのLinuxチューニングの記事がひどい事になっている件について
あまりに酷いのでdisる記事を書こうかと思ったら、末尾に小さく
出典:日経Linux 2002年4月号 45ページより (記事は執筆時の情報に基づいており,現在では異なる場合があります)
と書いてあった。6年前の記事かよ!!
古い内容が多いので、よい子は信用しないでね。

と物言いがついていて、さらに

ITProのチューニング記事(noatime付加)を検証してみた - 科学と非科学の迷宮
また、はてブのコメントを元に relatime オプションを付加して検証を行ったところ、こちらも性能向上は見られませんでした。

となっているのだけど、ちょっと待った!


ITProのチューニング記事(noatime付加)を検証してみた - 科学と非科学の迷宮
  1. 電源投入してからGUIログイン画面が表示されるまでの時間を測定する。
  2. 「# time find /usr/ -name linux」を実行し、findの実行時間を測定する。

1.はとにかく、2.では検証になりません。

基本的な「atimeはいつ更新されるのか」が誤解されているためです。

atimeは、データを読み出した時に更新される → メタデータを読んでも更新されない

atimeの更新をひと言でまとめると、上の通りになります。

実際にその様子を見てみましょう。

以下のperlスクリプトを動かしてみます。

#!/usr/local/bin/perl
use strict;
use warnings;

my $filename = shift || 'test.txt';

my $line = join '', ( '0' .. '9', 'A' .. 'Z', 'a' .. 'z', '_', "\n" );

sub check_timestamp {
    my ( $atime, $mtime, $ctime ) = ( stat(shift) )[ 8 .. 10 ];
    printf "atime=%d, mtime=%d, ctime=%d\n", $atime, $mtime, $ctime;
    1;
}

{
    print "# Create\n";
    unlink $filename;
    open my $fh, '>', $filename or die "$filename:$!";
    print $fh $line for ( 0 .. 63 );
    close $fh;
    check_timestamp($filename) and sleep(2);
}
{
    print "# Just open\n";
    open my $fh, '<', $filename or die "$filename:$!";
    close $fh;
    check_timestamp($filename) and sleep(2);
}
{
    print "# Open and Read\n";
    open my $fh, '<', $filename or die "$filename:$!";
    my $cont = do { local $/; <$fh> };
    close $fh;
    check_timestamp($filename);
}

こんな感じになります。

# Create
atime=1212988860, mtime=1212988860, ctime=1212988860
# Just open
atime=1212988860, mtime=1212988860, ctime=1212988860
# Open and Read
atime=1212988864, mtime=1212988860, ctime=1212988860

三回目で、やっとatimeが更新されていることに注目してください。atimeはファイルをstat()しただけは更新せず、open()してもまだ更新されず、read()してやっと更新されることがおわかりいただけるかと思います。

よって、

革命の日々! relatimeがどこで実装されているのか調べてみた
atimeが更新されるのは、だいたい
  1. readしたとき
  2. readdirしたとき
  3. readlinkしたとき(openの副作用による暗黙のリンクオープンも含む)
  4. mmapしたとき
の4ケースで、writeは最初から入ってない。

ここまでは正しいのですが、

革命の日々! relatimeがどこで実装されているのか調べてみた
  1. findやメーラーなど、山ほどファイルを開きまくるケース

は前の下りが正しくない。なぜなら、findはメタデータにはアクセスしても、データにはアクセスしないからです。考えてみれば当然で、findの実行そのものがメタデータを改変してしまってはfindの意味がなくなってしまうからです。findのオプションそのものに-atimeがあるのに、findがそれを更新してしまっては元も子もなくなってしまいます。

もちろん現代的なfindは、単にファイルを「見つける」だけではなく、-exec-deleteを通してファイルシステムを改変する機能も持っていますが、その基本機能は「メタデータの検索」であって「データの検索/更新」ではありません。

厳密には、findもディレクトリの内容は読むので(ただしreadddir経由とは限らない; 例えば4.4BSDのfindではftsという仕組みを使っている)、この時にディレクトリのatimeが更新する可能性がありますが、ファイルのatimeを更新することはありえません。たいていの状況において、ディレクトリ数≪ファイル数なので、findではあまり適切なベンチマークが取れないというわけです。

で、noatimeって効用あるの?

私は FreeBSD User なので、Linuxの事情に関して確定的なことは言えませんが、

革命の日々! relatimeがどこで実装されているのか調べてみた
こいつがなぜパフォーマンスを落とすかというとatime更新 = inode情報の更新 = ディスク書き込みの必要が発生という連鎖を引き起こすからです。
さらに悪いことに、inode情報はたいていデータ本体とはディスクの物理的な位置が離れているので、無駄なシークが誘発されます。
たいていのディスク系ベンチマークはシーク回数で勝負が決するので、これがベンチで性能がよく見える原因です。

というのはOSやファイルシステムを問わず事実なので、多かれ少なかれ効用が期待できるというのは確かだと思われます。で、4Kbyteのファイルを読み書きした場合を、実際に以下のようにして確認してみました。

  1. Disk Imageをこさえてmount
  2. dd if=/dev/zero of=test.ufs2 bs=16M count=64
    mdconfig -a -t vnode -f ./test.ufs2 -u 0
    newfs -U /dev/md0
    mount -o noatime /dev/md0 /mnt
    mkdir /mnt/dankogai
    chown dankogai /mnt/dankogai
    
  3. 初期状態(noatime, softupdateあり)で計測
  4. average: 0.483453, stddev:0.045193 (9.347932%) at bench.pl line 34.
    
  5. atimeありで計測
  6. mount -u atime /mnt
    average: 0.547789, stddev:0.083034 (15.157962%) at bench.pl line 36.
    
  7. softupdateもなしで計測
  8. umount /mnt
    tunefs -n disable /dev/md0
    mount /dev/md0 /mnt
    
    average: 0.543603, stddev:0.096880 (17.821746%) at bench.pl line 34.
    
計測用script
#!/usr/local/bin/perl
use strict;
use warnings;
use Time::HiRes qw/time/;

my $count    = shift || 10000;
my $filename = shift || 'test.txt';

my @elapsed = ();

for my $i (1..100){
    my $started = time();
    for my $i (1..$count){
        open my $fh, '<', $filename or die "$filename:$!";
        my $content = do{ local $/; <$fh> };
        close $fh;
    }
    my $elapsed = time()-$started;
    push @elapsed, $elapsed;
    warn $elapsed;
}

my ($ave, $stdev) = sub{
    my $a = 0;
    $a += $_ for @_;
    $a /= @_;
    my $v = 0;
    $v +=  ($_ - $a)**2 for @_;
    $v /= @_;
    ($a, sqrt($v));
}->(@elapsed);

warn sprintf "average: %f, stddev:%f (%f%%)", $ave, $stdev, ($stdev/$ave)*100;

パフォーマンス向上は、この場合で12%と出ました。扱うファイルはたった一つですし、サイズも4Kbytesで、ファイルシステムには事実上これしかファイルがないという状態ですから、disk cacheはかなり効く、すなわち差が出にくい状況でしたが、有為な差が見られました。これが細かいファイルがもっと沢山あって、それらの内容が頻繁にアクセスされる状況ではもっと出るかと思われます。

というわけで、noatimeというのは比較的損がないおまじないのようであるという結論でした。

Dan the Man with Too Many Kinds of Filesystems to Fiddle


この記事へのトラックバックURL

この記事へのトラックバック
はてなブックマークを見ていたら Linuxチューニング 第1部第1回 ファイル・アクセスを高速化:ITpro という記事が目に付いた。 システムのボトルネックは今も昔もハードディスクであることが多い。CDやDVD...
ファイルアクセスの高速化をしてみた【おやじまんのだめだこりゃ日記】at 2008年06月09日 22:46
404 Blog Not Found さんがトラックバックくれてるみたいだ。 すばらしいまとめ記事をありがとうございます。 せっかくなのでお礼がてら、トラバ??.
atimeはいつ更新される? のつづき【革命の日々!】at 2008年06月09日 18:47