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

我正在编写一个程序,从父目录中查找所有子目录,其中包含使用os.File.Readdir的大量文件,但是运行strace来查看系统调用的计数表明go版本使用的是lstat()在父目录中存在的所有文件/目录上. (我现在用/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,但我是为特定的文件系统编写的.

尝试* File.Readdirnames(),它不使用lstat并提供所有文件和目录的列表,但是要查看返回的字符串是文件还是目录,最终会再次执行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条目* /,32768)= 32752
>这有点像过早的优化,我不应该担心吗?我提出了这个问题,因为被监视目录中的文件数量将包含大量的小型存档文件,系统调用的差异几乎是C和GO版本之间的两倍,这将会击中磁盘.

最佳答案
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)
        }
    }
}

转载注明原文:golang os * File.Readdir在所有文件上使用lstat.可以优化吗? - 代码日志