You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by at...@apache.org on 2026/10/09 16:20:02 UTC
(iceberg-go) branch main updated: perf(transforms): avoid redundant integer truncate modulus (#2145)
This is an automated email from the ASF dual-hosted git repository.
laskoviymishka 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 85222f5c1 perf(transforms): avoid redundant integer truncate modulus (#2145)
85222f5c1 is described below
commit 85222f5c197a1eea13a8cdeceeb12be582c509f3
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Fri Oct 9 18:19:56 2026 +0200
perf(transforms): avoid redundant integer truncate modulus (#2145)
* perf(transforms): avoid redundant integer truncate modulus
* test(transforms): correct minimum integer truncate expectation
* test(transforms): strengthen integer truncate coverage
Signed-off-by: Minh Vu <vu...@gmail.com>
---
transforms.go | 12 +++++-
transforms_integer_bench_test.go | 83 ++++++++++++++++++++++++++++++++++++++++
transforms_test.go | 23 ++++++++++-
3 files changed, 115 insertions(+), 3 deletions(-)
diff --git a/transforms.go b/transforms.go
index 6c66797df..5fea782eb 100644
--- a/transforms.go
+++ b/transforms.go
@@ -742,8 +742,12 @@ func (t TruncateTransform) Transformer(src Type) (func(any) any, error) {
val := v.(int32)
width := int32(t.Width)
+ remainder := val % width
+ if remainder < 0 {
+ remainder += width
+ }
- return val - (((val % width) + width) % width)
+ return val - remainder
}, nil
case Int64Type:
return func(v any) any {
@@ -753,8 +757,12 @@ func (t TruncateTransform) Transformer(src Type) (func(any) any, error) {
val := v.(int64)
width := int64(t.Width)
+ remainder := val % width
+ if remainder < 0 {
+ remainder += width
+ }
- return val - (((val % width) + width) % width)
+ return val - remainder
}, nil
case StringType:
return func(v any) any {
diff --git a/transforms_integer_bench_test.go b/transforms_integer_bench_test.go
new file mode 100644
index 000000000..a8849dced
--- /dev/null
+++ b/transforms_integer_bench_test.go
@@ -0,0 +1,83 @@
+// 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_test
+
+import (
+ "math"
+ "testing"
+
+ "github.com/apache/iceberg-go"
+)
+
+var benchmarkIntegerTruncateResult any
+
+func BenchmarkTruncateTransformInteger(b *testing.B) {
+ tests := []struct {
+ name string
+ typ iceberg.Type
+ width int
+ values []any
+ }{
+ {
+ name: "int32/mixed",
+ typ: iceberg.PrimitiveTypes.Int32,
+ width: 97,
+ values: []any{
+ int32(-1000), int32(-98), int32(-1), int32(0),
+ int32(1), int32(96), int32(97), int32(1000),
+ },
+ },
+ {
+ name: "int64/mixed",
+ typ: iceberg.PrimitiveTypes.Int64,
+ width: 97,
+ values: []any{
+ int64(-1 << 40), int64(-1000), int64(-1), int64(0),
+ int64(1), int64(1000), int64(1 << 40),
+ },
+ },
+ {
+ name: "int32/width-max",
+ typ: iceberg.PrimitiveTypes.Int32,
+ width: math.MaxInt32,
+ values: []any{
+ int32(math.MinInt32), int32(-1), int32(0),
+ int32(math.MaxInt32 - 1), int32(math.MaxInt32),
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ b.Run(tt.name, func(b *testing.B) {
+ transformer, err := (iceberg.TruncateTransform{Width: tt.width}).Transformer(tt.typ)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ReportAllocs()
+ index := 0
+ for b.Loop() {
+ benchmarkIntegerTruncateResult = transformer(tt.values[index])
+ index++
+ if index == len(tt.values) {
+ index = 0
+ }
+ }
+ })
+ }
+}
diff --git a/transforms_test.go b/transforms_test.go
index 493a53441..2024c1ebb 100644
--- a/transforms_test.go
+++ b/transforms_test.go
@@ -1137,8 +1137,24 @@ func TestTruncateTransform(t *testing.T) {
}{
{10, iceberg.Int32Literal(1), iceberg.Int32Literal(0)},
{10, iceberg.Int32Literal(-1), iceberg.Int32Literal(-10)},
+ {10, iceberg.Int32Literal(-10), iceberg.Int32Literal(-10)},
+ {10, iceberg.Int32Literal(-11), iceberg.Int32Literal(-20)},
+ {1, iceberg.Int32Literal(math.MinInt32), iceberg.Int32Literal(math.MinInt32)},
+ {1 << 30, iceberg.Int32Literal((1 << 30) - 1), iceberg.Int32Literal(0)},
+ {(1 << 30) + 1, iceberg.Int32Literal((1 << 30) - 1), iceberg.Int32Literal(0)},
+ {math.MaxInt32, iceberg.Int32Literal(math.MaxInt32 - 1), iceberg.Int32Literal(0)},
+ {math.MaxInt32, iceberg.Int32Literal(math.MaxInt32), iceberg.Int32Literal(math.MaxInt32)},
+ {math.MaxInt32, iceberg.Int32Literal(-1), iceberg.Int32Literal(-math.MaxInt32)},
+ // The final subtraction retains signed integer wraparound at the minimum value.
+ {math.MaxInt32, iceberg.Int32Literal(math.MinInt32), iceberg.Int32Literal(2)},
{10, iceberg.Int64Literal(1), iceberg.Int64Literal(0)},
{10, iceberg.Int64Literal(-1), iceberg.Int64Literal(-10)},
+ {10, iceberg.Int64Literal(-10), iceberg.Int64Literal(-10)},
+ {10, iceberg.Int64Literal(-11), iceberg.Int64Literal(-20)},
+ {1, iceberg.Int64Literal(math.MinInt64), iceberg.Int64Literal(math.MinInt64)},
+ {10, iceberg.Int64Literal(math.MaxInt64), iceberg.Int64Literal(math.MaxInt64 - 7)},
+ {10, iceberg.Int64Literal(math.MinInt64), iceberg.Int64Literal(math.MaxInt64 - 1)},
+ {math.MaxInt32, iceberg.Int64Literal(math.MaxInt64), iceberg.Int64Literal(math.MaxInt64 - 1)},
{50, iceberg.DecimalLiteral{
Val: decimal128.FromI64(1065),
Scale: 2,
@@ -1154,8 +1170,13 @@ func TestTruncateTransform(t *testing.T) {
}
for _, tt := range tests {
- t.Run(fmt.Sprintf("width=%d value=%v", tt.width, tt.value.Any()), func(t *testing.T) {
+ t.Run(fmt.Sprintf("type=%s/width=%d/value=%v", tt.value.Type(), tt.width, tt.value.Any()), func(t *testing.T) {
transform := iceberg.TruncateTransform{Width: tt.width}
+ transformer, err := transform.Transformer(tt.value.Type())
+ require.NoError(t, err)
+ assert.Equal(t, tt.expected.Any(), transformer(tt.value.Any()))
+ assert.Nil(t, transformer(nil))
+
result := transform.Apply(iceberg.Optional[iceberg.Literal]{Val: tt.value, Valid: true})
require.True(t, result.Valid)
assert.Equal(t, tt.expected, result.Val)