Skip to content

Commit 90a8f48

Browse files
committed
[Performance] Avoid bitmap copies for aligned tablet prefixes
1 parent a6ac4cb commit 90a8f48

3 files changed

Lines changed: 242 additions & 3 deletions

File tree

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -970,9 +970,11 @@ private static boolean containsFailedStatus(TSStatus[] results, int start, int l
970970
return false;
971971
}
972972

973-
private static boolean containsMarkedBit(BitMap bitMap, int start, int length) {
974-
if (length <= 0) {
975-
return false;
973+
static boolean containsMarkedBit(BitMap bitMap, int start, int length) {
974+
// Avoid materializing a byte-array copy on the common aligned-tablet path, which starts at
975+
// offset 0 and can be inspected directly by BitMap.
976+
if (start == 0) {
977+
return length > 0 && !bitMap.isAllUnmarked(length);
976978
}
977979

978980
byte[] bytes = bitMap.getByteArray();
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.db.utils.datastructure;
21+
22+
import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
23+
import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
24+
import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
25+
26+
import org.apache.tsfile.utils.BitMap;
27+
import org.junit.Assert;
28+
import org.junit.Assume;
29+
import org.junit.Test;
30+
31+
import java.util.Locale;
32+
33+
public class AlignedBitmapRangeCheckPerformanceTest {
34+
35+
private static final String ENABLED_PROPERTY = "iotdb.aligned.bitmap.range-check.perf.enabled";
36+
private static final String ITERATIONS_PROPERTY =
37+
"iotdb.aligned.bitmap.range-check.perf.iterations";
38+
private static final String ROUNDS_PROPERTY = "iotdb.aligned.bitmap.range-check.perf.rounds";
39+
private static final int REPETITIONS = 2048;
40+
private static final int BITMAP_COUNT = 64;
41+
private static final int BITMAP_MASK = BITMAP_COUNT - 1;
42+
43+
private static volatile long benchmarkBlackhole;
44+
45+
@Test
46+
public void bitmapRangeCheckBenchmark() {
47+
Assume.assumeTrue(
48+
String.format(
49+
Locale.ROOT,
50+
"Manual performance UT. Enable with -D%s=true; optionally tune -D%s and -D%s.",
51+
ENABLED_PROPERTY,
52+
ITERATIONS_PROPERTY,
53+
ROUNDS_PROPERTY),
54+
Boolean.getBoolean(ENABLED_PROPERTY));
55+
Assume.assumeTrue(
56+
"Current-thread CPU time and allocation metrics are required.",
57+
ManualPerformanceTestUtils.enableThreadMetrics());
58+
59+
int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 4000);
60+
int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
61+
Assert.assertTrue(iterations > 0);
62+
Assert.assertTrue(rounds > 0);
63+
64+
runScenario("prefix", createBitMaps(), 0, Long.SIZE, iterations, rounds);
65+
runScenario("partial", createBitMaps(), 1, Long.SIZE - 1, iterations, rounds);
66+
}
67+
68+
private static void runScenario(
69+
String label, BitMap[] bitMaps, int start, int length, int iterations, int rounds) {
70+
int operations = iterations * REPETITIONS;
71+
runLegacy(bitMaps, start, length, REPETITIONS);
72+
runOptimized(bitMaps, start, length, REPETITIONS);
73+
74+
Measurement[] legacyMeasurements = new Measurement[rounds];
75+
Measurement[] optimizedMeasurements = new Measurement[rounds];
76+
for (int i = 0; i < rounds; i++) {
77+
if ((i & 1) == 0) {
78+
legacyMeasurements[i] = measureLegacy(bitMaps, start, length, operations);
79+
optimizedMeasurements[i] = measureOptimized(bitMaps, start, length, operations);
80+
} else {
81+
optimizedMeasurements[i] = measureOptimized(bitMaps, start, length, operations);
82+
legacyMeasurements[i] = measureLegacy(bitMaps, start, length, operations);
83+
}
84+
}
85+
86+
Summary legacySummary = ManualPerformanceTestUtils.summarize(legacyMeasurements, operations);
87+
Summary optimizedSummary =
88+
ManualPerformanceTestUtils.summarize(optimizedMeasurements, operations);
89+
printResult(label, start, length, operations, rounds, legacySummary, optimizedSummary);
90+
}
91+
92+
private static Measurement measureLegacy(
93+
BitMap[] bitMaps, int start, int length, int operations) {
94+
return ManualPerformanceTestUtils.measure(
95+
1, () -> runLegacy(bitMaps, start, length, operations));
96+
}
97+
98+
private static Measurement measureOptimized(
99+
BitMap[] bitMaps, int start, int length, int operations) {
100+
return ManualPerformanceTestUtils.measure(
101+
1, () -> runOptimized(bitMaps, start, length, operations));
102+
}
103+
104+
private static void runLegacy(BitMap[] bitMaps, int start, int length, int operations) {
105+
long markedCount = 0;
106+
for (int i = 0; i < operations; i++) {
107+
if (legacyContainsMarkedBit(bitMaps[i & BITMAP_MASK], start, length)) {
108+
markedCount++;
109+
}
110+
}
111+
benchmarkBlackhole = markedCount;
112+
}
113+
114+
private static void runOptimized(BitMap[] bitMaps, int start, int length, int operations) {
115+
long markedCount = 0;
116+
for (int i = 0; i < operations; i++) {
117+
if (AlignedTVList.containsMarkedBit(bitMaps[i & BITMAP_MASK], start, length)) {
118+
markedCount++;
119+
}
120+
}
121+
benchmarkBlackhole = markedCount;
122+
}
123+
124+
private static boolean legacyContainsMarkedBit(BitMap bitMap, int start, int length) {
125+
byte[] bytes = bitMap.getByteArray();
126+
int end = start + length - 1;
127+
int firstByteIndex = start >>> 3;
128+
int lastByteIndex = end >>> 3;
129+
if (firstByteIndex == lastByteIndex) {
130+
int mask = (0xFF << (start & 7)) & (0xFF >>> (7 - (end & 7)));
131+
return (bytes[firstByteIndex] & mask) != 0;
132+
}
133+
if ((bytes[firstByteIndex] & (0xFF << (start & 7))) != 0) {
134+
return true;
135+
}
136+
for (int i = firstByteIndex + 1; i < lastByteIndex; i++) {
137+
if (bytes[i] != 0) {
138+
return true;
139+
}
140+
}
141+
return (bytes[lastByteIndex] & (0xFF >>> (7 - (end & 7)))) != 0;
142+
}
143+
144+
private static BitMap[] createBitMaps() {
145+
BitMap[] bitMaps = new BitMap[BITMAP_COUNT];
146+
for (int i = 0; i < BITMAP_COUNT; i++) {
147+
bitMaps[i] = BitMap.createBitMapDynamically(Long.SIZE);
148+
if ((i & 1) != 0) {
149+
bitMaps[i].mark(Long.SIZE - 1);
150+
}
151+
}
152+
return bitMaps;
153+
}
154+
155+
private static void printResult(
156+
String label,
157+
int start,
158+
int length,
159+
int operations,
160+
int rounds,
161+
Summary legacySummary,
162+
Summary optimizedSummary) {
163+
System.out.printf(
164+
Locale.ROOT,
165+
"Aligned bitmap range-check benchmark (%s): start=%d, length=%d, operations/round=%d, rounds=%d%n",
166+
label,
167+
start,
168+
length,
169+
operations,
170+
rounds);
171+
printSummary("legacy", legacySummary);
172+
printSummary("optimized", optimizedSummary);
173+
System.out.printf(
174+
Locale.ROOT,
175+
" optimized/legacy CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
176+
percentage(
177+
optimizedSummary.getCpuNanosPerOperation(), legacySummary.getCpuNanosPerOperation()),
178+
percentage(
179+
optimizedSummary.getAllocatedBytesPerOperation(),
180+
legacySummary.getAllocatedBytesPerOperation()));
181+
System.out.printf(
182+
Locale.ROOT,
183+
" optimized-legacy CPU delta=%+.3f ns/check, allocation delta=%+.1f bytes/check%n",
184+
optimizedSummary.getCpuNanosPerOperation() - legacySummary.getCpuNanosPerOperation(),
185+
optimizedSummary.getAllocatedBytesPerOperation()
186+
- legacySummary.getAllocatedBytesPerOperation());
187+
}
188+
189+
private static void printSummary(String label, Summary summary) {
190+
System.out.printf(
191+
Locale.ROOT,
192+
" %-10s CPU=%.3f ns/check, allocated=%.1f bytes/check%n",
193+
label,
194+
summary.getCpuNanosPerOperation(),
195+
summary.getAllocatedBytesPerOperation());
196+
}
197+
198+
private static double percentage(double numerator, double denominator) {
199+
return denominator == 0 ? 0 : numerator * 100.0 / denominator;
200+
}
201+
}

iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,42 @@ public void testEmptyInputBitmapsDoNotMaterializeMemTableBitmaps() {
244244
Assert.assertNull(tvList.getBitMaps());
245245
}
246246

247+
@Test
248+
public void testContainsMarkedBitForLongAndArrayBackedBitmaps() {
249+
assertContainsMarkedBitRanges(new BitMap(Long.SIZE));
250+
assertContainsMarkedBitRanges(BitMap.createBitMapDynamically(Long.SIZE));
251+
252+
BitMap largeBitMap = BitMap.createBitMapDynamically(Long.SIZE * 2 + 2);
253+
Assert.assertFalse(AlignedTVList.containsMarkedBit(largeBitMap, 1, Long.SIZE));
254+
largeBitMap.mark(Long.SIZE);
255+
Assert.assertTrue(AlignedTVList.containsMarkedBit(largeBitMap, 1, Long.SIZE));
256+
Assert.assertFalse(AlignedTVList.containsMarkedBit(largeBitMap, Long.SIZE + 1, Long.SIZE));
257+
largeBitMap.mark(Long.SIZE * 2 + 1);
258+
Assert.assertTrue(AlignedTVList.containsMarkedBit(largeBitMap, Long.SIZE + 1, Long.SIZE + 1));
259+
}
260+
261+
private static void assertContainsMarkedBitRanges(BitMap bitMap) {
262+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 0, 0));
263+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 0, Long.SIZE));
264+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 1, Long.SIZE - 1));
265+
266+
bitMap.mark(0);
267+
Assert.assertTrue(AlignedTVList.containsMarkedBit(bitMap, 0, 1));
268+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 1, Long.SIZE - 1));
269+
270+
bitMap.reset();
271+
bitMap.mark(Long.SIZE / 2);
272+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 0, Long.SIZE / 2));
273+
Assert.assertTrue(AlignedTVList.containsMarkedBit(bitMap, 0, Long.SIZE / 2 + 1));
274+
Assert.assertTrue(AlignedTVList.containsMarkedBit(bitMap, Long.SIZE / 2 - 1, 3));
275+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, Long.SIZE / 2 + 1, 1));
276+
277+
bitMap.reset();
278+
bitMap.mark(Long.SIZE - 1);
279+
Assert.assertFalse(AlignedTVList.containsMarkedBit(bitMap, 1, Long.SIZE - 2));
280+
Assert.assertTrue(AlignedTVList.containsMarkedBit(bitMap, 1, Long.SIZE - 1));
281+
}
282+
247283
@Test
248284
public void testPrimitiveArraysAreAllocatedOnFirstWrite() {
249285
AlignedTVList tvList =

0 commit comments

Comments
 (0)