2017-01-01 88 views
6

Piszę program, który znajduje wszystkie podkatalogi z katalogu nadrzędnego, który zawiera ogromną liczbę plików przy użyciu os.File.Readdir, ale uruchomienie strace, aby zobaczyć liczbę systemów, pokazało, że wersja go używa pliku lstat() we wszystkich plikach/katalogi obecne w katalogu nadrzędnym. (I 'm testowania to z /usr/bin katalogu na razie)golang os * File.Readdir za pomocą lstat na wszystkie pliki. Czy można go zoptymalizować?

kod

Go:

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 na program (bez następujących wątków):

% 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 

Przetestowałem to samo z C na readdir() nie widząc tego zachowania.

kod

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 W programie:

% 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 

Zdaję sobie sprawę, że tylko pola w strukturze dirent które są upoważnione przez POSIX.1 są d_name i d_ino, ale Piszę to dla określonego systemu plików.

Tried *File.Readdirnames(), który nie korzysta z lstat i podaje listę wszystkich plików i katalogów, ale aby sprawdzić, czy zwrócony ciąg jest plik lub katalog w końcu zrobić lstat ponownie.

  • Zastanawiam się, czy możliwe jest ponowne napisanie programu go w taki sposób, aby nie trzeba było koniecznie koniecznie zapisywać wszystkich plików na lstat(). Widzę, że program C używa poniższych systemów. 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
  • Czy jest to coś w rodzaju przedwczesnej optymalizacji, o której nie powinienem się martwić? Podniosłem to pytanie, ponieważ liczba plików w monitorowanym katalogu będzie zawierała ogromną liczbę małych zarchiwizowanych plików, a różnica w systemach będzie prawie dwukrotnie większa od wersji C i GO, która będzie trafiać na dysk.
+1

[Pakiet] (https://godoc.org/github.com/EricLagergren/go-gnulib/dirent) wydaje się, że powinna ona dostarczyć pożądane zachowanie. –

+0

Dziękuję @TimCooper. Jeśli mógłbyś to ująć jako odpowiedź, zaakceptuję to. – nohup

Odpowiedz

4

Wygląda na to, że pakiet dirent spełnia to, czego szukasz. Poniżej jest Twój przykład C napisany w ruchu:

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) 
     } 
    } 
}