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 cf608e9e6 perf(table): build multi-part location paths in one 
allocation (#2249)
cf608e9e6 is described below

commit cf608e9e634aee54de6fe8a35e2434ef7edd228d
Author: Minh Vu <[email protected]>
AuthorDate: Wed Oct 21 22:48:19 2026 +0200

    perf(table): build multi-part location paths in one allocation (#2249)
    
    * perf(table): build multi-part location paths in one allocation
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * perf(table): keep direct join for zero and one path component
    
    ---------
    
    Signed-off-by: Minh Vu <[email protected]>
---
 table/locations.go            | 25 ++++++++++++++++++++++++-
 table/locations_bench_test.go | 20 ++++++++++++++++++++
 table/locations_join_test.go  | 42 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/table/locations.go b/table/locations.go
index 74cb3dcfd..a051c291f 100644
--- a/table/locations.go
+++ b/table/locations.go
@@ -53,7 +53,30 @@ type simpleLocationProvider struct {
 // It trims trailing slashes from the base but does not otherwise clean,
 // encode, or decode the local path or URI.
 func joinLocationPath(location string, elem ...string) string {
-       return strings.TrimRight(location, "/") + "/" + strings.Join(elem, "/")
+       base := strings.TrimRight(location, "/")
+       // The common zero- and one-component cases already need just one
+       // allocation. Keep their direct concatenation path.
+       if len(elem) < 2 {
+               return base + "/" + strings.Join(elem, "/")
+       }
+
+       size := len(base) + len(elem)
+       for _, component := range elem {
+               size += len(component)
+       }
+
+       var b strings.Builder
+       b.Grow(size)
+       b.WriteString(base)
+       b.WriteByte('/')
+       for i, component := range elem {
+               if i > 0 {
+                       b.WriteByte('/')
+               }
+               b.WriteString(component)
+       }
+
+       return b.String()
 }
 
 func (slp *simpleLocationProvider) NewDataLocation(dataFileName string) string 
{
diff --git a/table/locations_bench_test.go b/table/locations_bench_test.go
index 7a3282164..e3ba717e2 100644
--- a/table/locations_bench_test.go
+++ b/table/locations_bench_test.go
@@ -29,3 +29,23 @@ func BenchmarkComputeHash(b *testing.B) {
                computeHashBenchmarkSink = computeHash(dataFileName)
        }
 }
+
+var locationPathBenchmarkSink string
+
+func BenchmarkJoinLocationPath(b *testing.B) {
+       for _, tc := range []struct {
+               name, location string
+               parts          []string
+       }{
+               {"one", "s3://bucket/table/data", 
[]string{"file-000-uuid.parquet"}},
+               {"two", "s3://bucket/table/data/", []string{"0001/0010/0011", 
"file-000-uuid.parquet"}},
+               {"three", "s3://bucket/table/data//", []string{"part=x", 
"sha-hash", "file-000-uuid.parquet"}},
+       } {
+               b.Run(tc.name, func(b *testing.B) {
+                       b.ReportAllocs()
+                       for b.Loop() {
+                               locationPathBenchmarkSink = 
joinLocationPath(tc.location, tc.parts...)
+                       }
+               })
+       }
+}
diff --git a/table/locations_join_test.go b/table/locations_join_test.go
new file mode 100644
index 000000000..6dadc5efc
--- /dev/null
+++ b/table/locations_join_test.go
@@ -0,0 +1,42 @@
+// 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 "testing"
+
+func TestJoinLocationPath(t *testing.T) {
+       for _, tc := range []struct {
+               name, location, want string
+               parts                []string
+       }{
+               {"one", "s3://bucket/table", "s3://bucket/table/file.parquet", 
[]string{"file.parquet"}},
+               {"two", "s3://bucket/table/", 
"s3://bucket/table/part=x/file.parquet", []string{"part=x", "file.parquet"}},
+               {"three", "s3://bucket/table//", 
"s3://bucket/table/part=x/hash/file.parquet", []string{"part=x", "hash", 
"file.parquet"}},
+               {"empty-base", "////", "/data.parquet", 
[]string{"data.parquet"}},
+               {"no-components", "s3://bucket/table", "s3://bucket/table/", 
nil},
+               {"empty-components", "table", "table//file/", []string{"", 
"file", ""}},
+               {"literal-components", "s3://bucket/a/../b//", 
"s3://bucket/a/../b/x%20/foo#?", []string{"x%20", "foo#?"}},
+               {"leading-slash-component", "table", "table//file.parquet", 
[]string{"/file.parquet"}},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       if got := joinLocationPath(tc.location, tc.parts...); 
got != tc.want {
+                               t.Errorf("joinLocationPath(%q, %q) = %q, want 
%q", tc.location, tc.parts, got, tc.want)
+                       }
+               })
+       }
+}

Reply via email to