This is an automated email from the ASF dual-hosted git repository.

jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 14ecc9f212a [Performance] Reduce allocations on aligned tablet writes 
(#18358)
14ecc9f212a is described below

commit 14ecc9f212a41a99b039659b2c6337f220e7254b
Author: Caideyipi <[email protected]>
AuthorDate: Thu Aug 6 10:31:01 2026 +0800

    [Performance] Reduce allocations on aligned tablet writes (#18358)
    
    * [Performance] Avoid bitmap copies for aligned tablet prefixes
    
    * [Performance] Defer bitmap allocation for sparse aligned writes
    
    * [Performance] Avoid device collection for single-device aligned tablets
---
 .../node/write/RelationalInsertTabletNode.java     |   4 +
 .../dataregion/memtable/TsFileProcessor.java       |  38 ++-
 .../db/utils/datastructure/AlignedTVList.java      | 208 +++++---------
 ...ignedBitmapMemoryAccountingPerformanceTest.java | 214 +++++++++++++-
 .../dataregion/memtable/TsFileProcessorTest.java   |  84 ++++--
 .../AlignedBitmapRangeCheckPerformanceTest.java    | 201 +++++++++++++
 .../AlignedSparseWritePerformanceTest.java         | 314 +++++++++++++++++++++
 .../db/utils/datastructure/AlignedTVListTest.java  | 109 ++++++-
 pom.xml                                            |   2 +-
 9 files changed, 989 insertions(+), 185 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
index d4c373e57d7..029c55499bf 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
@@ -116,6 +116,10 @@ public class RelationalInsertTabletNode extends 
InsertTabletNode {
     this.singleDevice = true;
   }
 
+  public boolean isSingleDevice() {
+    return singleDevice;
+  }
+
   public List<Binary[]> getObjectColumns() {
     List<Binary[]> objectColumns = new ArrayList<>();
     for (int i = 0;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
index ee9b39d2ffe..759b58d4966 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
@@ -48,6 +48,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNod
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalDeleteDataNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
 import org.apache.iotdb.db.schemaengine.schemaregion.utils.ResourceByPathUtils;
 import org.apache.iotdb.db.service.metrics.WritingMetrics;
 import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
@@ -648,19 +649,8 @@ public class TsFileProcessor {
 
     ensureMemTable(infoForMetrics);
     workMemTable.checkDataType(insertTabletNode);
-    Set<IDeviceID> alignedDeviceIds = new HashSet<>();
-    if (insertTabletNode.isAligned()) {
-      for (int[] range : rangeList) {
-        for (Pair<IDeviceID, Integer> deviceEndPosition :
-            insertTabletNode.splitByDevice(range[0], range[1])) {
-          alignedDeviceIds.add(deviceEndPosition.getLeft());
-        }
-      }
-    }
     AlignedTVListRamCostSnapshot alignedRamCostSnapshot =
-        alignedDeviceIds.isEmpty()
-            ? null
-            : new AlignedTVListRamCostSnapshot(workMemTable, alignedDeviceIds);
+        takeAlignedTVListRamCostSnapshot(workMemTable, insertTabletNode, 
rangeList);
 
     long[] memIncrements =
         scheduleMemoryBlock(insertTabletNode, rangeList, results, 
infoForMetrics);
@@ -1394,6 +1384,30 @@ public class TsFileProcessor {
     }
   }
 
+  static AlignedTVListRamCostSnapshot takeAlignedTVListRamCostSnapshot(
+      IMemTable memTable, InsertTabletNode insertTabletNode, List<int[]> 
rangeList) {
+    if (!insertTabletNode.isAligned() || rangeList.isEmpty()) {
+      return null;
+    }
+
+    if (!(insertTabletNode instanceof RelationalInsertTabletNode)
+        || ((RelationalInsertTabletNode) insertTabletNode).isSingleDevice()) {
+      return new AlignedTVListRamCostSnapshot(
+          memTable, insertTabletNode.getDeviceID(rangeList.get(0)[0]));
+    }
+
+    Set<IDeviceID> alignedDeviceIds = new HashSet<>();
+    for (int[] range : rangeList) {
+      for (Pair<IDeviceID, Integer> deviceEndPosition :
+          insertTabletNode.splitByDevice(range[0], range[1])) {
+        alignedDeviceIds.add(deviceEndPosition.getLeft());
+      }
+    }
+    return alignedDeviceIds.isEmpty()
+        ? null
+        : new AlignedTVListRamCostSnapshot(memTable, alignedDeviceIds);
+  }
+
   static final class AlignedTVListRamCostSnapshot {
 
     private final IMemTable memTable;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
index 1e29d245756..035b9a89bb9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
@@ -87,7 +87,8 @@ public abstract class AlignedTVList extends TVList {
   private long materializedBitmapMemoryCost;
   private long arrayMemCostWithoutPrimitiveArraysAndIndex;
 
-  // Data type list -> list of TVList, add 1 when expanded -> primitive array 
of basic type
+  // Data type list -> list of TVList, add 1 when expanded -> primitive array 
of basic type.
+  // A null primitive array means all existing rows in that block are null for 
the column.
   // Index relation: columnIndex(dataTypeIndex) -> arrayIndex -> elementIndex
   protected List<List<Object>> values;
 
@@ -261,7 +262,7 @@ public abstract class AlignedTVList extends TVList {
         markNullValue(i, arrayIndex, elementIndex);
         continue;
       }
-      Object valueArray = getOrCreateValueArray(i, arrayIndex);
+      Object valueArray = getOrCreateValueArray(i, arrayIndex, elementIndex);
       switch (dataTypes.get(i)) {
         case TEXT:
         case BLOB:
@@ -406,37 +407,13 @@ public abstract class AlignedTVList extends TVList {
   }
 
   public void extendColumn(TSDataType dataType) {
-    if (bitMaps == null) {
-      List<List<BitMap>> localBitMaps = new ArrayList<>(values.size());
-      for (int i = 0; i < values.size(); i++) {
-        localBitMaps.add(null);
-      }
-      bitMaps = localBitMaps;
-    }
     List<Object> columnValue = new ArrayList<>(timestamps.size());
-    List<BitMap> columnBitMaps = new ArrayList<>(timestamps.size());
     for (int i = 0; i < timestamps.size(); i++) {
       columnValue.add(null);
-      BitMap bitMap = BitMap.createBitMapDynamically(ARRAY_SIZE);
-      // The following code is for these 2 kinds of scenarios.
-
-      // Eg1: If rowCount=5 and ARRAY_SIZE=2, we need to supply 3 bitmaps for 
the extending column.
-      // The first 2 bitmaps should mark all bits to represent 4 nulls and the 
3rd bitmap should
-      // mark
-      // the 1st bit to represent 1 null value.
-
-      // Eg2: If rowCount=4 and ARRAY_SIZE=2, we need to supply 2 bitmaps for 
the extending column.
-      // These 2 bitmaps should mark all bits to represent 4 nulls.
-      if (i == timestamps.size() - 1 && rowCount % ARRAY_SIZE != 0) {
-        bitMap.markRange(0, rowCount % ARRAY_SIZE);
-      } else {
-        bitMap.markAll();
-      }
-      columnBitMaps.add(bitMap);
     }
-    materializedBitmapMemoryCost +=
-        (long) timestamps.size() * (bitmapReferenceRamCost() + 
bitmapRamCost());
-    this.bitMaps.add(columnBitMaps);
+    if (bitMaps != null) {
+      bitMaps.add(null);
+    }
     this.values.add(columnValue);
     this.dataTypes.add(dataType);
     refreshArrayMemCostWithoutPrimitiveArrays();
@@ -745,28 +722,14 @@ public abstract class AlignedTVList extends TVList {
    * or delete wrong rows.
    */
   public synchronized void deleteColumn(int columnIndex) {
-    if (bitMaps == null) {
-      List<List<BitMap>> localBitMaps = new ArrayList<>(dataTypes.size());
-      for (int j = 0; j < dataTypes.size(); j++) {
-        localBitMaps.add(null);
-      }
-      bitMaps = localBitMaps;
-    }
-    if (bitMaps.get(columnIndex) == null) {
-      List<BitMap> columnBitMaps = new 
ArrayList<>(values.get(columnIndex).size());
-      for (int i = 0; i < values.get(columnIndex).size(); i++) {
-        columnBitMaps.add(BitMap.createBitMapDynamically(ARRAY_SIZE));
-      }
-      bitMaps.set(columnIndex, columnBitMaps);
-      materializedBitmapMemoryCost +=
-          (long) columnBitMaps.size() * (bitmapReferenceRamCost() + 
bitmapRamCost());
+    List<Object> columnValues = values.get(columnIndex);
+    if (columnValues == null) {
+      return;
     }
-    for (int i = 0; i < bitMaps.get(columnIndex).size(); i++) {
-      if (bitMaps.get(columnIndex).get(i) == null) {
-        bitMaps.get(columnIndex).set(i, 
BitMap.createBitMapDynamically(ARRAY_SIZE));
-        materializedBitmapMemoryCost += bitmapRamCost();
+    for (int arrayIndex = 0; arrayIndex < columnValues.size(); arrayIndex++) {
+      if (columnValues.get(arrayIndex) != null) {
+        getBitMap(columnIndex, arrayIndex).markAll();
       }
-      bitMaps.get(columnIndex).get(i).markAll();
     }
   }
 
@@ -951,7 +914,7 @@ public abstract class AlignedTVList extends TVList {
   }
 
   private void markNullBitmapRange(
-      Object[] values,
+      Object[] inputValues,
       BitMap[] bitMaps,
       TSStatus[] results,
       int idx,
@@ -965,15 +928,21 @@ public abstract class AlignedTVList extends TVList {
             ? buildResultBitMap(results, idx, elementIdx, len)
             : null;
 
-    for (int j = 0; j < values.length; j++) {
+    for (int j = 0; j < inputValues.length; j++) {
+      // A null value array represents an entirely null block, so no bitmap is 
needed until the
+      // block receives its first non-null value.
+      if (values.get(j).get(arrayIndex) == null) {
+        continue;
+      }
+
       /* Fast-path: column is entirely null */
-      if (values[j] == null) {
+      if (inputValues[j] == null) {
         getBitMap(j, arrayIndex).markRange(elementIdx, len);
         continue;
       }
 
       /* 2.mask the column bitmap */
-      if (bitMaps != null && bitMaps[j] != null && 
containsMarkedBit(bitMaps[j], idx, len)) {
+      if (bitMaps != null && bitMaps[j] != null && 
bitMaps[j].isRangeAnyMarked(idx, len)) {
         getBitMap(j, arrayIndex).merge(bitMaps[j], idx, elementIdx, len);
       }
 
@@ -993,31 +962,6 @@ public abstract class AlignedTVList extends TVList {
     return false;
   }
 
-  private static boolean containsMarkedBit(BitMap bitMap, int start, int 
length) {
-    if (length <= 0) {
-      return false;
-    }
-
-    byte[] bytes = bitMap.getByteArray();
-    int end = start + length - 1;
-    int firstByteIndex = start >>> 3;
-    int lastByteIndex = end >>> 3;
-    if (firstByteIndex == lastByteIndex) {
-      int mask = (0xFF << (start & 7)) & (0xFF >>> (7 - (end & 7)));
-      return (bytes[firstByteIndex] & mask) != 0;
-    }
-
-    if ((bytes[firstByteIndex] & (0xFF << (start & 7))) != 0) {
-      return true;
-    }
-    for (int i = firstByteIndex + 1; i < lastByteIndex; i++) {
-      if (bytes[i] != 0) {
-        return true;
-      }
-    }
-    return (bytes[lastByteIndex] & (0xFF >>> (7 - (end & 7)))) != 0;
-  }
-
   public static byte[] buildResultBitMapBytes(
       TSStatus[] results, int idx, int elementIdx, int length) {
     int totalBits = (elementIdx & 7) + length;
@@ -1055,7 +999,7 @@ public abstract class AlignedTVList extends TVList {
       if (value[i] == null || !containsNonNullValue(bitMaps, results, i, idx, 
remaining)) {
         continue;
       }
-      Object valueArray = getOrCreateValueArray(i, arrayIndex);
+      Object valueArray = getOrCreateValueArray(i, arrayIndex, elementIndex);
       switch (dataTypes.get(i)) {
         case TEXT:
         case BLOB:
@@ -1101,7 +1045,7 @@ public abstract class AlignedTVList extends TVList {
   private static boolean containsNonNullValue(
       BitMap[] bitMaps, TSStatus[] results, int columnIndex, int start, int 
length) {
     BitMap bitMap = bitMaps == null ? null : bitMaps[columnIndex];
-    boolean containsNull = bitMap != null && containsMarkedBit(bitMap, start, 
length);
+    boolean containsNull = bitMap != null && bitMap.isRangeAnyMarked(start, 
length);
     boolean containsFailure = results != null && containsFailedStatus(results, 
start, length);
     if (!containsNull && !containsFailure) {
       return true;
@@ -1119,7 +1063,7 @@ public abstract class AlignedTVList extends TVList {
     return false;
   }
 
-  private Object getOrCreateValueArray(int columnIndex, int arrayIndex) {
+  private Object getOrCreateValueArray(int columnIndex, int arrayIndex, int 
elementIndex) {
     List<Object> columnValues = values.get(columnIndex);
     Object valueArray = columnValues.get(arrayIndex);
     if (valueArray == null) {
@@ -1127,6 +1071,9 @@ public abstract class AlignedTVList extends TVList {
       columnValues.set(arrayIndex, valueArray);
       materializedValueArrayCounts[columnIndex]++;
       materializedValueArrayMemCost += 
valueListArrayMemCost(dataTypes.get(columnIndex));
+      if (elementIndex > 0) {
+        getBitMap(columnIndex, arrayIndex).markRange(0, elementIndex);
+      }
     }
     return valueArray;
   }
@@ -1161,6 +1108,10 @@ public abstract class AlignedTVList extends TVList {
   }
 
   private boolean markNullValue(int columnIndex, int arrayIndex, int 
elementIndex) {
+    List<Object> columnValues = values.get(columnIndex);
+    if (columnValues == null || columnValues.get(arrayIndex) == null) {
+      return false;
+    }
     // mark the null value in the current bitmap
     BitMap bitMap = getBitMap(columnIndex, arrayIndex);
     if (bitMap.isMarked(elementIndex)) {
@@ -1802,60 +1753,55 @@ public abstract class AlignedTVList extends TVList {
   }
 
   public BitMap getAllValueColDeletedMap(int rowCount) {
-    // row exists when any column value exists
-    if (bitMaps == null) {
+    if (rowCount <= 0) {
       return null;
     }
+
+    int blockCount = (rowCount + ARRAY_SIZE - 1) / ARRAY_SIZE;
+    // Preserve the dense fast path: one column without null blocks or bitmaps 
proves that every row
+    // has at least one value.
     for (int columnIndex = 0; columnIndex < values.size(); columnIndex++) {
-      if (values.get(columnIndex) != null && bitMaps.get(columnIndex) == null) 
{
+      List<Object> columnValues = values.get(columnIndex);
+      if (columnValues == null) {
+        continue;
+      }
+      List<BitMap> columnBitMaps = bitMaps == null ? null : 
bitMaps.get(columnIndex);
+      boolean columnHasNoNull = true;
+      for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
+        if (columnValues.get(blockIndex) == null
+            || (columnBitMaps != null && columnBitMaps.get(blockIndex) != 
null)) {
+          columnHasNoNull = false;
+          break;
+        }
+      }
+      if (columnHasNoNull) {
         return null;
       }
     }
 
-    byte[] rowBitsArr = new byte[rowCount / Byte.SIZE + 1];
-    int bitsMapSize =
-        rowCount % ARRAY_SIZE == 0 ? rowCount / ARRAY_SIZE : rowCount / 
ARRAY_SIZE + 1;
-    boolean[] allNotNullArray = new boolean[bitsMapSize];
+    byte[] rowBitsArr = new byte[(rowCount + Byte.SIZE - 1) / Byte.SIZE];
     Arrays.fill(rowBitsArr, (byte) 0xFF);
-    for (int columnIndex = 0; columnIndex < values.size(); columnIndex++) {
-      List<BitMap> columnBitMaps = bitMaps.get(columnIndex);
-      if (columnBitMaps == null) {
-        Arrays.fill(rowBitsArr, (byte) 0x00);
-        break;
-      } else if (values.get(columnIndex) != null) {
-        int row = 0;
-        boolean isEnd = true;
-        for (int i = 0; i < bitsMapSize; i++) {
-          if (allNotNullArray[i]) {
-            row += ARRAY_SIZE;
-            continue;
-          }
-
-          BitMap bitMap = columnBitMaps.get(i);
-          int index = row / Byte.SIZE;
-          int size = ((Math.min((rowCount - row), ARRAY_SIZE)) + 7) >>> 3;
-          row += ARRAY_SIZE;
-
-          if (bitMap == null) {
-            Arrays.fill(rowBitsArr, index, index + size, (byte) 0x00);
-            allNotNullArray[i] = true;
-            continue;
-          }
-
-          byte bits = (byte) 0X00;
-          byte[] bitMapBytes = bitMap.getByteArray();
-          for (int j = 0; j < size; j++) {
-            rowBitsArr[index] &= bitMapBytes[j];
-            bits |= rowBitsArr[index++];
-            isEnd = false;
-          }
-
-          allNotNullArray[i] = bits == (byte) 0;
+    for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
+      int row = blockIndex * ARRAY_SIZE;
+      int byteIndex = row / Byte.SIZE;
+      int byteCount = (Math.min(rowCount - row, ARRAY_SIZE) + Byte.SIZE - 1) / 
Byte.SIZE;
+      for (int columnIndex = 0; columnIndex < values.size(); columnIndex++) {
+        List<Object> columnValues = values.get(columnIndex);
+        if (columnValues == null || columnValues.get(blockIndex) == null) {
+          continue;
         }
 
-        if (isEnd) {
+        List<BitMap> columnBitMaps = bitMaps == null ? null : 
bitMaps.get(columnIndex);
+        BitMap bitMap = columnBitMaps == null ? null : 
columnBitMaps.get(blockIndex);
+        if (bitMap == null) {
+          Arrays.fill(rowBitsArr, byteIndex, byteIndex + byteCount, (byte) 
0x00);
           break;
         }
+
+        byte[] bitMapBytes = bitMap.getByteArray();
+        for (int i = 0; i < byteCount; i++) {
+          rowBitsArr[byteIndex + i] &= bitMapBytes[i];
+        }
       }
     }
     return new BitMap(rowCount, rowBitsArr);
@@ -1884,19 +1830,9 @@ public abstract class AlignedTVList extends TVList {
 
   private int getColumnValueCnt(int columnIndex) {
     int pointNum = 0;
-    if (bitMaps == null || bitMaps.get(columnIndex) == null) {
-      pointNum = rowCount;
-    } else {
-      for (int i = 0; i < rowCount; i++) {
-        int arrayIndex = i / ARRAY_SIZE;
-        if (bitMaps.get(columnIndex).get(arrayIndex) == null) {
-          pointNum++;
-        } else {
-          int elementIndex = i % ARRAY_SIZE;
-          if 
(!bitMaps.get(columnIndex).get(arrayIndex).isMarked(elementIndex)) {
-            pointNum++;
-          }
-        }
+    for (int i = 0; i < rowCount; i++) {
+      if (!isNullValue(i, columnIndex)) {
+        pointNum++;
       }
     }
     return pointNum;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
index 8a872f6af61..b18b543a2d5 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
@@ -19,6 +19,10 @@
 
 package org.apache.iotdb.db.storageengine.dataregion.memtable;
 
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
 import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
 import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
 import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
@@ -26,6 +30,7 @@ import 
org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.utils.BitMap;
+import org.apache.tsfile.utils.Pair;
 import org.apache.tsfile.write.schema.IMeasurementSchema;
 import org.apache.tsfile.write.schema.MeasurementSchema;
 import org.junit.Assert;
@@ -33,9 +38,12 @@ import org.junit.Assume;
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 
 public class AlignedBitmapMemoryAccountingPerformanceTest {
 
@@ -49,10 +57,12 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
   private static final String ROUNDS_PROPERTY = 
"iotdb.aligned.bitmap.accounting.perf.rounds";
   private static final int RECONCILIATION_REPETITIONS = 1024;
 
+  private static final TsFileProcessor.AlignedTVListRamCostSnapshot[] 
SNAPSHOT_BLACKHOLE =
+      new 
TsFileProcessor.AlignedTVListRamCostSnapshot[RECONCILIATION_REPETITIONS];
   private static volatile long benchmarkBlackhole;
 
   @Test
-  public void alignedTabletBitmapAccountingBenchmark() {
+  public void alignedTabletBitmapAccountingBenchmark() throws 
IllegalPathException {
     Assume.assumeTrue(
         String.format(
             "Manual performance UT. Enable with -D%s=true, optionally tune 
-D%s, -D%s, -D%s, -D%s and -D%s.",
@@ -78,12 +88,11 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
     Assert.assertTrue(iterations > 0);
     Assert.assertTrue(rounds > 0);
 
-    runScenario(
-        "dense",
-        createScenario(columnCount, rowCount, false),
-        warmupIterations,
-        iterations,
-        rounds);
+    Scenario denseScenario = createScenario(columnCount, rowCount, false);
+    Summary denseWriteSummary =
+        runScenario("dense", denseScenario, warmupIterations, iterations, 
rounds);
+    runSingleDeviceSnapshotScenario(
+        denseScenario, warmupIterations, iterations, rounds, 
denseWriteSummary);
     runScenario(
         "null-heavy",
         createScenario(columnCount, rowCount, true),
@@ -92,7 +101,7 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
         rounds);
   }
 
-  private static void runScenario(
+  private static Summary runScenario(
       String label, Scenario scenario, int warmupIterations, int iterations, 
int rounds) {
     runReconciliation(
         createAccountingTarget(scenario), warmupIterations * 
RECONCILIATION_REPETITIONS);
@@ -116,6 +125,107 @@ public class AlignedBitmapMemoryAccountingPerformanceTest 
{
     Summary writeSummary = 
ManualPerformanceTestUtils.summarize(writeMeasurements, iterations);
     printResult(
         label, scenario, warmupIterations, iterations, rounds, 
reconciliationSummary, writeSummary);
+    return writeSummary;
+  }
+
+  private static void runSingleDeviceSnapshotScenario(
+      Scenario scenario, int warmupIterations, int iterations, int rounds, 
Summary writeSummary)
+      throws IllegalPathException {
+    SnapshotTarget target = createSnapshotTarget(scenario);
+    int warmupOperations = Math.multiplyExact(warmupIterations, 
RECONCILIATION_REPETITIONS);
+    int operations = Math.multiplyExact(iterations, 
RECONCILIATION_REPETITIONS);
+
+    TsFileProcessor.AlignedTVListRamCostSnapshot legacySnapshot =
+        takeLegacyAlignedTVListRamCostSnapshot(
+            target.memTable, target.insertTabletNode, target.rangeList);
+    TsFileProcessor.AlignedTVListRamCostSnapshot optimizedSnapshot =
+        TsFileProcessor.takeAlignedTVListRamCostSnapshot(
+            target.memTable, target.insertTabletNode, target.rangeList);
+    Assert.assertNotNull(legacySnapshot);
+    Assert.assertNotNull(optimizedSnapshot);
+    Assert.assertEquals(
+        legacySnapshot.getMemoryCorrection(0), 
optimizedSnapshot.getMemoryCorrection(0));
+
+    runLegacySnapshotLifecycle(target, warmupOperations);
+    runOptimizedSnapshotLifecycle(target, warmupOperations);
+
+    Measurement[] legacyMeasurements = new Measurement[rounds];
+    Measurement[] optimizedMeasurements = new Measurement[rounds];
+    for (int i = 0; i < rounds; i++) {
+      if ((i & 1) == 0) {
+        legacyMeasurements[i] = measureLegacySnapshotLifecycle(target, 
operations);
+        optimizedMeasurements[i] = measureOptimizedSnapshotLifecycle(target, 
operations);
+      } else {
+        optimizedMeasurements[i] = measureOptimizedSnapshotLifecycle(target, 
operations);
+        legacyMeasurements[i] = measureLegacySnapshotLifecycle(target, 
operations);
+      }
+    }
+
+    Summary legacySummary = 
ManualPerformanceTestUtils.summarize(legacyMeasurements, operations);
+    Summary optimizedSummary =
+        ManualPerformanceTestUtils.summarize(optimizedMeasurements, 
operations);
+    printSnapshotResult(
+        scenario,
+        warmupOperations,
+        operations,
+        rounds,
+        legacySummary,
+        optimizedSummary,
+        writeSummary);
+  }
+
+  private static Measurement measureLegacySnapshotLifecycle(SnapshotTarget 
target, int operations) {
+    Arrays.fill(SNAPSHOT_BLACKHOLE, null);
+    return ManualPerformanceTestUtils.measure(
+        1, () -> runLegacySnapshotLifecycle(target, operations));
+  }
+
+  private static Measurement measureOptimizedSnapshotLifecycle(
+      SnapshotTarget target, int operations) {
+    Arrays.fill(SNAPSHOT_BLACKHOLE, null);
+    return ManualPerformanceTestUtils.measure(
+        1, () -> runOptimizedSnapshotLifecycle(target, operations));
+  }
+
+  private static void runLegacySnapshotLifecycle(SnapshotTarget target, int 
operations) {
+    long correction = 0;
+    for (int i = 0; i < operations; i++) {
+      TsFileProcessor.AlignedTVListRamCostSnapshot snapshot =
+          takeLegacyAlignedTVListRamCostSnapshot(
+              target.memTable, target.insertTabletNode, target.rangeList);
+      correction += snapshot.getMemoryCorrection(0);
+      SNAPSHOT_BLACKHOLE[i % SNAPSHOT_BLACKHOLE.length] = snapshot;
+    }
+    benchmarkBlackhole = correction + operations;
+  }
+
+  private static void runOptimizedSnapshotLifecycle(SnapshotTarget target, int 
operations) {
+    long correction = 0;
+    for (int i = 0; i < operations; i++) {
+      TsFileProcessor.AlignedTVListRamCostSnapshot snapshot =
+          TsFileProcessor.takeAlignedTVListRamCostSnapshot(
+              target.memTable, target.insertTabletNode, target.rangeList);
+      correction += snapshot.getMemoryCorrection(0);
+      SNAPSHOT_BLACKHOLE[i % SNAPSHOT_BLACKHOLE.length] = snapshot;
+    }
+    benchmarkBlackhole = correction + operations;
+  }
+
+  private static TsFileProcessor.AlignedTVListRamCostSnapshot
+      takeLegacyAlignedTVListRamCostSnapshot(
+          IMemTable memTable, InsertTabletNode insertTabletNode, List<int[]> 
rangeList) {
+    Set<IDeviceID> alignedDeviceIds = new HashSet<>();
+    if (insertTabletNode.isAligned()) {
+      for (int[] range : rangeList) {
+        for (Pair<IDeviceID, Integer> deviceEndPosition :
+            insertTabletNode.splitByDevice(range[0], range[1])) {
+          alignedDeviceIds.add(deviceEndPosition.getLeft());
+        }
+      }
+    }
+    return alignedDeviceIds.isEmpty()
+        ? null
+        : new TsFileProcessor.AlignedTVListRamCostSnapshot(memTable, 
alignedDeviceIds);
   }
 
   private static Measurement measureReconciliation(Scenario scenario, int 
iterations) {
@@ -184,6 +294,26 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
     return new AccountingTarget(memTable, deviceId);
   }
 
+  private static SnapshotTarget createSnapshotTarget(Scenario scenario)
+      throws IllegalPathException {
+    AccountingTarget accountingTarget = createAccountingTarget(scenario);
+    InsertTabletNode insertTabletNode =
+        new InsertTabletNode(
+            new PlanNodeId("snapshot_perf"),
+            new PartialPath("root.accounting.d0"),
+            true,
+            scenario.measurements,
+            scenario.dataTypes,
+            scenario.times,
+            scenario.bitMaps,
+            scenario.columns,
+            scenario.times.length);
+    return new SnapshotTarget(
+        accountingTarget.memTable,
+        insertTabletNode,
+        Collections.singletonList(new int[] {0, scenario.times.length}));
+  }
+
   private static Scenario createScenario(int columnCount, int rowCount, 
boolean nullHeavy) {
     String[] measurements = new String[columnCount];
     TSDataType[] dataTypes = new TSDataType[columnCount];
@@ -243,6 +373,60 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
             writeSummary.getAllocatedBytesPerOperation()));
   }
 
+  private static void printSnapshotResult(
+      Scenario scenario,
+      int warmupOperations,
+      int operations,
+      int rounds,
+      Summary legacySummary,
+      Summary optimizedSummary,
+      Summary writeSummary) {
+    System.out.printf(
+        Locale.ROOT,
+        "Aligned single-device snapshot lifecycle benchmark (dense tree 
tablet): columns=%d, rows=%d, warmup operations=%d, operations/round=%d, 
rounds=%d%n",
+        scenario.measurements.length,
+        scenario.times.length,
+        warmupOperations,
+        operations,
+        rounds);
+    printSnapshotSummary("legacy", legacySummary);
+    printSnapshotSummary("optimized", optimizedSummary);
+    System.out.printf(
+        Locale.ROOT,
+        "  optimized/legacy CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
+        percentage(
+            optimizedSummary.getCpuNanosPerOperation(), 
legacySummary.getCpuNanosPerOperation()),
+        percentage(
+            optimizedSummary.getAllocatedBytesPerOperation(),
+            legacySummary.getAllocatedBytesPerOperation()));
+    System.out.printf(
+        Locale.ROOT,
+        "  optimized-legacy CPU delta=%+.3f ns/tablet, allocation delta=%+.1f 
bytes/tablet%n",
+        optimizedSummary.getCpuNanosPerOperation() - 
legacySummary.getCpuNanosPerOperation(),
+        optimizedSummary.getAllocatedBytesPerOperation()
+            - legacySummary.getAllocatedBytesPerOperation());
+    System.out.printf(
+        Locale.ROOT,
+        "  savings/dense-write CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
+        percentage(
+            legacySummary.getCpuNanosPerOperation() - 
optimizedSummary.getCpuNanosPerOperation(),
+            writeSummary.getCpuNanosPerOperation()),
+        percentage(
+            legacySummary.getAllocatedBytesPerOperation()
+                - optimizedSummary.getAllocatedBytesPerOperation(),
+            writeSummary.getAllocatedBytesPerOperation()));
+  }
+
+  private static void printSnapshotSummary(String label, Summary summary) {
+    System.out.printf(
+        Locale.ROOT,
+        "  %-10s CPU=%.3f ns/tablet, allocated=%.1f bytes/tablet, peak heap 
delta=%.3f MiB%n",
+        label,
+        summary.getCpuNanosPerOperation(),
+        summary.getAllocatedBytesPerOperation(),
+        summary.getPeakHeapDeltaBytes() / 1024.0 / 1024.0);
+  }
+
   private static void printSummary(String label, Summary summary) {
     System.out.printf(
         Locale.ROOT,
@@ -268,6 +452,20 @@ public class AlignedBitmapMemoryAccountingPerformanceTest {
     }
   }
 
+  private static final class SnapshotTarget {
+
+    private final IMemTable memTable;
+    private final InsertTabletNode insertTabletNode;
+    private final List<int[]> rangeList;
+
+    private SnapshotTarget(
+        IMemTable memTable, InsertTabletNode insertTabletNode, List<int[]> 
rangeList) {
+      this.memTable = memTable;
+      this.insertTabletNode = insertTabletNode;
+      this.rangeList = rangeList;
+    }
+  }
+
   private static final class Scenario {
 
     private final String[] measurements;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
index a8193a9c0b2..43832097c0e 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
@@ -39,6 +39,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNod
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertRowNode;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
 import org.apache.iotdb.db.storageengine.dataregion.DataRegionInfo;
 import org.apache.iotdb.db.storageengine.dataregion.DataRegionTest;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
@@ -65,6 +66,7 @@ import org.apache.tsfile.read.query.dataset.QueryDataSet;
 import org.apache.tsfile.read.reader.IPointReader;
 import org.apache.tsfile.utils.Binary;
 import org.apache.tsfile.utils.BitMap;
+import org.apache.tsfile.utils.Pair;
 import org.apache.tsfile.write.record.TSRecord;
 import org.apache.tsfile.write.record.datapoint.DataPoint;
 import org.apache.tsfile.write.schema.MeasurementSchema;
@@ -589,9 +591,7 @@ public class TsFileProcessorTest {
 
     Assert.assertEquals(
         expectedProcessor.getWorkMemTable().getTVListsRamCost()
-            - AlignedTVList.valueListArrayMemCost(dataType)
-            + 2 * AlignedTVList.bitmapReferenceRamCost()
-            + AlignedTVList.bitmapRamCost(),
+            - AlignedTVList.valueListArrayMemCost(dataType),
         actualProcessor.getWorkMemTable().getTVListsRamCost());
     Assert.assertEquals(
         TSStatusCode.OUT_OF_TTL.getStatusCode(), 
actualResults[failedIndex].getCode());
@@ -620,10 +620,7 @@ public class TsFileProcessorTest {
         AlignedTVList.alignedTvListArrayMemCost(
             new TSDataType[] {TSDataType.INT32, TSDataType.INT32}, null);
     Assert.assertEquals(
-        denseBlockCost
-            - AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
-            + 2 * AlignedTVList.bitmapReferenceRamCost()
-            + AlignedTVList.bitmapRamCost(),
+        denseBlockCost - AlignedTVList.valueListArrayMemCost(TSDataType.INT32),
         processor.getWorkMemTable().getTVListsRamCost() - 
ramCostBeforeNewBlock);
   }
 
@@ -649,10 +646,7 @@ public class TsFileProcessorTest {
         AlignedTVList.alignedTvListArrayMemCost(
             new TSDataType[] {TSDataType.INT32, TSDataType.INT32}, null);
     Assert.assertEquals(
-        denseBlockCost
-            - AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
-            + 2 * AlignedTVList.bitmapReferenceRamCost()
-            + AlignedTVList.bitmapRamCost(),
+        denseBlockCost - AlignedTVList.valueListArrayMemCost(TSDataType.INT32),
         processor.getWorkMemTable().getTVListsRamCost() - 
ramCostBeforeNewBlock);
   }
 
@@ -700,12 +694,67 @@ public class TsFileProcessorTest {
         
alignedMemChunk.getWorkingTVList().getValues().get(extendedColumnIndex).get(0));
     Assert.assertNull(
         
alignedMemChunk.getWorkingTVList().getValues().get(extendedColumnIndex).get(1));
-    for (BitMap bitMap : 
alignedMemChunk.getWorkingTVList().getBitMaps().get(extendedColumnIndex)) {
-      Assert.assertNotNull(bitMap);
-    }
+    List<BitMap> extendedColumnBitMaps =
+        
alignedMemChunk.getWorkingTVList().getBitMaps().get(extendedColumnIndex);
+    Assert.assertNull(extendedColumnBitMaps.get(0));
+    Assert.assertNull(extendedColumnBitMaps.get(1));
+    Assert.assertNotNull(extendedColumnBitMaps.get(2));
     assertAlignedTvListRamCostMatchesActual(deviceId);
   }
 
+  @Test
+  public void alignedTabletRamCostSnapshotUsesSingleDeviceFastPath() {
+    IMemTable memTable = new PrimitiveMemTable("root.snapshot", "0");
+    IDeviceID firstDevice = 
IDeviceID.Factory.DEFAULT_FACTORY.create("root.snapshot.d0");
+    IDeviceID secondDevice = 
IDeviceID.Factory.DEFAULT_FACTORY.create("root.snapshot.d1");
+    List<int[]> rangeList = Collections.singletonList(new int[] {0, 2});
+
+    int[] treeSplitCalls = {0};
+    InsertTabletNode treeNode =
+        new InsertTabletNode(new PlanNodeId("tree")) {
+          @Override
+          public IDeviceID getDeviceID(int rowIdx) {
+            return firstDevice;
+          }
+
+          @Override
+          public List<Pair<IDeviceID, Integer>> splitByDevice(int start, int 
end) {
+            treeSplitCalls[0]++;
+            return Collections.singletonList(new Pair<>(firstDevice, end));
+          }
+        };
+    treeNode.setAligned(true);
+
+    Assert.assertNotNull(
+        TsFileProcessor.takeAlignedTVListRamCostSnapshot(memTable, treeNode, 
rangeList));
+    Assert.assertEquals(0, treeSplitCalls[0]);
+
+    int[] relationalSplitCalls = {0};
+    RelationalInsertTabletNode relationalNode =
+        new RelationalInsertTabletNode(new PlanNodeId("table")) {
+          @Override
+          public IDeviceID getDeviceID(int rowIdx) {
+            return rowIdx == 0 ? firstDevice : secondDevice;
+          }
+
+          @Override
+          public List<Pair<IDeviceID, Integer>> splitByDevice(int start, int 
end) {
+            relationalSplitCalls[0]++;
+            return Arrays.asList(new Pair<>(firstDevice, 1), new 
Pair<>(secondDevice, end));
+          }
+        };
+    relationalNode.setAligned(true);
+
+    Assert.assertNotNull(
+        TsFileProcessor.takeAlignedTVListRamCostSnapshot(memTable, 
relationalNode, rangeList));
+    Assert.assertEquals(1, relationalSplitCalls[0]);
+
+    relationalNode.setSingleDevice();
+    Assert.assertNotNull(
+        TsFileProcessor.takeAlignedTVListRamCostSnapshot(memTable, 
relationalNode, rangeList));
+    Assert.assertEquals(1, relationalSplitCalls[0]);
+  }
+
   @Test
   public void alignedTvListRamCostTest2()
       throws MetadataException, WriteProcessException, IOException {
@@ -771,7 +820,8 @@ public class TsFileProcessorTest {
         new TSStatus[10],
         true,
         new long[5]);
-    Assert.assertEquals(5269104, memTable.getTVListsRamCost());
+    Assert.assertEquals(
+        5269104 - 3000L * AlignedTVList.bitmapRamCost(), 
memTable.getTVListsRamCost());
     processor.insertTablet(
         genInsertTableNodeFors3000ToS6000(300, true),
         Collections.singletonList(new int[] {0, 10}),
@@ -1069,9 +1119,7 @@ public class TsFileProcessorTest {
     long denseRowRamIncrement = memTable.getTVListsRamCost() - 
ramCostBeforeDenseRow;
 
     Assert.assertEquals(
-        AlignedTVList.valueListArrayMemCost(dataType)
-            - 2 * AlignedTVList.bitmapReferenceRamCost()
-            - AlignedTVList.bitmapRamCost(),
+        AlignedTVList.valueListArrayMemCost(dataType),
         denseRowRamIncrement - sparseRowRamIncrement);
   }
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedBitmapRangeCheckPerformanceTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedBitmapRangeCheckPerformanceTest.java
new file mode 100644
index 00000000000..7e58dff6e98
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedBitmapRangeCheckPerformanceTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 org.apache.iotdb.db.utils.datastructure;
+
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+
+import org.apache.tsfile.utils.BitMap;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.util.Locale;
+
+public class AlignedBitmapRangeCheckPerformanceTest {
+
+  private static final String ENABLED_PROPERTY = 
"iotdb.aligned.bitmap.range-check.perf.enabled";
+  private static final String ITERATIONS_PROPERTY =
+      "iotdb.aligned.bitmap.range-check.perf.iterations";
+  private static final String ROUNDS_PROPERTY = 
"iotdb.aligned.bitmap.range-check.perf.rounds";
+  private static final int REPETITIONS = 2048;
+  private static final int BITMAP_COUNT = 64;
+  private static final int BITMAP_MASK = BITMAP_COUNT - 1;
+
+  private static volatile long benchmarkBlackhole;
+
+  @Test
+  public void bitmapRangeCheckBenchmark() {
+    Assume.assumeTrue(
+        String.format(
+            Locale.ROOT,
+            "Manual performance UT. Enable with -D%s=true; optionally tune 
-D%s and -D%s.",
+            ENABLED_PROPERTY,
+            ITERATIONS_PROPERTY,
+            ROUNDS_PROPERTY),
+        Boolean.getBoolean(ENABLED_PROPERTY));
+    Assume.assumeTrue(
+        "Current-thread CPU time and allocation metrics are required.",
+        ManualPerformanceTestUtils.enableThreadMetrics());
+
+    int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 4000);
+    int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+    Assert.assertTrue(iterations > 0);
+    Assert.assertTrue(rounds > 0);
+
+    runScenario("prefix", createBitMaps(), 0, Long.SIZE, iterations, rounds);
+    runScenario("partial", createBitMaps(), 1, Long.SIZE - 1, iterations, 
rounds);
+  }
+
+  private static void runScenario(
+      String label, BitMap[] bitMaps, int start, int length, int iterations, 
int rounds) {
+    int operations = iterations * REPETITIONS;
+    runLegacy(bitMaps, start, length, REPETITIONS);
+    runOptimized(bitMaps, start, length, REPETITIONS);
+
+    Measurement[] legacyMeasurements = new Measurement[rounds];
+    Measurement[] optimizedMeasurements = new Measurement[rounds];
+    for (int i = 0; i < rounds; i++) {
+      if ((i & 1) == 0) {
+        legacyMeasurements[i] = measureLegacy(bitMaps, start, length, 
operations);
+        optimizedMeasurements[i] = measureOptimized(bitMaps, start, length, 
operations);
+      } else {
+        optimizedMeasurements[i] = measureOptimized(bitMaps, start, length, 
operations);
+        legacyMeasurements[i] = measureLegacy(bitMaps, start, length, 
operations);
+      }
+    }
+
+    Summary legacySummary = 
ManualPerformanceTestUtils.summarize(legacyMeasurements, operations);
+    Summary optimizedSummary =
+        ManualPerformanceTestUtils.summarize(optimizedMeasurements, 
operations);
+    printResult(label, start, length, operations, rounds, legacySummary, 
optimizedSummary);
+  }
+
+  private static Measurement measureLegacy(
+      BitMap[] bitMaps, int start, int length, int operations) {
+    return ManualPerformanceTestUtils.measure(
+        1, () -> runLegacy(bitMaps, start, length, operations));
+  }
+
+  private static Measurement measureOptimized(
+      BitMap[] bitMaps, int start, int length, int operations) {
+    return ManualPerformanceTestUtils.measure(
+        1, () -> runOptimized(bitMaps, start, length, operations));
+  }
+
+  private static void runLegacy(BitMap[] bitMaps, int start, int length, int 
operations) {
+    long markedCount = 0;
+    for (int i = 0; i < operations; i++) {
+      if (legacyContainsMarkedBit(bitMaps[i & BITMAP_MASK], start, length)) {
+        markedCount++;
+      }
+    }
+    benchmarkBlackhole = markedCount;
+  }
+
+  private static void runOptimized(BitMap[] bitMaps, int start, int length, 
int operations) {
+    long markedCount = 0;
+    for (int i = 0; i < operations; i++) {
+      if (bitMaps[i & BITMAP_MASK].isRangeAnyMarked(start, length)) {
+        markedCount++;
+      }
+    }
+    benchmarkBlackhole = markedCount;
+  }
+
+  private static boolean legacyContainsMarkedBit(BitMap bitMap, int start, int 
length) {
+    byte[] bytes = bitMap.getByteArray();
+    int end = start + length - 1;
+    int firstByteIndex = start >>> 3;
+    int lastByteIndex = end >>> 3;
+    if (firstByteIndex == lastByteIndex) {
+      int mask = (0xFF << (start & 7)) & (0xFF >>> (7 - (end & 7)));
+      return (bytes[firstByteIndex] & mask) != 0;
+    }
+    if ((bytes[firstByteIndex] & (0xFF << (start & 7))) != 0) {
+      return true;
+    }
+    for (int i = firstByteIndex + 1; i < lastByteIndex; i++) {
+      if (bytes[i] != 0) {
+        return true;
+      }
+    }
+    return (bytes[lastByteIndex] & (0xFF >>> (7 - (end & 7)))) != 0;
+  }
+
+  private static BitMap[] createBitMaps() {
+    BitMap[] bitMaps = new BitMap[BITMAP_COUNT];
+    for (int i = 0; i < BITMAP_COUNT; i++) {
+      bitMaps[i] = BitMap.createBitMapDynamically(Long.SIZE);
+      if ((i & 1) != 0) {
+        bitMaps[i].mark(Long.SIZE - 1);
+      }
+    }
+    return bitMaps;
+  }
+
+  private static void printResult(
+      String label,
+      int start,
+      int length,
+      int operations,
+      int rounds,
+      Summary legacySummary,
+      Summary optimizedSummary) {
+    System.out.printf(
+        Locale.ROOT,
+        "Aligned bitmap range-check benchmark (%s): start=%d, length=%d, 
operations/round=%d, rounds=%d%n",
+        label,
+        start,
+        length,
+        operations,
+        rounds);
+    printSummary("legacy", legacySummary);
+    printSummary("optimized", optimizedSummary);
+    System.out.printf(
+        Locale.ROOT,
+        "  optimized/legacy CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
+        percentage(
+            optimizedSummary.getCpuNanosPerOperation(), 
legacySummary.getCpuNanosPerOperation()),
+        percentage(
+            optimizedSummary.getAllocatedBytesPerOperation(),
+            legacySummary.getAllocatedBytesPerOperation()));
+    System.out.printf(
+        Locale.ROOT,
+        "  optimized-legacy CPU delta=%+.3f ns/check, allocation delta=%+.1f 
bytes/check%n",
+        optimizedSummary.getCpuNanosPerOperation() - 
legacySummary.getCpuNanosPerOperation(),
+        optimizedSummary.getAllocatedBytesPerOperation()
+            - legacySummary.getAllocatedBytesPerOperation());
+  }
+
+  private static void printSummary(String label, Summary summary) {
+    System.out.printf(
+        Locale.ROOT,
+        "  %-10s CPU=%.3f ns/check, allocated=%.1f bytes/check%n",
+        label,
+        summary.getCpuNanosPerOperation(),
+        summary.getAllocatedBytesPerOperation());
+  }
+
+  private static double percentage(double numerator, double denominator) {
+    return denominator == 0 ? 0 : numerator * 100.0 / denominator;
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedSparseWritePerformanceTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedSparseWritePerformanceTest.java
new file mode 100644
index 00000000000..8a759a6792b
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedSparseWritePerformanceTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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 org.apache.iotdb.db.utils.datastructure;
+
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static 
org.apache.iotdb.db.storageengine.rescon.memory.PrimitiveArrayManager.ARRAY_SIZE;
+
+public class AlignedSparseWritePerformanceTest {
+
+  private static final String PREFIX = "iotdb.aligned.sparse-write.perf.";
+  private static final String ENABLED_PROPERTY = PREFIX + "enabled";
+  private static final String COLUMNS_PROPERTY = PREFIX + "columns";
+  private static final String WRITTEN_COLUMNS_PROPERTY = PREFIX + 
"written-columns";
+  private static final String ROWS_PROPERTY = PREFIX + "rows";
+  private static final String HISTORICAL_ROWS_PROPERTY = PREFIX + 
"historical-rows";
+  private static final String WARMUPS_PROPERTY = PREFIX + "warmup.iterations";
+  private static final String ITERATIONS_PROPERTY = PREFIX + "iterations";
+  private static final String ROUNDS_PROPERTY = PREFIX + "rounds";
+
+  private static volatile long benchmarkBlackhole;
+
+  @Test
+  public void sparseAlignedWriteBenchmark() {
+    Assume.assumeTrue(
+        String.format(
+            Locale.ROOT,
+            "Manual performance UT. Enable with -D%s=true and tune properties 
under -D%s*.",
+            ENABLED_PROPERTY,
+            PREFIX),
+        Boolean.getBoolean(ENABLED_PROPERTY));
+    Assume.assumeTrue(
+        "Current-thread CPU time and allocation metrics are required.",
+        ManualPerformanceTestUtils.enableThreadMetrics());
+
+    int columns = Integer.getInteger(COLUMNS_PROPERTY, 64);
+    int writtenColumns = Integer.getInteger(WRITTEN_COLUMNS_PROPERTY, 1);
+    int rows = Integer.getInteger(ROWS_PROPERTY, ARRAY_SIZE);
+    int historicalRows = Integer.getInteger(HISTORICAL_ROWS_PROPERTY, 
ARRAY_SIZE * 1024);
+    int warmups = Integer.getInteger(WARMUPS_PROPERTY, 200);
+    int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 5000);
+    int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+    Assert.assertTrue(columns > 0);
+    Assert.assertTrue(writtenColumns > 0 && writtenColumns <= columns);
+    Assert.assertTrue(rows > 0);
+    Assert.assertTrue(historicalRows > 0);
+    Assert.assertTrue(warmups > 0);
+    Assert.assertTrue(iterations > 0);
+    Assert.assertTrue(rounds > 0);
+
+    List<TSDataType> dataTypes = createDataTypes(columns);
+    Summary dense =
+        runWriteScenario(
+            "dense",
+            dataTypes,
+            Scenario.batch(columns, columns, rows),
+            warmups,
+            iterations,
+            rounds);
+    Summary sparse =
+        runWriteScenario(
+            "sparse",
+            dataTypes,
+            Scenario.batch(columns, writtenColumns, rows),
+            warmups,
+            iterations,
+            rounds);
+    Summary allNull =
+        runWriteScenario(
+            "all-null", dataTypes, Scenario.batch(columns, 0, rows), warmups, 
iterations, rounds);
+    Summary nullPrefix =
+        runWriteScenario(
+            "null-prefix-first-non-null",
+            dataTypes,
+            Scenario.nullPrefix(columns, writtenColumns, rows),
+            warmups,
+            iterations,
+            rounds);
+    runExtensionScenario(historicalRows, warmups, iterations, rounds);
+
+    printComparison("sparse/dense", sparse, dense);
+    printComparison("all-null/dense", allNull, dense);
+    printComparison("null-prefix/sparse", nullPrefix, sparse);
+    printAvoidedBitmapAccounting(columns, writtenColumns, rows, 
historicalRows);
+  }
+
+  private static Summary runWriteScenario(
+      String label,
+      List<TSDataType> dataTypes,
+      Scenario scenario,
+      int warmups,
+      int iterations,
+      int rounds) {
+    runWrite(AlignedTVList.newAlignedList(new ArrayList<>(dataTypes)), 
scenario, warmups);
+    Measurement[] measurements = new Measurement[rounds];
+    for (int round = 0; round < rounds; round++) {
+      AlignedTVList target = AlignedTVList.newAlignedList(new 
ArrayList<>(dataTypes));
+      measurements[round] =
+          ManualPerformanceTestUtils.measure(1, () -> runWrite(target, 
scenario, iterations));
+    }
+    Summary summary = ManualPerformanceTestUtils.summarize(measurements, 
iterations);
+    System.out.printf(
+        Locale.ROOT,
+        "Aligned sparse-write benchmark (%s): columns=%d, written columns=%d, 
rows=%d, batches/round=%d, rounds=%d%n",
+        label,
+        dataTypes.size(),
+        scenario.writtenColumnCount,
+        scenario.rowCount,
+        iterations,
+        rounds);
+    printSummary(summary, "batch");
+    return summary;
+  }
+
+  private static void runWrite(AlignedTVList target, Scenario scenario, int 
iterations) {
+    for (int i = 0; i < iterations; i++) {
+      scenario.writeTo(target);
+    }
+    benchmarkBlackhole = target.getRamSize() + target.rowCount();
+  }
+
+  private static List<TSDataType> createDataTypes(int columnCount) {
+    List<TSDataType> dataTypes = new ArrayList<>(columnCount);
+    for (int column = 0; column < columnCount; column++) {
+      dataTypes.add(TSDataType.INT32);
+    }
+    return dataTypes;
+  }
+
+  private static long[] createTimes(int rowCount, int startTime) {
+    long[] times = new long[rowCount];
+    for (int row = 0; row < rowCount; row++) {
+      times[row] = startTime + row;
+    }
+    return times;
+  }
+
+  private static Object[] createColumns(int columnCount, int 
writtenColumnCount, int rowCount) {
+    Object[] columns = new Object[columnCount];
+    for (int column = 0; column < writtenColumnCount; column++) {
+      int[] columnValues = new int[rowCount];
+      for (int row = 0; row < rowCount; row++) {
+        columnValues[row] = row;
+      }
+      columns[column] = columnValues;
+    }
+    return columns;
+  }
+
+  private static void runExtensionScenario(
+      int historicalRows, int warmups, int iterations, int rounds) {
+    runExtensions(createHistoricalTarget(historicalRows), warmups);
+    Measurement[] measurements = new Measurement[rounds];
+    for (int round = 0; round < rounds; round++) {
+      AlignedTVList target = createHistoricalTarget(historicalRows);
+      measurements[round] =
+          ManualPerformanceTestUtils.measure(1, () -> runExtensions(target, 
iterations));
+    }
+    Summary summary = ManualPerformanceTestUtils.summarize(measurements, 
iterations);
+    System.out.printf(
+        Locale.ROOT,
+        "Aligned sparse-write benchmark (extend-column): historical rows=%d, 
historical blocks=%d, extensions/round=%d, rounds=%d%n",
+        historicalRows,
+        (historicalRows + ARRAY_SIZE - 1) / ARRAY_SIZE,
+        iterations,
+        rounds);
+    printSummary(summary, "column");
+  }
+
+  private static AlignedTVList createHistoricalTarget(int historicalRows) {
+    long[] times = new long[historicalRows];
+    int[] values = new int[historicalRows];
+    for (int row = 0; row < historicalRows; row++) {
+      times[row] = row;
+      values[row] = row;
+    }
+    AlignedTVList target = AlignedTVList.newAlignedList(new 
ArrayList<>(List.of(TSDataType.INT32)));
+    target.putAlignedValues(times, new Object[] {values}, null, 0, 
historicalRows, null);
+    return target;
+  }
+
+  private static void runExtensions(AlignedTVList target, int count) {
+    for (int i = 0; i < count; i++) {
+      target.extendColumn(TSDataType.INT32);
+    }
+    benchmarkBlackhole = target.getRamSize() + target.getTsDataTypes().size();
+  }
+
+  private static void printSummary(Summary summary, String operation) {
+    System.out.printf(
+        Locale.ROOT,
+        "  CPU=%.3f us/%s, allocated=%.1f bytes/%s, peak heap delta=%.3f 
MiB/round%n",
+        summary.getCpuNanosPerOperation() / 1_000.0,
+        operation,
+        summary.getAllocatedBytesPerOperation(),
+        operation,
+        summary.getPeakHeapDeltaBytes() / 1024.0 / 1024.0);
+    if (summary.getCpuNanosPerOperation() == 0) {
+      System.out.printf(
+          "  CPU sample is below the platform timer resolution; increase 
-D%s.%n",
+          ITERATIONS_PROPERTY);
+    }
+  }
+
+  private static void printComparison(String label, Summary numerator, Summary 
denominator) {
+    System.out.printf(
+        Locale.ROOT,
+        "  %s CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
+        label,
+        percentage(numerator.getCpuNanosPerOperation(), 
denominator.getCpuNanosPerOperation()),
+        percentage(
+            numerator.getAllocatedBytesPerOperation(),
+            denominator.getAllocatedBytesPerOperation()));
+  }
+
+  private static double percentage(double numerator, double denominator) {
+    return denominator == 0 ? 0 : numerator * 100.0 / denominator;
+  }
+
+  private static void printAvoidedBitmapAccounting(
+      int columns, int writtenColumns, int rows, int historicalRows) {
+    long bitmapCost = AlignedTVList.bitmapReferenceRamCost() + 
AlignedTVList.bitmapRamCost();
+    long blocks = (rows + ARRAY_SIZE - 1L) / ARRAY_SIZE;
+    long sparseBytes = (columns - writtenColumns) * blocks * bitmapCost;
+    long allNullBytes = columns * blocks * bitmapCost;
+    long historicalBlocks = (historicalRows + ARRAY_SIZE - 1L) / ARRAY_SIZE;
+    System.out.printf(
+        Locale.ROOT,
+        "  avoided bitmap RAM accounting: sparse=%d bytes/batch, all-null=%d 
bytes/batch, extension=%d bytes/column%n",
+        sparseBytes,
+        allNullBytes,
+        historicalBlocks * bitmapCost);
+  }
+
+  private static final class Scenario {
+
+    private final long[] firstTimes;
+    private final Object[] firstColumns;
+    private final long[] secondTimes;
+    private final Object[] secondColumns;
+    private final int rowCount;
+    private final int writtenColumnCount;
+
+    private Scenario(
+        long[] firstTimes,
+        Object[] firstColumns,
+        long[] secondTimes,
+        Object[] secondColumns,
+        int writtenColumnCount) {
+      this.firstTimes = firstTimes;
+      this.firstColumns = firstColumns;
+      this.secondTimes = secondTimes;
+      this.secondColumns = secondColumns;
+      rowCount = firstTimes.length + (secondTimes == null ? 0 : 
secondTimes.length);
+      this.writtenColumnCount = writtenColumnCount;
+    }
+
+    private static Scenario batch(int columns, int writtenColumns, int rows) {
+      return new Scenario(
+          createTimes(rows, 0),
+          createColumns(columns, writtenColumns, rows),
+          null,
+          null,
+          writtenColumns);
+    }
+
+    private static Scenario nullPrefix(int columns, int writtenColumns, int 
rows) {
+      int prefixRows = rows - 1;
+      return new Scenario(
+          createTimes(prefixRows, 0),
+          new Object[columns],
+          createTimes(1, prefixRows),
+          createColumns(columns, writtenColumns, 1),
+          writtenColumns);
+    }
+
+    private void writeTo(AlignedTVList target) {
+      if (firstTimes.length > 0) {
+        target.putAlignedValues(firstTimes, firstColumns, null, 0, 
firstTimes.length, null);
+      }
+      if (secondTimes != null) {
+        target.putAlignedValues(secondTimes, secondColumns, null, 0, 
secondTimes.length, null);
+      }
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
index 381f733bf4d..fbbd35cff43 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
@@ -270,20 +270,26 @@ public class AlignedTVListTest {
 
     Assert.assertNull(tvList.getValues().get(2).get(0));
     Assert.assertNull(tvList.getValues().get(2).get(1));
-    Assert.assertEquals(
-        2L * (AlignedTVList.bitmapReferenceRamCost() + 
AlignedTVList.bitmapRamCost()),
-        tvList.calculateRamSize().getRamSize() - ramSizeBeforeExtension);
+    Assert.assertNull(tvList.getBitMaps().get(2));
+    Assert.assertEquals(ramSizeBeforeExtension, 
tvList.calculateRamSize().getRamSize());
 
     long ramSizeBeforeExtendedColumnMaterialization = 
tvList.calculateRamSize().getRamSize();
     tvList.putAlignedValue(ARRAY_SIZE + 2L, new Object[] {null, null, 2});
 
     Assert.assertNull(tvList.getValues().get(2).get(0));
     Assert.assertNotNull(tvList.getValues().get(2).get(1));
+    Assert.assertNull(tvList.getBitMaps().get(2).get(0));
+    Assert.assertNotNull(tvList.getBitMaps().get(2).get(1));
+    Assert.assertTrue(tvList.getBitMaps().get(2).get(1).isMarked(0));
+    Assert.assertTrue(tvList.getBitMaps().get(2).get(1).isMarked(1));
+    Assert.assertFalse(tvList.getBitMaps().get(2).get(1).isMarked(2));
     Assert.assertTrue(tvList.isNullValue(0, 2));
     Assert.assertFalse(tvList.isNullValue(ARRAY_SIZE + 2, 2));
     Assert.assertEquals(2, tvList.getIntByValueIndex(ARRAY_SIZE + 2, 2));
     Assert.assertEquals(
-        AlignedTVList.valueListArrayMemCost(TSDataType.INT32),
+        AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
+            + 2L * AlignedTVList.bitmapReferenceRamCost()
+            + AlignedTVList.bitmapRamCost(),
         tvList.calculateRamSize().getRamSize() - 
ramSizeBeforeExtendedColumnMaterialization);
   }
 
@@ -299,7 +305,9 @@ public class AlignedTVListTest {
     tvList.putAlignedValue(ARRAY_SIZE + 1L, new Object[] {1L, 1L});
 
     Assert.assertEquals(
-        AlignedTVList.valueListArrayMemCost(TSDataType.INT64),
+        AlignedTVList.valueListArrayMemCost(TSDataType.INT64)
+            + 2L * AlignedTVList.bitmapReferenceRamCost()
+            + AlignedTVList.bitmapRamCost(),
         tvList.calculateRamSize().getRamSize() - ramSizeBeforeMaterialization);
 
     Assert.assertEquals(
@@ -315,7 +323,8 @@ public class AlignedTVListTest {
                 * 
projectedTvList.alignedTvListArrayMemCostWithoutPrimitiveArrays()
             + AlignedTVList.valueListArrayMemCost(TSDataType.INT64)
             + (long) projectedTvList.getBitMaps().get(0).size()
-                * (AlignedTVList.bitmapReferenceRamCost() + 
AlignedTVList.bitmapRamCost()),
+                * AlignedTVList.bitmapReferenceRamCost()
+            + AlignedTVList.bitmapRamCost(),
         projectedTvList.calculateRamSize().getRamSize());
 
     tvList.clear();
@@ -333,12 +342,10 @@ public class AlignedTVListTest {
     int blockCount = tvList.getValues().get(0).size();
     long denseRamSize = blockCount * tvList.alignedTvListArrayMemCost();
     long expectedRamSize =
-        denseRamSize
-            - blockCount * 
AlignedTVList.valueListArrayMemCost(TSDataType.INT64)
-            + (long) blockCount
-                * (AlignedTVList.bitmapReferenceRamCost() + 
AlignedTVList.bitmapRamCost());
+        denseRamSize - blockCount * 
AlignedTVList.valueListArrayMemCost(TSDataType.INT64);
 
     Assert.assertEquals(expectedRamSize, 
tvList.calculateRamSize().getRamSize());
+    Assert.assertNull(tvList.getBitMaps());
   }
 
   @Test
@@ -359,9 +366,91 @@ public class AlignedTVListTest {
 
     Assert.assertNotNull(tvList.getValues().get(0).get(0));
     Assert.assertNull(tvList.getValues().get(1).get(0));
+    Assert.assertNull(tvList.getBitMaps());
     Assert.assertTrue(tvList.isNullValue(ARRAY_SIZE - 1, 1));
   }
 
+  @Test
+  public void testImplicitNullBlocksAndAllValueDeletedMap() {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(Arrays.asList(TSDataType.INT64, 
TSDataType.INT64));
+    for (int i = 0; i <= ARRAY_SIZE; i++) {
+      tvList.putAlignedValue(i, new Object[] {null, null});
+    }
+
+    Assert.assertNull(tvList.getBitMaps());
+    Assert.assertNull(tvList.getValues().get(0).get(0));
+    Assert.assertNull(tvList.getValues().get(0).get(1));
+    Assert.assertNull(tvList.getValues().get(1).get(0));
+    Assert.assertNull(tvList.getValues().get(1).get(1));
+    BitMap allValueDeletedMap = tvList.getAllValueColDeletedMap();
+    for (int i = 0; i <= ARRAY_SIZE; i++) {
+      Assert.assertTrue(allValueDeletedMap.isMarked(i));
+    }
+
+    tvList.putAlignedValue(ARRAY_SIZE + 1L, new Object[] {1L, null});
+    tvList.putAlignedValue(ARRAY_SIZE + 2L, new Object[] {null, 2L});
+
+    Assert.assertNull(tvList.getBitMaps().get(0).get(0));
+    Assert.assertNull(tvList.getBitMaps().get(1).get(0));
+    Assert.assertNotNull(tvList.getBitMaps().get(0).get(1));
+    Assert.assertNotNull(tvList.getBitMaps().get(1).get(1));
+    allValueDeletedMap = tvList.getAllValueColDeletedMap();
+    for (int i = 0; i <= ARRAY_SIZE; i++) {
+      Assert.assertTrue(allValueDeletedMap.isMarked(i));
+    }
+    Assert.assertFalse(allValueDeletedMap.isMarked(ARRAY_SIZE + 1));
+    Assert.assertFalse(allValueDeletedMap.isMarked(ARRAY_SIZE + 2));
+  }
+
+  @Test
+  public void testDeletingImplicitNullColumnDoesNotMaterializeBitmaps() {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(Arrays.asList(TSDataType.INT64, 
TSDataType.INT64));
+    for (int i = 0; i < ARRAY_SIZE; i++) {
+      tvList.putAlignedValue(i, new Object[] {(long) i, null});
+    }
+
+    Assert.assertEquals(0, (int) tvList.delete(0, ARRAY_SIZE - 1L, 1).left);
+    Assert.assertNull(tvList.getBitMaps());
+    tvList.deleteColumn(1);
+    Assert.assertNull(tvList.getBitMaps());
+
+    tvList.deleteColumn(0);
+    Assert.assertNotNull(tvList.getBitMaps().get(0).get(0));
+    Assert.assertNull(tvList.getBitMaps().get(1));
+    for (int i = 0; i < ARRAY_SIZE; i++) {
+      Assert.assertTrue(tvList.isNullValue(i, 0));
+      Assert.assertTrue(tvList.isNullValue(i, 1));
+    }
+  }
+
+  @Test
+  public void testAllValueDeletedMapMatchesPerColumnNullState() {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(
+            Arrays.asList(TSDataType.INT64, TSDataType.INT64, 
TSDataType.INT64));
+    int rowCount = ARRAY_SIZE * 2 + 7;
+    for (int row = 0; row < rowCount; row++) {
+      tvList.putAlignedValue(
+          row,
+          new Object[] {
+            row % 3 == 0 ? null : (long) row,
+            row % 5 == 0 ? null : (long) row,
+            row < ARRAY_SIZE || row % 7 == 0 ? null : (long) row
+          });
+    }
+
+    BitMap allValueDeletedMap = tvList.getAllValueColDeletedMap();
+    for (int row = 0; row < rowCount; row++) {
+      boolean allNull = true;
+      for (int column = 0; column < 3; column++) {
+        allNull &= tvList.isNullValue(row, column);
+      }
+      Assert.assertEquals(allNull, allValueDeletedMap != null && 
allValueDeletedMap.isMarked(row));
+    }
+  }
+
   @Test
   public void testNullPrimitiveArrayCanBeClonedAndSerialized() throws 
IOException {
     AlignedTVList tvList =
diff --git a/pom.xml b/pom.xml
index 4496cc23e5d..94c0b3a8031 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,7 +146,7 @@
         <thrift.version>0.23.0</thrift.version>
         <xz.version>1.9</xz.version>
         <zstd-jni.version>1.5.6-3</zstd-jni.version>
-        <tsfile.version>2.3.2-260721-SNAPSHOT</tsfile.version>
+        <tsfile.version>2.3.2-260731-SNAPSHOT</tsfile.version>
         <i18n.locale>en</i18n.locale>
         <tsfile.locale.opt/>
     </properties>

Reply via email to