Skip to content

Commit e4d5beb

Browse files
authored
Merge pull request #1 from MichaHoffmann/mhoffmann/make-alp-faster
alp: improve performance
2 parents fd90d59 + c02c4ba commit e4d5beb

2 files changed

Lines changed: 43 additions & 15 deletions

File tree

alp/alp.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func Decompress(dst []float64, data []byte) []float64 {
145145
numValues := metadata.Count
146146

147147
// Use lookup table for power of 10.
148-
factor := powersOf10[metadata.Exponent+10]
148+
invFactor := powersOf10[(10-metadata.Exponent+21)%21]
149149

150150
// Combined loop: add minValue and convert to float64 in one pass
151151
// This reduces memory traffic and allows better optimization
@@ -155,13 +155,13 @@ func Decompress(dst []float64, data []byte) []float64 {
155155
_ = ints[i+3]
156156
_ = result[i+3]
157157

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
158+
result[i] = float64(ints[i]+minValue) * invFactor
159+
result[i+1] = float64(ints[i+1]+minValue) * invFactor
160+
result[i+2] = float64(ints[i+2]+minValue) * invFactor
161+
result[i+3] = float64(ints[i+3]+minValue) * invFactor
162162
}
163163
for ; i < numValues; i++ {
164-
result[i] = float64(ints[i]+minValue) / factor
164+
result[i] = float64(ints[i]+minValue) * invFactor
165165
}
166166

167167
return dst[:metadata.Count]
@@ -185,6 +185,7 @@ func findBestExponent(data []float64) int {
185185
// Try different exponents
186186
for exp := MinExponent; exp <= MaxExponent; exp++ {
187187
factor := powersOf10[exp+10]
188+
invFactor := powersOf10[(10-exp+21)%21]
188189
maxBits := 0
189190
valid := true
190191

@@ -197,8 +198,8 @@ func findBestExponent(data []float64) int {
197198
rounded := math.Round(scaled)
198199
intValue := int64(rounded)
199200

200-
// Reconstruct and check if lossless
201-
reconstructed := float64(intValue) / factor
201+
// Reconstruct and check if lossless using same method as decompression
202+
reconstructed := float64(intValue) * invFactor
202203
relativeError := math.Abs(original - reconstructed)
203204
if original != 0 {
204205
relativeError /= math.Abs(original)
@@ -262,9 +263,23 @@ func DecompressValues(result []float64, src []byte, metadata CompressionMetadata
262263

263264
// Reverse frame-of-reference and convert back to float64 in one pass
264265
minValue := metadata.FrameOfRef
265-
factor := powersOf10[metadata.Exponent+10]
266-
for i := range metadata.Count {
267-
result[i] = float64(unpacked[i]+minValue) / factor
266+
invFactor := powersOf10[(10-metadata.Exponent+21)%21]
267+
numValues := metadata.Count
268+
269+
// Unroll loop for better performance
270+
i := int32(0)
271+
for ; i+3 < numValues; i += 4 {
272+
// Bounds check hint for the group of 4
273+
_ = unpacked[i+3]
274+
_ = result[i+3]
275+
276+
result[i] = float64(unpacked[i]+minValue) * invFactor
277+
result[i+1] = float64(unpacked[i+1]+minValue) * invFactor
278+
result[i+2] = float64(unpacked[i+2]+minValue) * invFactor
279+
result[i+3] = float64(unpacked[i+3]+minValue) * invFactor
280+
}
281+
for ; i < numValues; i++ {
282+
result[i] = float64(unpacked[i]+minValue) * invFactor
268283
}
269284
}
270285
}

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)