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

noh*_*hup 7 c linux filesystems go stat

我正在编写一个程序,它从父目录中查找包含大量文件的所有子目录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)
    }

}
Run Code Online (Sandbox Code Playgroud)

程序上的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
Run Code Online (Sandbox Code Playgroud)

我在readdir()没有看到这种行为的情况下对C进行了相同的测试.

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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我知道POSIX.1强制要求的dirent结构中的唯一字段是d_name和d_ino,但我是为特定的文件系统编写的.

尝试*File.Readdirnames(),它不使用an 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 entries */, 32768) = 32752
  • 这有点像过早的优化,我不应该担心吗?我提出这个问题是因为被监视目录中的文件数量将包含大量的小型归档文件,而系统调用的差异几乎是版本之间的两倍,CGO版本将会出现在磁盘上.

Tim*_*per 5

该程序包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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)