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 7c429c6df perf(table): speed up partition metadata ordering keys 
(#2252)
7c429c6df is described below

commit 7c429c6df881125ded8809ccad4c8e573f52a3cd
Author: Minh Vu <[email protected]>
AuthorDate: Wed Oct 21 22:50:47 2026 +0200

    perf(table): speed up partition metadata ordering keys (#2252)
    
    * perf(table): speed up partition metadata ordering keys
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * style(table): modernize partition benchmark loop
    
    ---------
    
    Signed-off-by: Minh Vu <[email protected]>
---
 table/inspect_partition_key_test.go | 148 ++++++++++++++++++++++++++++++++++++
 table/inspect_partitions.go         |  41 +++++++++-
 2 files changed, 188 insertions(+), 1 deletion(-)

diff --git a/table/inspect_partition_key_test.go 
b/table/inspect_partition_key_test.go
new file mode 100644
index 000000000..4014f7fb6
--- /dev/null
+++ b/table/inspect_partition_key_test.go
@@ -0,0 +1,148 @@
+// 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 table
+
+import (
+       "fmt"
+       "math"
+       "math/rand"
+       "sort"
+       "strings"
+       "testing"
+       "time"
+)
+
+// Keep the old formatter as the oracle for uncommon value types and edge 
cases.
+func inspectPartitionKeyFmtReference(partition map[int]any) string {
+       ids := make([]int, 0, len(partition))
+       for id := range partition {
+               ids = append(ids, id)
+       }
+       sort.Ints(ids)
+
+       var key strings.Builder
+       for _, id := range ids {
+               fmt.Fprintf(&key, "%d:%T:%v;", id, partition[id], partition[id])
+       }
+
+       return key.String()
+}
+
+func TestInspectPartitionKeyMatchesFmt(t *testing.T) {
+       check := func(partition map[int]any) {
+               t.Helper()
+               want := inspectPartitionKeyFmtReference(partition)
+               if got := inspectPartitionKey(partition); got != want {
+                       t.Fatalf("partition key mismatch for %v: got %q, want 
%q", partition, got, want)
+               }
+       }
+
+       for _, partition := range []map[int]any{
+               nil,
+               {},
+               {101: int32(-2147483648), 12: int64(-9223372036854775807 - 1), 
-1: false},
+               {2: "unicode / 😀", 99: []byte{0, 127, 255}, 4: true},
+               {30: []byte(nil), 40: []byte{}, 50: nil},
+               {7: math.NaN(), 8: math.Inf(-1), 1: time.Unix(0, 0).UTC()},
+               {1: struct{ Tag string }{"fallback"}},
+       } {
+               check(partition)
+       }
+
+       rng := rand.New(rand.NewSource(19))
+       for range 1000 {
+               partition := make(map[int]any)
+               for range rng.Intn(24) {
+                       id := rng.Intn(50) - 10
+                       switch rng.Intn(9) {
+                       case 0:
+                               partition[id] = rng.Int31()
+                       case 1:
+                               partition[id] = rng.Int63()
+                       case 2:
+                               partition[id] = fmt.Sprintf("str-%d", 
rng.Int63())
+                       case 3:
+                               partition[id] = rng.Intn(2) == 1
+                       case 4:
+                               partition[id] = []byte{byte(rng.Intn(256)), 
byte(rng.Intn(256))}
+                       case 5:
+                               partition[id] = []byte(nil)
+                       case 6:
+                               partition[id] = 
math.Copysign(float64(rng.Intn(100)), -1)
+                       case 7:
+                               partition[id] = nil
+                       case 8:
+                               partition[id] = uint32(rng.Uint32())
+                       }
+               }
+               check(partition)
+       }
+}
+
+var inspectPartitionKeyBenchmarkSink string
+
+func BenchmarkInspectPartitionKey(b *testing.B) {
+       for _, fieldCount := range []int{1, 8, 32} {
+               for _, kind := range []string{"int32", "string", "binary", 
"mixed"} {
+                       partitions := make([]map[int]any, 1000)
+                       for i := range partitions {
+                               partition := make(map[int]any, fieldCount)
+                               for j := range fieldCount {
+                                       id := j + 1000
+                                       switch kind {
+                                       case "int32":
+                                               partition[id] = int32(i + j)
+                                       case "string":
+                                               partition[id] = 
fmt.Sprintf("part-%d-field-%d", i, j)
+                                       case "binary":
+                                               partition[id] = []byte{1, 2, 3, 
4, 5, 6, 7, 8, 9, 10}
+                                       case "mixed":
+                                               switch j % 4 {
+                                               case 0:
+                                                       partition[id] = int32(i 
+ j)
+                                               case 1:
+                                                       partition[id] = 
int64(-i - j)
+                                               case 2:
+                                                       partition[id] = 
fmt.Sprintf("p-%d", i)
+                                               case 3:
+                                                       partition[id] = 
[]byte{1, 2, 3, 4, 5}
+                                               }
+                                       }
+                               }
+                               partitions[i] = partition
+                       }
+
+                       b.Run(fmt.Sprintf("%s/%d_fields", kind, fieldCount), 
func(b *testing.B) {
+                               for _, benchmark := range []struct {
+                                       name string
+                                       key  func(map[int]any) string
+                               }{
+                                       {"fmt", 
inspectPartitionKeyFmtReference},
+                                       {"optimized", inspectPartitionKey},
+                               } {
+                                       b.Run(benchmark.name, func(b 
*testing.B) {
+                                               b.ReportAllocs()
+                                               for i := range b.N {
+                                                       
inspectPartitionKeyBenchmarkSink = benchmark.key(partitions[i%len(partitions)])
+                                               }
+                                       })
+                               }
+                       })
+               }
+       }
+}
diff --git a/table/inspect_partitions.go b/table/inspect_partitions.go
index 62f73efba..4bb492bcf 100644
--- a/table/inspect_partitions.go
+++ b/table/inspect_partitions.go
@@ -22,6 +22,7 @@ import (
        "fmt"
        "slices"
        "sort"
+       "strconv"
        "strings"
 
        "github.com/apache/arrow-go/v18/arrow"
@@ -399,9 +400,47 @@ func inspectPartitionKey(partition map[int]any) string {
                ids = append(ids, id)
        }
        sort.Ints(ids)
+
        var key strings.Builder
+       key.Grow(len(ids) * 24)
+       var number [32]byte
        for _, id := range ids {
-               fmt.Fprintf(&key, "%d:%T:%v;", id, partition[id], partition[id])
+               key.Write(strconv.AppendInt(number[:0], int64(id), 10))
+               key.WriteByte(':')
+
+               value := partition[id]
+               switch v := value.(type) {
+               case int32:
+                       key.WriteString("int32:")
+                       key.Write(strconv.AppendInt(number[:0], int64(v), 10))
+               case int64:
+                       key.WriteString("int64:")
+                       key.Write(strconv.AppendInt(number[:0], v, 10))
+               case string:
+                       key.WriteString("string:")
+                       key.WriteString(v)
+               case bool:
+                       key.WriteString("bool:")
+                       if v {
+                               key.WriteString("true")
+                       } else {
+                               key.WriteString("false")
+                       }
+               case []byte:
+                       // Match fmt's []uint8:[0 1 ...] representation, 
including nil slices.
+                       key.WriteString("[]uint8:[")
+                       for i, byt := range v {
+                               if i > 0 {
+                                       key.WriteByte(' ')
+                               }
+                               key.Write(strconv.AppendInt(number[:0], 
int64(byt), 10))
+                       }
+                       key.WriteByte(']')
+               default:
+                       // Preserve formatting for named Iceberg types and 
uncommon values.
+                       fmt.Fprintf(&key, "%T:%v", value, value)
+               }
+               key.WriteByte(';')
        }
 
        return key.String()

Reply via email to