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

SteNicholas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/celeborn.git


The following commit(s) were added to refs/heads/main by this push:
     new 886e359d5 [CELEBORN-2314] Optimize the performance of 
DataBatches.requireBatches
886e359d5 is described below

commit 886e359d5e5a12698c1ed63f181f1f8a086c754b
Author: afterincomparableyum 
<[email protected]>
AuthorDate: Mon May 11 17:48:20 2026 +0800

    [CELEBORN-2314] Optimize the performance of DataBatches.requireBatches
    
    ### What changes were proposed in this pull request?
    
    `requireBatches(int requestSize)` currently calls `batches.remove(0)` per 
iteration, which shifts all remaining elements each time, overall O(kn). 
Replacing with a two pass approach (find split point, then subList(0, 
count).clear()) reduces this to O(n).
    
    ### Why are the changes needed?
    
    This is a minor performance optimization.
    
    ### Does this PR resolve a correctness bug?
    
    No.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, this is just a performance improvement.
    
    ### How was this patch tested?
    
    CI Unit/Integration tests.
    
    Closes #3671 from afterincomparableyum/celeborn-2314.
    
    Authored-by: afterincomparableyum 
<[email protected]>
    Signed-off-by: 子懿 <[email protected]>
---
 .../apache/celeborn/common/write/DataBatches.java  |  19 +-
 .../celeborn/common/write/DataBatchesSuiteJ.java   | 193 +++++++++++++++++++++
 2 files changed, 204 insertions(+), 8 deletions(-)

diff --git 
a/common/src/main/java/org/apache/celeborn/common/write/DataBatches.java 
b/common/src/main/java/org/apache/celeborn/common/write/DataBatches.java
index 9c17682fc..617163e29 100644
--- a/common/src/main/java/org/apache/celeborn/common/write/DataBatches.java
+++ b/common/src/main/java/org/apache/celeborn/common/write/DataBatches.java
@@ -18,6 +18,7 @@
 package org.apache.celeborn.common.write;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.celeborn.common.protocol.PartitionLocation;
 
@@ -57,18 +58,20 @@ public class DataBatches {
   public ArrayList<DataBatch> requireBatches(int requestSize) {
     if (requestSize >= totalSize) {
       totalSize = 0;
-      return batches;
+      ArrayList<DataBatch> allBatches = batches;
+      batches = new ArrayList<>();
+      return allBatches;
     }
-    // TODO: ArrayList.remove(0) in a loop is O(n^2) due to element shifting.
-    // Investigate subList(0, count).clear() or use LinkedList/ArrayDeque.
-    ArrayList<DataBatch> retBatches = new ArrayList<>();
+    int count = 0;
     int currentSize = 0;
     while (currentSize < requestSize) {
-      DataBatch elem = batches.remove(0);
-      retBatches.add(elem);
-      currentSize += elem.body.length;
-      totalSize -= elem.body.length;
+      currentSize += batches.get(count).body.length;
+      count++;
     }
+    List<DataBatch> head = batches.subList(0, count);
+    ArrayList<DataBatch> retBatches = new ArrayList<>(head);
+    head.clear();
+    totalSize -= currentSize;
     return retBatches;
   }
 }
diff --git 
a/common/src/test/java/org/apache/celeborn/common/write/DataBatchesSuiteJ.java 
b/common/src/test/java/org/apache/celeborn/common/write/DataBatchesSuiteJ.java
new file mode 100644
index 000000000..5e047ab2d
--- /dev/null
+++ 
b/common/src/test/java/org/apache/celeborn/common/write/DataBatchesSuiteJ.java
@@ -0,0 +1,193 @@
+/*
+ * 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.celeborn.common.write;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+public class DataBatchesSuiteJ {
+
+  private static void addBatch(DataBatches batches, int batchId, int size) {
+    byte[] body = new byte[size];
+    if (size > 0) {
+      body[0] = (byte) batchId;
+    }
+    batches.addDataBatch(null, batchId, body);
+  }
+
+  private static int[] batchIds(ArrayList<DataBatches.DataBatch> list) {
+    int[] ids = new int[list.size()];
+    for (int i = 0; i < list.size(); i++) {
+      ids[i] = list.get(i).batchId;
+    }
+    return ids;
+  }
+
+  @Test
+  public void requireBatchesReturnsAllAndResetsWhenRequestExceedsTotal() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+    addBatch(batches, 3, 30);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(100);
+
+    assertArrayEquals(new int[] {1, 2, 3}, batchIds(result));
+    assertEquals(0, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesReturnsAllWhenRequestEqualsTotal() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(30);
+
+    assertArrayEquals(new int[] {1, 2}, batchIds(result));
+    assertEquals(0, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesAllPathLeavesInstanceReusable() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+
+    ArrayList<DataBatches.DataBatch> first = batches.requireBatches(100);
+    assertEquals(1, first.size());
+
+    addBatch(batches, 2, 25);
+    assertEquals(25, batches.getTotalSize());
+    assertEquals(1, first.size());
+
+    ArrayList<DataBatches.DataBatch> second = batches.requireBatches(100);
+    assertArrayEquals(new int[] {2}, batchIds(second));
+  }
+
+  @Test
+  public void requireBatchesReturnsHeadBatchesUntilRequestSatisfied() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+    addBatch(batches, 3, 30);
+    addBatch(batches, 4, 40);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(25);
+
+    assertArrayEquals(new int[] {1, 2}, batchIds(result));
+    assertEquals(70, batches.getTotalSize());
+  }
+
+  @Test
+  public void 
requireBatchesIncludesBoundaryBatchWhenCumulativeSizeMatchesRequest() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+    addBatch(batches, 3, 30);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(30);
+
+    assertArrayEquals(new int[] {1, 2}, batchIds(result));
+    assertEquals(30, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesPreservesOrderOfRemainingBatches() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 10, 5);
+    addBatch(batches, 20, 5);
+    addBatch(batches, 30, 5);
+    addBatch(batches, 40, 5);
+    addBatch(batches, 50, 5);
+
+    ArrayList<DataBatches.DataBatch> first = batches.requireBatches(7);
+    assertArrayEquals(new int[] {10, 20}, batchIds(first));
+    assertEquals(15, batches.getTotalSize());
+
+    ArrayList<DataBatches.DataBatch> rest = batches.requireBatches(100);
+    assertArrayEquals(new int[] {30, 40, 50}, batchIds(rest));
+    assertEquals(0, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesSupportsRepeatedPartialCalls() {
+    DataBatches batches = new DataBatches();
+    for (int i = 0; i < 6; i++) {
+      addBatch(batches, i, 10);
+    }
+    assertEquals(60, batches.getTotalSize());
+
+    ArrayList<DataBatches.DataBatch> a = batches.requireBatches(15);
+    assertArrayEquals(new int[] {0, 1}, batchIds(a));
+    assertEquals(40, batches.getTotalSize());
+
+    ArrayList<DataBatches.DataBatch> b = batches.requireBatches(25);
+    assertArrayEquals(new int[] {2, 3, 4}, batchIds(b));
+    assertEquals(10, batches.getTotalSize());
+
+    ArrayList<DataBatches.DataBatch> c = batches.requireBatches(10);
+    assertArrayEquals(new int[] {5}, batchIds(c));
+    assertEquals(0, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesWithZeroRequestReturnsEmptyAndDoesNotMutate() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(0);
+
+    assertTrue(result.isEmpty());
+    assertEquals(30, batches.getTotalSize());
+
+    // Underlying batches still intact and in original order.
+    ArrayList<DataBatches.DataBatch> all = batches.requireBatches(100);
+    assertArrayEquals(new int[] {1, 2}, batchIds(all));
+  }
+
+  @Test
+  public void requireBatchesOnEmptyReturnsEmpty() {
+    DataBatches batches = new DataBatches();
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(0);
+
+    assertTrue(result.isEmpty());
+    assertEquals(0, batches.getTotalSize());
+  }
+
+  @Test
+  public void requireBatchesReturnedBatchesAreIndependentOfFutureAdds() {
+    DataBatches batches = new DataBatches();
+    addBatch(batches, 1, 10);
+    addBatch(batches, 2, 20);
+    addBatch(batches, 3, 30);
+
+    ArrayList<DataBatches.DataBatch> result = batches.requireBatches(15);
+    assertArrayEquals(new int[] {1, 2}, batchIds(result));
+
+    addBatch(batches, 4, 40);
+    assertArrayEquals(new int[] {1, 2}, batchIds(result));
+    assertEquals(70, batches.getTotalSize());
+  }
+}

Reply via email to