地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【C】lsっぽいものをさらに早くするにはなにか出来ないかを考えてみた

ryuichi1208.hateblo.jp

この記事の番外編?的な記事。もっと早くならんかねを検証してみる。前回よりもファイル数を倍くらいにしてる(違う検証をしてたらこうなってしまっただけ)ので前回のデータとの単純な比較は出来ないです。

前回の最速のソース

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>

int main(int argc, char **argv) {
    DIR   *dir;
    struct dirent  *dp;
    char  path[512];

    if(setvbuf(stdout, NULL, _IOFBF, 2147483647) != 0 ){
        printf("setvbuf error\n");
    }

    if(argc<=1){
        strcpy(path,".");
    } else {
        strcpy(path,argv[1]);
    }

    if((dir=opendir(path))==NULL){
        perror("opendir");
        exit(1);
    }

    for(dp=readdir(dir);dp!=NULL;dp=readdir(dir)){
        fprintf(stdout, "%s\n",dp->d_name);
    }
}

これに対してtimeとstraceをするとgetdetns64の数を減らしてあげることができればさらに早くすることはできそう

root@master001:/tmp/lstest2# time ./a.out lstest > ls.lst

real    0m2.612s
user    0m0.124s
sys 0m0.675s

root@master001:/tmp/lstest2# strace -c ./a.out lstest > ls.lst
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 92.06    0.555802         261      2125           getdents64
  7.79    0.047044           7      6133           write
------ ----------- ----------- --------- --------- ----------------
100.00    0.603770          72      8296         2 total

getdents(2)のmanを見るとどうやらbufferを取ることが出来るっぽいのでやってみる。syscall(2)で呼ぶ必要がある

#define _GNU_SOURCE
#include <dirent.h> /* DT_* 定数の定義 */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#define handle_error(msg) \
        do { perror(msg); exit(1); } while (0)

struct linux_dirent {
    unsigned long  d_ino;
    off_t          d_off;
    unsigned short d_reclen;
    char           d_name[]; };

#define BUF_SIZE 1024 * 1024 // ここのサイズを変化させて計測してみた

int main(int argc, char *argv[]) {
    int fd;
    long nread;
    char buf[BUF_SIZE];
    struct linux_dirent *d;
    char d_type;


    fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
    if (fd == -1)
        handle_error("open");


    for (;;) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
        if (nread == -1)
            handle_error("getdents");


        if (nread == 0)
            break;


        for (long bpos = 0; bpos < nread;) {
            d = (struct linux_dirent *) (buf + bpos);
            d_type = *(buf + bpos + d->d_reclen - 1);
            printf("%s\n", d->d_name);
            bpos += d->d_reclen;
        }
    }
    exit(0);
}

BUF_SIZEをそれぞれ以下のように変えてファイル数を固定にしてそれぞれ計測してみた。

BUF_SIZE seconds usecs/call calls
1KB 0.880076 12 68795
4KB 0.680982 39 17036
16KB 0.605966 142 4250
128KB 0.602617 1132 532
512KB 0.584683 4363 134
1MB 0.559468 8227 68
2MB 0.613253 17521 35

大体1MBあたりが一番早くなりそうであった。元のソースと30回のずつの平均を比較すると5%程度高速化することが分かった。

  • 前回: 2.911s
  • 今回: 2.774s

感想

いつ使えるか分からない話ではあるが勉強にはなったのでよかった