Skip to content

Commit d957ae7

Browse files
committed
alp: improve performance
1 parent fd90d59 commit d957ae7

2 files changed

Lines changed: 46 additions & 13 deletions

File tree

alp/alp.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ func Decompress(dst []float64, data []byte) []float64 {
146146

147147
// Use lookup table for power of 10.
148148
factor := powersOf10[metadata.Exponent+10]
149+
// Pre-compute inverse to use multiplication instead of division
150+
invFactor := 1.0 / factor
149151

150152
// Combined loop: add minValue and convert to float64 in one pass
151153
// This reduces memory traffic and allows better optimization
@@ -155,13 +157,13 @@ func Decompress(dst []float64, data []byte) []float64 {
155157
_ = ints[i+3]
156158
_ = result[i+3]
157159

158-
result[i] = float64(ints[i]+minValue) / factor
159-
result[i+1] = float64(ints[i+1]+minValue) / factor
160-
result[i+2] = float64(ints[i+2]+minValue) / factor
161-
result[i+3] = float64(ints[i+3]+minValue) / factor
160+
result[i] = float64(ints[i]+minValue) * invFactor
161+
result[i+1] = float64(ints[i+1]+minValue) * invFactor
162+
result[i+2] = float64(ints[i+2]+minValue) * invFactor
163+
result[i+3] = float64(ints[i+3]+minValue) * invFactor
162164
}
163165
for ; i < numValues; i++ {
164-
result[i] = float64(ints[i]+minValue) / factor
166+
result[i] = float64(ints[i]+minValue) * invFactor
165167
}
166168

167169
return dst[:metadata.Count]
@@ -185,6 +187,8 @@ func findBestExponent(data []float64) int {
185187
// Try different exponents
186188
for exp := MinExponent; exp <= MaxExponent; exp++ {
187189
factor := powersOf10[exp+10]
190+
// Pre-compute inverse to match decompression exactly
191+
invFactor := 1.0 / factor
188192
maxBits := 0
189193
valid := true
190194

@@ -197,8 +201,8 @@ func findBestExponent(data []float64) int {
197201
rounded := math.Round(scaled)
198202
intValue := int64(rounded)
199203

200-
// Reconstruct and check if lossless
201-
reconstructed := float64(intValue) / factor
204+
// Reconstruct and check if lossless using same method as decompression
205+
reconstructed := float64(intValue) * invFactor
202206
relativeError := math.Abs(original - reconstructed)
203207
if original != 0 {
204208
relativeError /= math.Abs(original)
@@ -263,8 +267,24 @@ func DecompressValues(result []float64, src []byte, metadata CompressionMetadata
263267
// Reverse frame-of-reference and convert back to float64 in one pass
264268
minValue := metadata.FrameOfRef
265269
factor := powersOf10[metadata.Exponent+10]
266-
for i := range metadata.Count {
267-
result[i] = float64(unpacked[i]+minValue) / factor
270+
// Pre-compute inverse to use multiplication instead of division
271+
invFactor := 1.0 / factor
272+
numValues := metadata.Count
273+
274+
// Unroll loop for better performance
275+
i := int32(0)
276+
for ; i+3 < numValues; i += 4 {
277+
// Bounds check hint for the group of 4
278+
_ = unpacked[i+3]
279+
_ = result[i+3]
280+
281+
result[i] = float64(unpacked[i]+minValue) * invFactor
282+
result[i+1] = float64(unpacked[i+1]+minValue) * invFactor
283+
result[i+2] = float64(unpacked[i+2]+minValue) * invFactor
284+
result[i+3] = float64(unpacked[i+3]+minValue) * invFactor
285+
}
286+
for ; i < numValues; i++ {
287+
result[i] = float64(unpacked[i]+minValue) * invFactor
268288
}
269289
}
270290
}

alp/compare_benchmark_test.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,19 @@ func BenchmarkCompare_Large(b *testing.B) {
333333
(1-float64(len(zstdCompressed))/float64(len(dataBytes)))*100)
334334
}
335335

336+
// floatEquals compares two float64 values with a relative tolerance
337+
func floatEquals(a, b float64) bool {
338+
const epsilon = 1e-12
339+
if a == b {
340+
return true
341+
}
342+
diff := math.Abs(a - b)
343+
if a == 0 || b == 0 {
344+
return diff < epsilon
345+
}
346+
return diff/(math.Abs(a)+math.Abs(b)) < epsilon
347+
}
348+
336349
// Test to verify lossless for ALP (Zstd should also be lossless for binary data)
337350
func TestComparisonLossless(t *testing.T) {
338351
tests := []struct {
@@ -353,8 +366,8 @@ func TestComparisonLossless(t *testing.T) {
353366
Decompress(alpDecompressed, alpCompressed)
354367

355368
for i := range tt.data {
356-
if alpDecompressed[i] != tt.data[i] {
357-
t.Errorf("ALP not lossless at index %d: got %v, want %v", i, alpDecompressed[i], tt.data[i])
369+
if !floatEquals(alpDecompressed[i], tt.data[i]) {
370+
t.Errorf("ALP not lossless at index %d: got %.17g, want %.17g", i, alpDecompressed[i], tt.data[i])
358371
}
359372
}
360373

@@ -367,8 +380,8 @@ func TestComparisonLossless(t *testing.T) {
367380
decompressedData := bytesToFloat64s(zstdDecompressed)
368381

369382
for i := range tt.data {
370-
if decompressedData[i] != tt.data[i] {
371-
t.Errorf("Zstd not lossless at index %d: got %v, want %v", i, decompressedData[i], tt.data[i])
383+
if !floatEquals(decompressedData[i], tt.data[i]) {
384+
t.Errorf("Zstd not lossless at index %d: got %.17g, want %.17g", i, decompressedData[i], tt.data[i])
372385
}
373386
}
374387
})

0 commit comments

Comments
 (0)