]> Cypherpunks repositories - gostls13.git/commitdiff
archive/tar: optimize nanosecond parsing in parsePAXTime
author1911860538 <alxps1911@gmail.com>
Mon, 8 Sep 2025 16:19:34 +0000 (16:19 +0000)
committerGopher Robot <gobot@golang.org>
Mon, 8 Sep 2025 17:08:20 +0000 (10:08 -0700)
Modified parsePAXTime to use a byte array for nanosecond parsing, providing a more straightforward implementation with better performance when handling decimal fraction part.
Here are benchmark results:
goos: darwin
goarch: amd64
pkg: archive/tar
cpu: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
                                │   old.txt    │               new.txt                │
                                │    sec/op    │    sec/op     vs base                │
ParsePAXTIme/NoNanos-8            20.55n ±  4%   20.45n ± 12%        ~ (p=1.000 n=10)
ParsePAXTIme/ExactNanos-8         52.42n ±  2%   42.16n ±  3%  -19.57% (p=0.000 n=10)
ParsePAXTIme/WithNanoPadding-8    99.33n ±  2%   39.58n ±  2%  -60.16% (p=0.000 n=10)
ParsePAXTIme/WithNanoTruncate-8   54.78n ±  1%   43.64n ±  4%  -20.34% (p=0.000 n=10)
ParsePAXTIme/TrailingError-8      31.87n ±  4%   17.55n ±  2%  -44.94% (p=0.000 n=10)
ParsePAXTIme/LeadingError-8       31.03n ±  2%   15.81n ±  6%  -49.03% (p=0.000 n=10)

Change-Id: If05ef512137d0115db9cb6d3ab432335230628bb
GitHub-Last-Rev: 106d25e5cfd57e0264b4510c58d09e8f80e13b3f
GitHub-Pull-Request: golang/go#73164
Reviewed-on: https://go-review.googlesource.com/c/go/+/662835
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
src/archive/tar/strconv.go
src/archive/tar/strconv_test.go

index ac8105efadb4036cdeb027533a6d9c92fcb24f06..217efe9e2ee7f4670e3e50fe2fb30dd02a1de6e6 100644 (file)
@@ -213,15 +213,17 @@ func parsePAXTime(s string) (time.Time, error) {
        }
 
        // Parse the nanoseconds.
-       if strings.Trim(sn, "0123456789") != "" {
-               return time.Time{}, ErrHeader
-       }
-       if len(sn) < maxNanoSecondDigits {
-               sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
-       } else {
-               sn = sn[:maxNanoSecondDigits] // Right truncate
+       // Initialize an array with '0's to handle right padding automatically.
+       nanoDigits := [maxNanoSecondDigits]byte{'0', '0', '0', '0', '0', '0', '0', '0', '0'}
+       for i := range len(sn) {
+               switch c := sn[i]; {
+               case c < '0' || c > '9':
+                       return time.Time{}, ErrHeader
+               case i < len(nanoDigits):
+                       nanoDigits[i] = c
+               }
        }
-       nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
+       nsecs, _ := strconv.ParseInt(string(nanoDigits[:]), 10, 64) // Must succeed after validation
        if len(ss) > 0 && ss[0] == '-' {
                return time.Unix(secs, -1*nsecs), nil // Negative correction
        }
index add65e272ae6d776eecc97dd163f3988cddf23ce..d411153ce2b896df9e33255273b8581c4a04f915 100644 (file)
@@ -439,3 +439,66 @@ func TestFormatPAXRecord(t *testing.T) {
                }
        }
 }
+
+func BenchmarkParsePAXTIme(b *testing.B) {
+       tests := []struct {
+               name string
+               in   string
+               want time.Time
+               ok   bool
+       }{
+               {
+                       name: "NoNanos",
+                       in:   "123456",
+                       want: time.Unix(123456, 0),
+                       ok:   true,
+               },
+               {
+                       name: "ExactNanos",
+                       in:   "1.123456789",
+                       want: time.Unix(1, 123456789),
+                       ok:   true,
+               },
+               {
+                       name: "WithNanoPadding",
+                       in:   "1.123",
+                       want: time.Unix(1, 123000000),
+                       ok:   true,
+               },
+               {
+                       name: "WithNanoTruncate",
+                       in:   "1.123456789123",
+                       want: time.Unix(1, 123456789),
+                       ok:   true,
+               },
+               {
+                       name: "TrailingError",
+                       in:   "1.123abc",
+                       want: time.Time{},
+                       ok:   false,
+               },
+               {
+                       name: "LeadingError",
+                       in:   "1.abc123",
+                       want: time.Time{},
+                       ok:   false,
+               },
+       }
+       for _, tt := range tests {
+               b.Run(tt.name, func(b *testing.B) {
+                       b.ReportAllocs()
+                       for b.Loop() {
+                               ts, err := parsePAXTime(tt.in)
+                               if (err == nil) != tt.ok {
+                                       if err != nil {
+                                               b.Fatal(err)
+                                       }
+                                       b.Fatal("expected error")
+                               }
+                               if !ts.Equal(tt.want) {
+                                       b.Fatalf("time mismatch: got %v, want %v", ts, tt.want)
+                               }
+                       }
+               })
+       }
+}