golang os * File.Readdir在所有文件上使用lstat。 可以优化吗?

我正在编写一个程序,它使用os.File.Readdir包含大量文件的父目录中的所有子目录,但运行strace查看os.File.Readdir的计数表明go版本正在使用lstat()在目录中的所有文件/目录。 (我现在正在使用/usr/bin目录进行testing)

转到代码:

 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()testing了同样的情况,却没有看到这种行为。

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,但是我正在为特定的文件系统编写这个字段。

试过*File.Readdirnames() ,它不使用lstat并给出所有文件和目录的列表,而是查看返回的string是文件还是目录最终会再次执行lstat

  • 我想知道是否有可能重新编写go程序,以避免不必要的所有文件的lstat() 。 我可以看到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版本之间的两倍,将会碰到磁盘。

dirent看起来像它完成了你在找什么。 下面是你用Go编写的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) } } }