]> Cypherpunks repositories - gostls13.git/commitdiff
os: make windows Stat as fast as Lstat for files and directories
authorAlex Brainman <alex.brainman@gmail.com>
Tue, 9 May 2017 06:50:41 +0000 (16:50 +1000)
committerAlex Brainman <alex.brainman@gmail.com>
Fri, 19 May 2017 05:55:01 +0000 (05:55 +0000)
Recent CL 41834 made windows Stat work for all symlinks.
But CL 41834 also made Stat slow.

John Starks sugested
(see https://github.com/golang/go/issues/19922#issuecomment-300031421)
to use GetFileAttributesEx for files and directories instead.
This makes Stat as fast as at go1.9.

I see these improvements on my Windows 7

name       old time/op  new time/op  delta
StatDot    26.5µs ± 1%  20.6µs ± 2%  -22.37%  (p=0.000 n=9+10)
StatFile   22.8µs ± 2%   6.2µs ± 1%  -72.69%  (p=0.000 n=10+10)
StatDir    21.0µs ± 2%   6.1µs ± 3%  -71.12%  (p=0.000 n=10+9)
LstatDot   20.1µs ± 1%  20.7µs ± 6%   +3.37%  (p=0.000 n=9+10)
LstatFile  6.23µs ± 1%  6.36µs ± 8%     ~     (p=0.587 n=9+10)
LstatDir   6.10µs ± 0%  6.14µs ± 4%     ~     (p=0.590 n=9+10)

and on my Windows XP

name         old time/op  new time/op  delta
StatDot-2    20.6µs ± 0%  10.8µs ± 0%  -47.44%  (p=0.000 n=10+10)
StatFile-2   20.2µs ± 0%   7.9µs ± 0%  -60.91%  (p=0.000 n=8+10)
StatDir-2    19.3µs ± 0%   7.6µs ± 0%  -60.51%  (p=0.000 n=10+9)
LstatDot-2   10.8µs ± 0%  10.8µs ± 0%   -0.48%  (p=0.000 n=10+8)
LstatFile-2  7.83µs ± 0%  7.83µs ± 0%     ~     (p=0.844 n=10+8)
LstatDir-2   7.59µs ± 0%  7.56µs ± 0%   -0.46%  (p=0.000 n=10+10)

Updates #19922

Change-Id: Ice1fb5825defb05c79bab4dec0692e0fd1bcfcd5
Reviewed-on: https://go-review.googlesource.com/43071
Reviewed-by: Austin Clements <austin@google.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/os/os_test.go
src/os/stat_windows.go

index 22777aef9f7723c85399955c91c68230a4cda2ab..91c6be6148d37f5b7297f03a4a995169444b0ac5 100644 (file)
@@ -398,6 +398,50 @@ func BenchmarkReaddir(b *testing.B) {
        benchmarkReaddir(".", b)
 }
 
+func benchmarkStat(b *testing.B, path string) {
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               _, err := Stat(path)
+               if err != nil {
+                       b.Fatalf("Stat(%q) failed: %v", path, err)
+               }
+       }
+}
+
+func benchmarkLstat(b *testing.B, path string) {
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               _, err := Lstat(path)
+               if err != nil {
+                       b.Fatalf("Lstat(%q) failed: %v", path, err)
+               }
+       }
+}
+
+func BenchmarkStatDot(b *testing.B) {
+       benchmarkStat(b, ".")
+}
+
+func BenchmarkStatFile(b *testing.B) {
+       benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkStatDir(b *testing.B) {
+       benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
+func BenchmarkLstatDot(b *testing.B) {
+       benchmarkLstat(b, ".")
+}
+
+func BenchmarkLstatFile(b *testing.B) {
+       benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkLstatDir(b *testing.B) {
+       benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
 // Read the directory one entry at a time.
 func smallReaddirnames(file *File, length int, t *testing.T) []string {
        names := make([]string, length)
index a7220041cdce02e775f07c594c1d533c06fc8290..667b99905de83053c3e91819c03cb8b3eddf6f86 100644 (file)
@@ -71,7 +71,23 @@ func Stat(name string) (FileInfo, error) {
        if err != nil {
                return nil, &PathError{"Stat", name, err}
        }
-
+       // Apparently (see https://github.com/golang/go/issues/19922#issuecomment-300031421)
+       // GetFileAttributesEx is fastest approach to get file info.
+       // It does not work for symlinks. But symlinks are rare,
+       // so try GetFileAttributesEx first.
+       var fs fileStat
+       err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
+       if err == nil && fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+               fs.path = name
+               if !isAbs(fs.path) {
+                       fs.path, err = syscall.FullPath(fs.path)
+                       if err != nil {
+                               return nil, &PathError{"FullPath", name, err}
+                       }
+               }
+               fs.name = basename(name)
+               return &fs, nil
+       }
        // Use Windows I/O manager to dereference the symbolic link, as per
        // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
        h, err := syscall.CreateFile(namep, 0, 0, nil,
@@ -170,7 +186,7 @@ func Lstat(name string) (FileInfo, error) {
        if !isAbs(fs.path) {
                fs.path, e = syscall.FullPath(fs.path)
                if e != nil {
-                       return nil, e
+                       return nil, &PathError{"FullPath", name, e}
                }
        }
        return fs, nil