2017-01-01 5 views
6

私はos.File.Readdirを使用して膨大な数のファイルを含む親ディレクトリからすべてのサブディレクトリを検索するプログラムを作成していますが、straceを実行してシステムコールの数を確認すると、goバージョンはすべてのファイルに対してlstat() /ディレクトリが親ディレクトリにあります。golang os *すべてのファイルに対してlstatを使用したFile.Readdir。それは最適化できますか?

囲碁コードを(私は今の/usr/binディレクトリでこれをテストしてい):

package main 
import (
     "fmt" 
    "os" 
) 
func main() { 
    x, err := os.Open("/usr/bin") 
    if err != nil { 
     panic(err) 
    } 
    y, err := x.Readdir(0) 
    if err != nil { 
     panic(err) 
    } 
    for _, i := range y { 
    fmt.Println(i) 
    } 

} 

straceの(次のスレッドなし)プログラム上:

% time  seconds usecs/call  calls errors syscall 
------ ----------- ----------- --------- --------- ---------------- 
93.62 0.004110   2  2466   write 
    3.46 0.000152   7  22   getdents64 
    2.92 0.000128   0  2466   lstat // this increases with increase in no. of files. 
    0.00 0.000000   0  11   mmap 
    0.00 0.000000   0   1   munmap 
    0.00 0.000000   0  114   rt_sigaction 
    0.00 0.000000   0   8   rt_sigprocmask 
    0.00 0.000000   0   1   sched_yield 
    0.00 0.000000   0   3   clone 
    0.00 0.000000   0   1   execve 
    0.00 0.000000   0   2   sigaltstack 
    0.00 0.000000   0   1   arch_prctl 
    0.00 0.000000   0   1   gettid 
    0.00 0.000000   0  57   futex 
    0.00 0.000000   0   1   sched_getaffinity 
    0.00 0.000000   0   1   openat 
------ ----------- ----------- --------- --------- ---------------- 
100.00 0.004390     5156   total 

私はCのreaddir()と同じことをテストしましたこの動作を見ることなく。

Cコード:

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

int main (void) { 
    DIR* dir_p; 
    struct dirent* dir_ent; 

    dir_p = opendir ("/usr/bin"); 

    if (dir_p != NULL) { 
     // The readdir() function returns a pointer to a dirent structure representing the next 
     // directory entry in the directory stream pointed to by dirp. 
     // It returns NULL on reaching the end of the directory stream or if an error occurred. 
     while ((dir_ent = readdir (dir_p)) != NULL) { 
      // printf("%s", dir_ent->d_name); 
      // printf("%d", dir_ent->d_type); 
      if (dir_ent->d_type == DT_DIR) { 
       printf("%s is a directory", dir_ent->d_name); 
      } else { 
       printf("%s is not a directory", dir_ent->d_name); 
      } 

      printf("\n"); 
     } 
      (void) closedir(dir_p); 

    } 
    else 
     perror ("Couldn't open the directory"); 

    return 0; 
} 

straceのプログラム上:

% time  seconds usecs/call  calls errors syscall 
------ ----------- ----------- --------- --------- ---------------- 
100.00 0.000128   0  2468   write 
    0.00 0.000000   0   1   read 
    0.00 0.000000   0   3   open 
    0.00 0.000000   0   3   close 
    0.00 0.000000   0   4   fstat 
    0.00 0.000000   0   8   mmap 
    0.00 0.000000   0   3   mprotect 
    0.00 0.000000   0   1   munmap 
    0.00 0.000000   0   3   brk 
    0.00 0.000000   0   3   3 access 
    0.00 0.000000   0   1   execve 
    0.00 0.000000   0   4   getdents 
    0.00 0.000000   0   1   arch_prctl 
------ ----------- ----------- --------- --------- ---------------- 
100.00 0.000128     2503   3 total 

私はPOSIX.1で義務付けられているdirent構造体の唯一のフィールドがd_nameとd_inoであることを承知していますが、私はこれを特定のファイルシステム用に書いています。 を使用せず、すべてのファイルとディレクトリのリストを返しますが、返された文字列がファイルかディレクトリかを確認するために、最終的にlstatを実行します。

  • すべてのファイルのlstat()が必ずしも必要ないように、goプログラムを書き直すことが可能かどうかは疑問でした。 Cプログラムが以下のシステムコールを使用しているのがわかりました。 open("/usr/bin", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFDIR|0755, st_size=69632, ...}) = 0 brk(NULL) = 0x1098000 brk(0x10c1000) = 0x10c1000 getdents(3, /* 986 entries */, 32768) = 32752
  • これは早すぎる最適化のようなものですが、私は心配しないでください。私はこの問題を提起しました。監視されているディレクトリ内のファイルの数には、膨大な数の小さなアーカイブファイルがあるため、システムコールの違いはディスクに当たるCGOバージョンの間のほぼ2倍です。
+1

[このパッケージ](https://godoc.org/github.com/EricLagergren/go-gnulib/dirent)がそうです。 –

+0

ありがとう@TimCooper。あなたが答えとしてそれを置くことができれば、私はそれを受け入れます。 – nohup

答えて

4

パッケージdirentは、あなたが探しているものを達成するようです。下回らで書かれたあなたのCの例である:それは希望の動作を提供しなければならないよう

package main 

import (
    "bytes" 
    "fmt" 
    "io" 

    "github.com/EricLagergren/go-gnulib/dirent" 
    "golang.org/x/sys/unix" 
) 

func int8ToString(s []int8) string { 
    var buff bytes.Buffer 
    for _, chr := range s { 
     if chr == 0x00 { 
      break 
     } 
     buff.WriteByte(byte(chr)) 
    } 
    return buff.String() 
} 

func main() { 
    stream, err := dirent.Open("/usr/bin") 
    if err != nil { 
     panic(err) 
    } 
    defer stream.Close() 
    for { 
     entry, err := stream.Read() 
     if err != nil { 
      if err == io.EOF { 
       break 
      } 
      panic(err) 
     } 

     name := int8ToString(entry.Name[:]) 
     if entry.Type == unix.DT_DIR { 
      fmt.Printf("%s is a directory\n", name) 
     } else { 
      fmt.Printf("%s is not a directory\n", name) 
     } 
    } 
} 
関連する問題