Skip to content

Commit db64b88

Browse files
committed
Optimize writing RLE runs in parquet column descriptors
Use information about nullability of Blocks to write RLE runs for repetition and definition levels more efficiently in parquet writer BenchmarkParquetFormat#write UNCOMPRESSED Before After LINEITEM 293.0MB/s ± 2869.6kB/s (0.96%) 312.9MB/s ± 2869.2kB/s (0.90%) (N = 10, α = 99.9%) MAP_VARCHAR_DOUBLE 345.4MB/s ± 3275.7kB/s (0.93%) 359.6MB/s ± 5555.4kB/s (1.51%) (N = 10, α = 99.9%) LARGE_MAP_VARCHAR_DOUBLE 402.0MB/s ± 6815.6kB/s (1.66%) 448.6MB/s ± 4808.3kB/s (1.05%) (N = 10, α = 99.9%) MAP_INT_DOUBLE 606.2MB/s ± 2136.1kB/s (0.34%) 676.1MB/s ± 5620.8kB/s (0.81%) (N = 10, α = 99.9%) LARGE_ARRAY_VARCHAR 257.8MB/s ± 9303.4kB/s (3.52%) 275.7MB/s ± 2583.1kB/s (0.91%) (N = 10, α = 99.9%)
1 parent 2cb0023 commit db64b88

12 files changed

+247
-149
lines changed

lib/trino-parquet/src/main/java/io/trino/parquet/writer/repdef/DefLevelWriterProviders.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ public ValuesCount writeDefinitionLevels(int positionsCount)
8484
checkValidPosition(offset, positionsCount, block.getPositionCount());
8585
int nonNullsCount = 0;
8686
if (!block.mayHaveNull()) {
87-
for (int position = offset; position < offset + positionsCount; position++) {
88-
encoder.writeInteger(maxDefinitionLevel);
89-
}
87+
encoder.writeRepeatInteger(maxDefinitionLevel, positionsCount);
9088
nonNullsCount = positionsCount;
9189
}
9290
else {

lib/trino-parquet/src/main/java/io/trino/parquet/writer/repdef/RepLevelWriterProviders.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@ public void writeRepetitionLevels(int parentLevel)
8080
public void writeRepetitionLevels(int parentLevel, int positionsCount)
8181
{
8282
checkValidPosition(offset, positionsCount, block.getPositionCount());
83-
for (int i = 0; i < positionsCount; i++) {
84-
encoder.writeInteger(parentLevel);
85-
}
83+
encoder.writeRepeatInteger(parentLevel, positionsCount);
8684
offset += positionsCount;
8785
}
8886
};

lib/trino-parquet/src/main/java/io/trino/parquet/writer/valuewriter/ColumnDescriptorValuesWriter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public interface ColumnDescriptorValuesWriter
2929
*/
3030
void writeInteger(int value);
3131

32+
/**
33+
* @param value the value to encode
34+
* @param valueRepetitions number of times the input value is repeated in the input stream
35+
*/
36+
void writeRepeatInteger(int value, int valueRepetitions);
37+
3238
/**
3339
* used to decide if we want to work to the next page
3440
*

lib/trino-parquet/src/main/java/io/trino/parquet/writer/valuewriter/DevNullValuesWriter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public void reset() {}
3636
@Override
3737
public void writeInteger(int v) {}
3838

39+
@Override
40+
public void writeRepeatInteger(int value, int valueRepetitions) {}
41+
3942
@Override
4043
public BytesInput getBytes()
4144
{

lib/trino-parquet/src/main/java/io/trino/parquet/writer/valuewriter/RunLengthBitPackingHybridEncoder.java

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,42 +133,69 @@ private void reset(boolean resetBaos)
133133
public void writeInt(int value)
134134
throws IOException
135135
{
136-
if (value == previousValue) {
137-
// keep track of how many times we've seen this value
138-
// consecutively
139-
++repeatCount;
136+
writeRepeatedInteger(value, 1);
137+
}
140138

141-
if (repeatCount >= 8) {
142-
// we've seen this at least 8 times, we're
143-
// certainly going to write an rle-run,
144-
// so just keep on counting repeats for now
145-
return;
146-
}
139+
public void writeRepeatedInteger(int value, int valueRepetitions)
140+
throws IOException
141+
{
142+
if (valueRepetitions == 0) {
143+
return;
147144
}
148-
else {
149-
// This is a new value, check if it signals the end of
150-
// an rle-run
145+
// Process 1st occurrence of new value
146+
if (value != previousValue) {
147+
// This is a new value, check if it signals the end of an rle-run
151148
if (repeatCount >= 8) {
152149
// it does! write an rle-run
153150
writeRleRun();
154151
}
155152

156153
// this is a new value so we've only seen it once
157154
repeatCount = 1;
155+
valueRepetitions--;
158156
// start tracking this value for repeats
159157
previousValue = value;
158+
159+
bufferedValues[numBufferedValues++] = value;
160+
if (numBufferedValues == 8) {
161+
// we've encountered less than 8 repeated values, so
162+
// either start a new bit-packed-run or append to the
163+
// current bit-packed-run
164+
writeOrAppendBitPackedRun();
165+
// we're going to see this value at least 8 times, so
166+
// just count remaining repeats for an rle-run
167+
if (valueRepetitions >= 8) {
168+
repeatCount = valueRepetitions;
169+
return;
170+
}
171+
}
160172
}
161173

162-
// We have not seen enough repeats to justify an rle-run yet,
163-
// so buffer this value in case we decide to write a bit-packed-run
164-
bufferedValues[numBufferedValues] = value;
165-
++numBufferedValues;
174+
// Process remaining repetitions of value
175+
while (valueRepetitions > 0) {
176+
repeatCount++;
177+
valueRepetitions--;
178+
if (repeatCount >= 8) {
179+
// we've seen this at least 8 times, we're
180+
// certainly going to write an rle-run,
181+
// so just keep on counting repeats for now
182+
repeatCount += valueRepetitions;
183+
return;
184+
}
166185

167-
if (numBufferedValues == 8) {
168-
// we've encountered less than 8 repeated values, so
169-
// either start a new bit-packed-run or append to the
170-
// current bit-packed-run
171-
writeOrAppendBitPackedRun();
186+
bufferedValues[numBufferedValues++] = value;
187+
if (numBufferedValues == 8) {
188+
// we've encountered less than 8 repeated values, so
189+
// either start a new bit-packed-run or append to the
190+
// current bit-packed-run
191+
writeOrAppendBitPackedRun();
192+
if (valueRepetitions >= 8) {
193+
// we're going to see this value at least 8 times, so
194+
// just count remaining repeats for an rle-run
195+
repeatCount = valueRepetitions;
196+
return;
197+
}
198+
}
172199
}
173200
}
174201

lib/trino-parquet/src/main/java/io/trino/parquet/writer/valuewriter/RunLengthBitPackingHybridValuesWriter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ public void writeInteger(int value)
4343
}
4444
}
4545

46+
@Override
47+
public void writeRepeatInteger(int value, int valueRepetitions)
48+
{
49+
try {
50+
encoder.writeRepeatedInteger(value, valueRepetitions);
51+
}
52+
catch (IOException e) {
53+
throw new ParquetEncodingException(e);
54+
}
55+
}
56+
4657
@Override
4758
public long getBufferedSize()
4859
{

lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestData.java

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,49 @@ public final class TestData
3838
{
3939
private TestData() {}
4040

41+
public enum UnsignedIntsGenerator
42+
{
43+
RANDOM {
44+
@Override
45+
public int[] getData(int size, int bitWidth)
46+
{
47+
Random random = new Random((long) size * bitWidth);
48+
int[] values = new int[size];
49+
for (int i = 0; i < size; i++) {
50+
values[i] = randomUnsignedInt(random, bitWidth);
51+
}
52+
return values;
53+
}
54+
},
55+
MIXED_AND_GROUPS_SMALL {
56+
@Override
57+
public int[] getData(int size, int bitWidth)
58+
{
59+
Random random = new Random((long) size * bitWidth);
60+
return generateMixedData(random, size, 13, bitWidth);
61+
}
62+
},
63+
MIXED_AND_GROUPS_LARGE {
64+
@Override
65+
public int[] getData(int size, int bitWidth)
66+
{
67+
Random random = new Random((long) size * bitWidth);
68+
return generateMixedData(random, size, 67, bitWidth);
69+
}
70+
},
71+
MIXED_AND_GROUPS_HUGE {
72+
@Override
73+
public int[] getData(int size, int bitWidth)
74+
{
75+
Random random = new Random((long) size * bitWidth);
76+
return generateMixedData(random, size, 997, bitWidth);
77+
}
78+
},
79+
/**/;
80+
81+
public abstract int[] getData(int size, int bitWidth);
82+
}
83+
4184
// Based on org.apache.parquet.schema.Types.BasePrimitiveBuilder.maxPrecision to determine the max decimal precision supported by INT32/INT64
4285
public static int maxPrecision(int numBytes)
4386
{
@@ -98,29 +141,6 @@ public static boolean[] generateMixedData(Random r, int size, int maxGroupSize)
98141
return result;
99142
}
100143

101-
public static int[] generateMixedData(Random r, int size, int maxGroupSize, int bitWidth)
102-
{
103-
IntList mixedList = new IntArrayList();
104-
while (mixedList.size() < size) {
105-
boolean isGroup = r.nextBoolean();
106-
int groupSize = r.nextInt(maxGroupSize);
107-
if (isGroup) {
108-
int value = randomInt(r, bitWidth);
109-
for (int i = 0; i < groupSize; i++) {
110-
mixedList.add(value);
111-
}
112-
}
113-
else {
114-
for (int i = 0; i < groupSize; i++) {
115-
mixedList.add(randomInt(r, bitWidth));
116-
}
117-
}
118-
}
119-
int[] result = new int[size];
120-
mixedList.getElements(0, result, 0, size);
121-
return result;
122-
}
123-
124144
public static Slice randomBigInteger(Random r)
125145
{
126146
BigInteger bigInteger = new BigInteger(126, r);
@@ -203,6 +223,29 @@ public static byte[][] randomAsciiData(int size, int minLength, int maxLength)
203223
return data;
204224
}
205225

226+
private static int[] generateMixedData(Random r, int size, int maxGroupSize, int bitWidth)
227+
{
228+
IntList mixedList = new IntArrayList();
229+
while (mixedList.size() < size) {
230+
boolean isGroup = r.nextBoolean();
231+
int groupSize = r.nextInt(maxGroupSize);
232+
if (isGroup) {
233+
int value = randomUnsignedInt(r, bitWidth);
234+
for (int i = 0; i < groupSize; i++) {
235+
mixedList.add(value);
236+
}
237+
}
238+
else {
239+
for (int i = 0; i < groupSize; i++) {
240+
mixedList.add(randomUnsignedInt(r, bitWidth));
241+
}
242+
}
243+
}
244+
int[] result = new int[size];
245+
mixedList.getElements(0, result, 0, size);
246+
return result;
247+
}
248+
206249
private static int propagateSignBit(int value, int bitsToPad)
207250
{
208251
return value << bitsToPad >> bitsToPad;

lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/BenchmarkRleBitPackingDecoder.java

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@
3434

3535
import java.io.IOException;
3636
import java.io.UncheckedIOException;
37-
import java.util.Random;
3837

3938
import static io.trino.jmh.Benchmarks.benchmark;
40-
import static io.trino.parquet.reader.TestData.generateMixedData;
41-
import static io.trino.parquet.reader.TestData.randomUnsignedInt;
39+
import static io.trino.parquet.reader.TestData.UnsignedIntsGenerator;
4240
import static java.lang.Math.min;
4341
import static java.util.concurrent.TimeUnit.MILLISECONDS;
4442
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -57,7 +55,7 @@ public class BenchmarkRleBitPackingDecoder
5755
private int[] output;
5856

5957
@Param
60-
public DataSet dataSet;
58+
public UnsignedIntsGenerator dataSet;
6159

6260
@Param({
6361
// This encoding is not meant to store big numbers so 2^20 is enough
@@ -67,49 +65,6 @@ public class BenchmarkRleBitPackingDecoder
6765
})
6866
public int bitWidth;
6967

70-
public enum DataSet
71-
{
72-
RANDOM {
73-
@Override
74-
int[] getData(int size, int bitWidth)
75-
{
76-
Random random = new Random((long) size * bitWidth);
77-
int[] values = new int[size];
78-
for (int i = 0; i < size; i++) {
79-
values[i] = randomUnsignedInt(random, bitWidth);
80-
}
81-
return values;
82-
}
83-
},
84-
MIXED_AND_GROUPS_SMALL {
85-
@Override
86-
int[] getData(int size, int bitWidth)
87-
{
88-
Random random = new Random((long) size * bitWidth);
89-
return generateMixedData(random, size, 23, bitWidth);
90-
}
91-
},
92-
MIXED_AND_GROUPS_LARGE {
93-
@Override
94-
int[] getData(int size, int bitWidth)
95-
{
96-
Random random = new Random((long) size * bitWidth);
97-
return generateMixedData(random, size, 127, bitWidth);
98-
}
99-
},
100-
MIXED_AND_GROUPS_HUGE {
101-
@Override
102-
int[] getData(int size, int bitWidth)
103-
{
104-
Random random = new Random((long) size * bitWidth);
105-
return generateMixedData(random, size, 2111, bitWidth);
106-
}
107-
},
108-
/**/;
109-
110-
abstract int[] getData(int size, int bitWidth);
111-
}
112-
11368
@Setup
11469
public void setup()
11570
throws IOException

lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestRleBitPackingDecoderBenchmark.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717

1818
import java.io.IOException;
1919

20+
import static io.trino.parquet.reader.TestData.UnsignedIntsGenerator;
21+
2022
public class TestRleBitPackingDecoderBenchmark
2123
{
2224
@Test
2325
public void testRleBitPackingDecoderBenchmark()
2426
throws IOException
2527
{
2628
for (int bitWidth = 1; bitWidth <= 20; bitWidth++) {
27-
for (BenchmarkRleBitPackingDecoder.DataSet dataSet : BenchmarkRleBitPackingDecoder.DataSet.values()) {
29+
for (UnsignedIntsGenerator dataSet : UnsignedIntsGenerator.values()) {
2830
BenchmarkRleBitPackingDecoder benchmark = new BenchmarkRleBitPackingDecoder();
2931
benchmark.bitWidth = bitWidth;
3032
benchmark.dataSet = dataSet;

0 commit comments

Comments
 (0)