Skip to content

Commit 887912f

Browse files
committed
Reuse Hadoop vectored read copy buffer in parquet reader
Signed-off-by: Hongbin Ma (Mahone) <mahongbin@apache.org>
1 parent 626c8ed commit 887912f

3 files changed

Lines changed: 79 additions & 17 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2026, NVIDIA CORPORATION.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.nvidia.spark.rapids.fileio;
18+
19+
import ai.rapids.cudf.HostMemoryBuffer;
20+
import com.nvidia.spark.rapids.jni.fileio.RapidsInputFile;
21+
22+
import java.io.IOException;
23+
import java.util.List;
24+
25+
/**
26+
* Capability interface for input files that can use a caller-supplied copy buffer.
27+
*/
28+
public interface CopyBufferReadableInputFile extends RapidsInputFile {
29+
/**
30+
* Reads ranges into {@code output} using {@code copyBuffer} as caller-owned scratch space.
31+
* Implementations must not retain a reference to {@code copyBuffer}.
32+
*
33+
* @param output the destination buffer
34+
* @param copyRanges input ranges and output offsets
35+
* @param copyBuffer caller-owned reusable copy buffer
36+
* @throws IOException if an I/O error occurs during reading
37+
*/
38+
void readVectored(
39+
HostMemoryBuffer output,
40+
List<RapidsInputFile.CopyRange> copyRanges,
41+
byte[] copyBuffer) throws IOException;
42+
}

sql-plugin/src/main/java/com/nvidia/spark/rapids/fileio/hadoop/HadoopInputFile.java

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.nvidia.spark.rapids.fileio.hadoop;
1818

1919
import ai.rapids.cudf.HostMemoryBuffer;
20+
import com.nvidia.spark.rapids.fileio.CopyBufferReadableInputFile;
2021
import com.nvidia.spark.rapids.jni.fileio.RapidsInputFile;
2122
import com.nvidia.spark.rapids.jni.fileio.SeekableInputStream;
2223
import org.apache.hadoop.conf.Configuration;
@@ -35,31 +36,22 @@
3536
* This class provides methods to get the length of the file and to open a seekable input stream
3637
* for reading the file.
3738
*/
38-
public class HadoopInputFile implements RapidsInputFile {
39-
private static final String PARQUET_READ_ALLOCATION_SIZE = "parquet.read.allocation.size";
40-
39+
public class HadoopInputFile implements CopyBufferReadableInputFile {
4140
private final Path filePath;
4241
private final FileSystem fs;
43-
private final int copyBufferSize;
4442

4543
public static HadoopInputFile create(Path filePath, Configuration conf) throws IOException {
4644
Objects.requireNonNull(filePath, "filePath can't be null!");
4745
Objects.requireNonNull(conf, "Hadoop conf can't be null");
4846
FileSystem fs = filePath.getFileSystem(conf);
49-
int copyBufferSize = conf.getInt(PARQUET_READ_ALLOCATION_SIZE,
50-
RapidsInputFile.DEFAULT_READ_VECTORED_COPY_BUFFER_SIZE);
51-
return new HadoopInputFile(filePath, fs, copyBufferSize);
47+
return new HadoopInputFile(filePath, fs);
5248
}
5349

54-
private HadoopInputFile(Path filePath, FileSystem fs, int copyBufferSize) {
50+
private HadoopInputFile(Path filePath, FileSystem fs) {
5551
Objects.requireNonNull(filePath, "filePath can't be null!");
5652
Objects.requireNonNull(fs, "FileSystem can't be null");
57-
if (copyBufferSize <= 0) {
58-
throw new IllegalArgumentException(PARQUET_READ_ALLOCATION_SIZE + " must be positive");
59-
}
6053
this.filePath = filePath;
6154
this.fs = fs;
62-
this.copyBufferSize = copyBufferSize;
6355
}
6456

6557
@Override
@@ -83,8 +75,10 @@ public SeekableInputStream open() throws IOException {
8375
}
8476

8577
@Override
86-
public void readVectored(HostMemoryBuffer output, List<RapidsInputFile.CopyRange> copyRanges)
87-
throws IOException {
88-
RapidsInputFile.readVectoredUsingCopyBuffer(this, output, copyRanges, copyBufferSize);
78+
public void readVectored(
79+
HostMemoryBuffer output,
80+
List<RapidsInputFile.CopyRange> copyRanges,
81+
byte[] copyBuffer) throws IOException {
82+
RapidsInputFile.readVectoredUsingCopyBuffer(this, output, copyRanges, copyBuffer);
8983
}
9084
}

sql-plugin/src/main/scala/com/nvidia/spark/rapids/parquet/GpuParquetScan.scala

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.nvidia.spark.rapids.RapidsConf.ParquetFooterReaderType
3838
import com.nvidia.spark.rapids.RapidsPluginImplicits._
3939
import com.nvidia.spark.rapids.RmmRapidsRetryIterator.withRetryNoSplit
4040
import com.nvidia.spark.rapids.filecache.FileCache
41+
import com.nvidia.spark.rapids.fileio.CopyBufferReadableInputFile
4142
import com.nvidia.spark.rapids.fileio.hadoop.HadoopFileIO
4243
import com.nvidia.spark.rapids.io.async._
4344
import com.nvidia.spark.rapids.jni.{DateTimeRebase, ParquetFooter, RmmSpark}
@@ -280,6 +281,22 @@ object GpuParquetScan {
280281
}
281282
}
282283

284+
private[parquet] def readRangesToHostMemory(
285+
inputFile: CopyBufferReadableInputFile,
286+
output: HostMemoryBuffer,
287+
ranges: Seq[CopyRange],
288+
metrics: Map[String, GpuMetric],
289+
copyBuffer: Array[Byte]): Long = {
290+
if (ranges.isEmpty) {
291+
0L
292+
} else {
293+
metrics.getOrElse(READ_FS_TIME, NoopMetric).ns {
294+
inputFile.readVectored(output, ranges.asJava, copyBuffer)
295+
}
296+
ranges.map(_.getLength).sum
297+
}
298+
}
299+
283300
def throwIfRebaseNeededInExceptionMode(table: Table, dateRebaseMode: DateTimeRebaseMode,
284301
timestampRebaseMode: DateTimeRebaseMode): Unit = {
285302
(0 until table.getNumberOfColumns).foreach { i =>
@@ -1579,6 +1596,9 @@ trait ParquetPartitionReaderBase extends Logging with ScanWithMetrics
15791596
def compressCfg: CpuCompressionConfig
15801597

15811598
val copyBufferSize = conf.getInt("parquet.read.allocation.size", 8 * 1024 * 1024)
1599+
require(copyBufferSize > 0, "parquet.read.allocation.size must be positive")
1600+
1601+
private[this] lazy val readVectoredCopyBuffer: Array[Byte] = new Array[Byte](copyBufferSize)
15821602

15831603
def checkIfNeedToSplitBlocks(currentDateRebaseMode: DateTimeRebaseMode,
15841604
nextDateRebaseMode: DateTimeRebaseMode,
@@ -2029,8 +2049,14 @@ trait ParquetPartitionReaderBase extends Logging with ScanWithMetrics
20292049
if (scheme != null && scheme.startsWith("s3")) {
20302050
GpuTaskMetrics.get.recordPerfioS3BackendOnce()
20312051
}
2032-
val totalBytesCopied = GpuParquetScan.readRangesToHostMemory(
2033-
inputFile, out.buffer, coalescedRanges, metrics)
2052+
val totalBytesCopied = inputFile match {
2053+
case copyBufferReadable: CopyBufferReadableInputFile =>
2054+
GpuParquetScan.readRangesToHostMemory(
2055+
copyBufferReadable, out.buffer, coalescedRanges, metrics,
2056+
readVectoredCopyBuffer)
2057+
case _ =>
2058+
GpuParquetScan.readRangesToHostMemory(inputFile, out.buffer, coalescedRanges, metrics)
2059+
}
20342060

20352061
// try to cache the remote ranges that were copied
20362062
remoteCopies.foreach { range =>

0 commit comments

Comments
 (0)