This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 904078c3f perf(transforms): speed up year and month partition 
transforms (#2263)
904078c3f is described below

commit 904078c3f6810c9ff5f845a4cccd12137c17ebe0
Author: Minh Vu <[email protected]>
AuthorDate: Thu Oct 22 19:58:24 2026 +0200

    perf(transforms): speed up year and month partition transforms (#2263)
    
    * perf(transforms): calculate year and month partitions from Unix days
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * test(transforms): cover calendar parity and benchmark reference path
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * style(transforms): keep YearTransform doc next to type
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * bench(transforms): compare inline reference converters
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * lint(transforms): add blank lines before returns
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * lint(transforms): space calendar reference returns
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * style(transforms): fix calendar fast-path benchmark lint
    
    ---------
    
    Signed-off-by: Minh Vu <[email protected]>
---
 transforms.go                        |  82 +++++++---
 transforms_calendar_fastpath_test.go | 289 +++++++++++++++++++++++++++++++++++
 2 files changed, 347 insertions(+), 24 deletions(-)

diff --git a/transforms.go b/transforms.go
index 92895949f..84e8b4fc3 100644
--- a/transforms.go
+++ b/transforms.go
@@ -988,6 +988,44 @@ func projectTimeTransform(t TimeTransform, name string, 
pred BoundPredicate) (Un
        return nil, nil
 }
 
+// yearMonthFromUnixDays converts days since 1970-01-01 to a Gregorian year
+// and month without constructing a time.Time. The 400-year era calculation
+// handles dates before the Unix epoch and all representable Iceberg dates.
+func yearMonthFromUnixDays(days int64) (year, month int) {
+       z := days + 719468
+       era := z / 146097
+       if z < 0 {
+               era = (z - 146096) / 146097
+       }
+
+       dayOfEra := z - era*146097
+       yearOfEra := (dayOfEra - dayOfEra/1460 + dayOfEra/36524 - 
dayOfEra/146096) / 365
+       y := yearOfEra + era*400
+       dayOfYear := dayOfEra - (365*yearOfEra + yearOfEra/4 - yearOfEra/100)
+       monthIndex := (5*dayOfYear + 2) / 153
+       m := monthIndex + 3
+       if monthIndex >= 10 {
+               m = monthIndex - 9
+       }
+       if m <= 2 {
+               y++
+       }
+
+       return int(y), int(m)
+}
+
+func yearPartitionFromDays(days int64) int32 {
+       year, _ := yearMonthFromUnixDays(days)
+
+       return int32(year - 1970)
+}
+
+func monthPartitionFromDays(days int64) int32 {
+       year, month := yearMonthFromUnixDays(days)
+
+       return int32((year-1970)*12 + month - 1)
+}
+
 // YearTransform transforms a datetime value into a year value.
 type YearTransform struct{}
 
@@ -1022,7 +1060,7 @@ func (YearTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
 
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32(v.(Date).ToTime().Year() - 
epochTM.Year()),
+                               Val:   yearPartitionFromDays(int64(v.(Date))),
                        }
                }, nil
        case TimestampType, TimestampTzType:
@@ -1033,7 +1071,7 @@ func (YearTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
 
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32(v.(Timestamp).ToTime().Year() - 
epochTM.Year()),
+                               Val:   
yearPartitionFromDays(int64(v.(Timestamp).ToDate())),
                        }
                }, nil
        case TimestampNsType, TimestampTzNsType:
@@ -1044,7 +1082,7 @@ func (YearTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
 
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32(v.(TimestampNano).ToTime().Year() 
- epochTM.Year()),
+                               Val:   
yearPartitionFromDays(int64(v.(TimestampNano).ToDate())),
                        }
                }, nil
        }
@@ -1058,18 +1096,21 @@ func (YearTransform) Apply(value Optional[Literal]) 
(out Optional[Literal]) {
                return out
        }
 
+       var days int64
        switch v := value.Val.(type) {
        case DateLiteral:
-               out.Valid = true
-               out.Val = Int32Literal(Date(v).ToTime().Year() - epochTM.Year())
+               days = int64(v)
        case TimestampLiteral:
-               out.Valid = true
-               out.Val = Int32Literal(Timestamp(v).ToTime().Year() - 
epochTM.Year())
+               days = int64(Timestamp(v).ToDate())
        case TimestampNsLiteral:
-               out.Valid = true
-               out.Val = Int32Literal(TimestampNano(v).ToTime().Year() - 
epochTM.Year())
+               days = int64(TimestampNano(v).ToDate())
+       default:
+               return out
        }
 
+       out.Valid = true
+       out.Val = Int32Literal(yearPartitionFromDays(days))
+
        return out
 }
 
@@ -1122,11 +1163,9 @@ func (MonthTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
                                return Optional[int32]{}
                        }
 
-                       d := v.(Date).ToTime()
-
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32((d.Year()-epochTM.Year())*12 + 
(int(d.Month()) - int(epochTM.Month()))),
+                               Val:   monthPartitionFromDays(int64(v.(Date))),
                        }
                }, nil
        case TimestampType, TimestampTzType:
@@ -1135,11 +1174,9 @@ func (MonthTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
                                return Optional[int32]{}
                        }
 
-                       d := v.(Timestamp).ToTime()
-
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32((d.Year()-epochTM.Year())*12 + 
(int(d.Month()) - int(epochTM.Month()))),
+                               Val:   
monthPartitionFromDays(int64(v.(Timestamp).ToDate())),
                        }
                }, nil
        case TimestampNsType, TimestampTzNsType:
@@ -1148,14 +1185,11 @@ func (MonthTransform) Transformer(src Type) (func(any) 
Optional[int32], error) {
                                return Optional[int32]{}
                        }
 
-                       d := v.(TimestampNano).ToTime()
-
                        return Optional[int32]{
                                Valid: true,
-                               Val:   int32((d.Year()-epochTM.Year())*12 + 
(int(d.Month()) - int(epochTM.Month()))),
+                               Val:   
monthPartitionFromDays(int64(v.(TimestampNano).ToDate())),
                        }
                }, nil
-
        }
 
        return nil, fmt.Errorf("%w: cannot apply month transform for type %s",
@@ -1167,20 +1201,20 @@ func (MonthTransform) Apply(value Optional[Literal]) 
(out Optional[Literal]) {
                return out
        }
 
-       var tm time.Time
+       var days int64
        switch v := value.Val.(type) {
        case DateLiteral:
-               tm = Date(v).ToTime()
+               days = int64(v)
        case TimestampLiteral:
-               tm = Timestamp(v).ToTime()
+               days = int64(Timestamp(v).ToDate())
        case TimestampNsLiteral:
-               tm = TimestampNano(v).ToTime()
+               days = int64(TimestampNano(v).ToDate())
        default:
                return out
        }
 
        out.Valid = true
-       out.Val = Int32Literal(int32((tm.Year()-epochTM.Year())*12 + 
(int(tm.Month()) - int(epochTM.Month()))))
+       out.Val = Int32Literal(monthPartitionFromDays(days))
 
        return out
 }
diff --git a/transforms_calendar_fastpath_test.go 
b/transforms_calendar_fastpath_test.go
new file mode 100644
index 000000000..3316f51c2
--- /dev/null
+++ b/transforms_calendar_fastpath_test.go
@@ -0,0 +1,289 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iceberg
+
+import (
+       "math"
+       "math/rand"
+       "testing"
+       "time"
+)
+
+func checkCalendarDays(t *testing.T, days int64) {
+       t.Helper()
+       expected := epochTM.AddDate(0, 0, int(days))
+       year, month := yearMonthFromUnixDays(days)
+       if year != expected.Year() || month != int(expected.Month()) {
+               t.Fatalf("days=%d: got %d-%02d; expected %d-%02d",
+                       days, year, month, expected.Year(), expected.Month())
+       }
+}
+
+func TestYearMonthFromUnixDaysMatchesTime(t *testing.T) {
+       for _, days := range []int64{
+               math.MinInt32, -719469, -719468, -719467,
+               -146097, -146096, -366, -365, -1, 0, 1, 59, 60,
+               365, 366, 10957, 19782, 20000, 146097, math.MaxInt32,
+       } {
+               checkCalendarDays(t, days)
+       }
+
+       // Exercise Gregorian leap days, century boundaries, pre-epoch dates
+       // and the entire signed Date range with a fixed seed.
+       rng := rand.New(rand.NewSource(19700101))
+       for range 30000 {
+               checkCalendarDays(t, int64(int32(rng.Uint32())))
+       }
+}
+
+func TestYearMonthPartitionTransformParity(t *testing.T) {
+       testCases := []struct {
+               name   string
+               typ    Type
+               values []any
+       }{
+               {
+                       name: "date",
+                       typ:  DateType{},
+                       values: []any{
+                               Date(math.MinInt32), Date(-719468), Date(-365), 
Date(-1),
+                               Date(0), Date(1), Date(18628), Date(20000), 
Date(math.MaxInt32),
+                       },
+               },
+               {
+                       name: "timestamp",
+                       typ:  TimestampType{},
+                       values: []any{
+                               Timestamp(math.MinInt64), 
Timestamp(-86_400_000_001),
+                               Timestamp(-1), Timestamp(0), Timestamp(1),
+                               Timestamp(86_399_999_999), 
Timestamp(86_400_000_000),
+                               Timestamp(math.MaxInt64),
+                       },
+               },
+               {
+                       name: "timestamp-nanoseconds",
+                       typ:  TimestampNsType{},
+                       values: []any{
+                               TimestampNano(math.MinInt64), 
TimestampNano(-86_400_000_000_001),
+                               TimestampNano(-1), TimestampNano(0), 
TimestampNano(1),
+                               TimestampNano(86_399_999_999_999), 
TimestampNano(86_400_000_000_000),
+                               TimestampNano(math.MaxInt64),
+                       },
+               },
+       }
+
+       for _, tc := range testCases {
+               for _, tr := range []struct {
+                       name      string
+                       transform TimeTransform
+               }{
+                       {"year", YearTransform{}},
+                       {"month", MonthTransform{}},
+               } {
+                       t.Run(tc.name+"/"+tr.name, func(t *testing.T) {
+                               fn, err := tr.transform.Transformer(tc.typ)
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+                               if got := fn(nil); got.Valid {
+                                       t.Fatal("nil input should return 
invalid optional")
+                               }
+
+                               check := func(value any) {
+                                       t.Helper()
+                                       var tm time.Time
+                                       var literal Literal
+                                       switch v := value.(type) {
+                                       case Date:
+                                               tm, literal = v.ToTime(), 
DateLiteral(v)
+                                       case Timestamp:
+                                               tm, literal = v.ToTime(), 
TimestampLiteral(v)
+                                       case TimestampNano:
+                                               tm, literal = v.ToTime(), 
TimestampNsLiteral(v)
+                                       }
+                                       expected := int32(tm.Year() - 
epochTM.Year())
+                                       if tr.name == "month" {
+                                               expected = 
int32((tm.Year()-epochTM.Year())*12 + int(tm.Month()) - int(epochTM.Month()))
+                                       }
+
+                                       if result := fn(value); !result.Valid 
|| result.Val != expected {
+                                               t.Fatalf("%s of %v: 
Transformer=%v, expected=%d",
+                                                       tr.name, value, result, 
expected)
+                                       }
+
+                                       if applied := 
tr.transform.Apply(Optional[Literal]{Valid: true, Val: literal}); 
!applied.Valid ||
+                                               applied.Val != 
Int32Literal(expected) {
+                                               t.Fatalf("%s of %v: Apply=%v, 
expected=%d",
+                                                       tr.name, value, 
applied, expected)
+                                       }
+                               }
+
+                               for _, value := range tc.values {
+                                       check(value)
+                               }
+                               rng := rand.New(rand.NewSource(934871))
+                               for range 5000 {
+                                       switch tc.typ.(type) {
+                                       case DateType:
+                                               check(Date(int32(rng.Uint32())))
+                                       case TimestampType:
+                                               
check(Timestamp(int64(rng.Uint64())))
+                                       case TimestampNsType:
+                                               
check(TimestampNano(int64(rng.Uint64())))
+                                       }
+                               }
+                       })
+               }
+       }
+}
+
+// referenceCalendarTransformer preserves the previous time.Time-based
+// implementations for paired benchmarks. Keep each source type assertion
+// inside its closure to compare equivalent work.
+func referenceCalendarTransformer(src Type, month bool) func(any) 
Optional[int32] {
+       switch src.(type) {
+       case DateType:
+               if month {
+                       return func(value any) Optional[int32] {
+                               if value == nil {
+                                       return Optional[int32]{}
+                               }
+                               tm := value.(Date).ToTime()
+
+                               return Optional[int32]{
+                                       Valid: true,
+                                       Val:   
int32((tm.Year()-epochTM.Year())*12 + int(tm.Month()) - int(epochTM.Month())),
+                               }
+                       }
+               }
+
+               return func(value any) Optional[int32] {
+                       if value == nil {
+                               return Optional[int32]{}
+                       }
+                       tm := value.(Date).ToTime()
+
+                       return Optional[int32]{Valid: true, Val: 
int32(tm.Year() - epochTM.Year())}
+               }
+       case TimestampType:
+               if month {
+                       return func(value any) Optional[int32] {
+                               if value == nil {
+                                       return Optional[int32]{}
+                               }
+                               tm := value.(Timestamp).ToTime()
+
+                               return Optional[int32]{
+                                       Valid: true,
+                                       Val:   
int32((tm.Year()-epochTM.Year())*12 + int(tm.Month()) - int(epochTM.Month())),
+                               }
+                       }
+               }
+
+               return func(value any) Optional[int32] {
+                       if value == nil {
+                               return Optional[int32]{}
+                       }
+                       tm := value.(Timestamp).ToTime()
+
+                       return Optional[int32]{Valid: true, Val: 
int32(tm.Year() - epochTM.Year())}
+               }
+       case TimestampNsType:
+               if month {
+                       return func(value any) Optional[int32] {
+                               if value == nil {
+                                       return Optional[int32]{}
+                               }
+                               tm := value.(TimestampNano).ToTime()
+
+                               return Optional[int32]{
+                                       Valid: true,
+                                       Val:   
int32((tm.Year()-epochTM.Year())*12 + int(tm.Month()) - int(epochTM.Month())),
+                               }
+                       }
+               }
+
+               return func(value any) Optional[int32] {
+                       if value == nil {
+                               return Optional[int32]{}
+                       }
+                       tm := value.(TimestampNano).ToTime()
+
+                       return Optional[int32]{Valid: true, Val: 
int32(tm.Year() - epochTM.Year())}
+               }
+       }
+
+       return nil
+}
+
+var calendarBenchmarkSink int32
+
+func BenchmarkCalendarPartitionTransform(b *testing.B) {
+       cases := []struct {
+               name   string
+               typ    Type
+               values []any
+       }{
+               {
+                       name:   "date",
+                       typ:    DateType{},
+                       values: []any{Date(0), Date(-10000), Date(19782), 
Date(20000), Date(math.MinInt32), Date(math.MaxInt32)},
+               },
+               {
+                       name:   "timestamp",
+                       typ:    TimestampType{},
+                       values: []any{Timestamp(0), Timestamp(-1), 
Timestamp(-1682982932131), Timestamp(17282931234567), Timestamp(math.MaxInt64)},
+               },
+               {
+                       name:   "timestamp-nanoseconds",
+                       typ:    TimestampNsType{},
+                       values: []any{TimestampNano(0), TimestampNano(-1), 
TimestampNano(-1682982932131), TimestampNano(17282931234567), 
TimestampNano(math.MaxInt64)},
+               },
+       }
+       for _, tc := range cases {
+               for _, tr := range []struct {
+                       name      string
+                       transform TimeTransform
+                       isMonth   bool
+               }{
+                       {name: "year", transform: YearTransform{}},
+                       {name: "month", transform: MonthTransform{}, isMonth: 
true},
+               } {
+                       optimized, err := tr.transform.Transformer(tc.typ)
+                       if err != nil {
+                               b.Fatal(err)
+                       }
+                       reference := referenceCalendarTransformer(tc.typ, 
tr.isMonth)
+                       for _, candidate := range []struct {
+                               name string
+                               fn   func(any) Optional[int32]
+                       }{
+                               {name: "reference", fn: reference},
+                               {name: "optimized", fn: optimized},
+                       } {
+                               b.Run(tc.name+"/"+tr.name+"/"+candidate.name, 
func(b *testing.B) {
+                                       b.ReportAllocs()
+                                       var sum int32
+                                       for i := range b.N {
+                                               sum += 
candidate.fn(tc.values[i%len(tc.values)]).Val
+                                       }
+                                       calendarBenchmarkSink = sum
+                               })
+                       }
+               }
+       }
+}

Reply via email to