Repository navigation
[CSV 6/9] Parse CSV input from device buffers in place, without copying it - #21
karthikeyann wants to merge 4 commits into
Conversation
`classify_integral_field` skips the leading zeros of integers of 19 digits or more before comparing them with the 64-bit limits. The loop dereferenced the next character before checking that it is still in the field, so an integer made of zeros only read the character after it. This is a pre-existing bug in upstream libcudf, in code that the CSV and JSON type inference share. The CSV reader's type inference reads one byte past the data when such a field ends the input without a final terminator: past the reader's copy of the input, or, for a staged block, past its rows in shared memory. In JSON, values are always followed by a structural character, so the read stays within the input there. It is fixed in this series because parsing device buffers in place (later in this series) makes that byte the one past the end of the caller's buffer; it was found while auditing every read of the CSV kernels against exact-size caller buffers. Check the bound first. The classification does not change: the byte past the digits only ended the loop, which also ends at the bound. Test: inferred reads of 19 to 32 zeros at the end of the data, with rows staged in shared memory and not. The test passes without the fix: it only detects the read under compute-sanitizer memcheck with exact allocations (--rmm_mode=cuda), when the rows are not staged: 21 invalid reads without the fix, none with it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`datasource::device_read(offset, size, stream)` returns a buffer that may own a copy of the data, as for kvikIO file sources, or be a view of device memory that holds the data already, as for device buffer sources. Readers cannot tell the two apart, so they copy the data of all sources into memory of their own, even when it is already in device memory. Add `datasource::supports_zero_copy_device_read()`, false by default. A source that returns true (and supports device reads) guarantees that the buffer returned by `device_read(offset, size, stream)` points to device memory that holds the data in stream order on `stream`, and that stays valid and unmodified until the buffer is destroyed and the reader's work on `stream` is complete. Readers can then process the data in place, without modifying it, whether or not the source prefers device reads, since no read takes place. It does not apply to reads into a preallocated buffer, which always copy. The memory must be device memory, not host memory that the device can access (pinned, or managed memory resident on the host): readers may read the data several times, and such sources should let them copy it once instead. The doc of `device_read(offset, size, stream)` now also allows calling it when the source supports zero-copy device reads. Device buffer sources return true; `datasource::create(device_span)` now documents that readers may process the buffer in place, and that it must hold the data when a read starts and must not be modified or freed until the read completes (host reads of the source are not ordered on the stream of the read). The wrapper of user-implemented sources forwards the query, so that user sources that hold their data in device memory can opt in. The name follows `supports_device_read()`, and Arrow's `RandomAccessFile::supports_zero_copy()`: it is a capability of the source, not a per-size preference like `is_device_read_preferred()`. Adding a virtual function changes the vtable of `datasource`, so subclasses outside of libcudf must be rebuilt. Test: the query for device buffer, host buffer, file and user sources, and device reads of a device buffer and of user sources through the wrapper, which return views exactly for zero-copy sources, in a new DATASOURCE_TEST. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CSV reader copies its whole input into device memory it owns before parsing it, because decoding unescapes quoted strings (`""` -> `"`) in place. For device buffer sources this is a device-to-device copy of the whole input (0.75 ms for the 554MB input of the TAXI benchmark), and device memory of the size of the input for the whole read. Whole-file reads (no byte range, skiprows, nrows or skipfooter, and no compression) of sources whose device reads return views of their data (`datasource::supports_zero_copy_device_read`, true for device buffers) now parse the view in place, whether or not the source prefers device reads, since a view involves no read. Reads with row selection still copy the chunks they read, and host, pinned and file sources are read as before: kvikIO file sources read into the reader's buffer, which is already a single read with no extra copy, and they keep unescaping in place. Empty ranges (a BOM-only input) are not requested from the source. The caller's buffer is never written: `decode_row_column_data` takes the data as read-only, and an `unescape_buffer` at the same offsets that receives the unescaped quoted fields. It is the data itself when the reader owns it (in place, as before), and otherwise uninitialized scratch memory of the size of the data, allocated when decoding starts and only if `doublequote` is set and a column is a string column. `unescape_doublequotes` now takes whether the output already holds the content: in place, from the data or from its staged copy, the characters before the first pair are not rewritten; into scratch memory, the whole unescaped content is written. Fields without escaped pairs are not written, and their strings keep pointing into the source's data. No kernel reads outside of the data, so the view can be exactly the caller's buffer, at any alignment: - The single-pass row gathering loads 16-byte words only for 32-byte slices that are aligned and lie entirely within the data, and one character at a time (bounded by the end) otherwise; the character before a slice is read only if the slice is not at the start. The blank row test reads only offsets below the size. - Row staging copies exactly the rows of a block, word loads of the field seek are bounded by the data start and the row end, and field parsing stays within the field (with the first commit's fix). Unaligned views (a UTF-8 BOM makes the data start 3 bytes into the buffer, and subspans of buffers can start anywhere) are parsed in place too, but the row gathering then loads every slice one character at a time: 783 us instead of 403 us for 512 MiB of data, which still costs less than the copy (681 us) plus the gathering of the aligned copy (438 us). Aligning the slices to absolute addresses would give them the word loads; that is left for a follow-up. Memory: the scratch memory replaces the copy, is only allocated for decoding and only when string columns can need it, so the peak device memory of a read is never larger than before. Measured on an RTX PRO 6000 (nvbench GPU time, 256MB configurations of the IO benchmarks, interleaved runs; the device is shared with other jobs, so gains vary by a few percent between sessions): - DEVICE_BUFFER, minimum / median of 5 runs: TAXI 5.49 -> 4.67 / 5.58 -> 4.79 ms (-15% / -14%), LOGS 3.17 -> 3.04 / 3.45 -> 3.05 ms (-4% / -12%), ANALYTICS 5.62 -> 4.92 / 5.67 -> 4.94 ms (-12% / -13%), QUOTE_0_PCT 6.63 -> 5.76 / 6.79 -> 5.82 ms (-13% / -14%), QUOTE_25_PCT 5.84 -> 5.58 / 5.99 -> 5.61 ms (-4% / -6%), QUOTE_100_PCT 9.31 -> 9.40 / 9.52 -> 9.53 ms (unchanged, see below). - Peak memory drops where no scratch memory is needed (no string column: ANALYTICS 849.5 -> 287.5 MiB, QUOTE_0_PCT 913.6 -> 265.1 MiB), and is unchanged otherwise (TAXI 903.7 MiB). - FILEPATH, HOST_BUFFER and PINNED_BUFFER (TAXI) and HOST_BUFFER (QUOTE_25_PCT): unchanged, within the noise of these runs (up to 5%). - Decoding data whose every string field has escaped quotes writes the unescaped strings to scratch memory rather than to cache lines just read: the decode kernel took 1645 us instead of 1486 us for 512 MiB (plus 0.7 ms saved on the copy). With the string columns of QUOTE_100_PCT, the slower decode takes about what the copy saves. Tests: device buffers at offsets 0 to 15 that end at the end of their allocation (BOM, comment and blank lines, escaped quotes at the start, middle and end of strings, quoted delimiters and terminators, CRLF, no final terminator), read with and without type inference, unescaping and quoting, with rows staged in shared memory and not staged (through LIBCUDF_CSV_ROW_STAGING), the same as from a host buffer and from a file, and left unchanged; empty, BOM-only and header-only device buffers and single fields at the end of the buffer; another quote character; device buffers of rows at every alignment; a user source with unaligned device data, parsed in place if it reports zero-copy reads (whether or not it prefers device reads) and copied otherwise, and copied with row selection. compute-sanitizer memcheck with exact allocations reports no error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Parsing a device buffer in place, the reader allocates scratch memory of the size of the input for the quoted strings it unescapes, whenever `doublequote` is set (the default) and a column is a string column, even if no field has an escaped quote pair. An escaped pair is two consecutive quote characters, and the single-pass row gathering already loads every character of the data. Its speculative pass now also records whether any quote character follows a quote character: in the word-loaded slices, from the quote bitmap it already computes (`quotes & (quotes << 1)`, and bit 0 if the character before the slice is a quote), in the others, character by character. The flag is copied to the host with the number of rows, in the synchronization that already sized the row offsets, and the reader only allocates the scratch memory if it is set. The flag ignores the parser state on purpose: it is a superset of the fields that decoding unescapes, whatever the options. A state-aware test could exclude empty quoted fields (`""`), but decoding also unescapes fields that the row state machine does not see as quoted (e.g. `, "a""b"` with detect_whitespace_around_quotes), so it could miss pairs that decoding writes. This helps reads of data with string columns and no two consecutive quote characters anywhere (no escaped quotes and no empty quoted fields). No CSV reader benchmark has such data: the TAXI, LOGS, QUOTE_25_PCT and QUOTE_100_PCT inputs have consecutive quotes, and QUOTE_0_PCT and ANALYTICS have no string column, so the previous commit already allocates no scratch memory for them. Measured on an RTX PRO 6000: - 256 MiB of rows `12345,678.5,s12345,t` read from a device buffer with inferred types: peak device memory 1439.5 -> 1183.5 MiB. With an empty quoted field in 10% of the rows, it is unchanged (1432.5 MiB). - Speculative row gathering pass over 512 MiB: 409 -> 412 us aligned, 873 -> 878 us unaligned; end-to-end times of the benchmarks are unchanged. Tests: the only escaped pair of the data at positions around the boundaries of 32-character slices, 16-byte words and 32KB tiles, in aligned and unaligned device buffers (an undetected pair makes the decode write to the missing scratch memory); a pair in a field that the row gathering does not consider quoted but decoding unescapes (detect_whitespace_around_quotes); the peak memory of a read with and without escaped pairs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Note Re-measured cleanly (2026-09-25); these numbers replace the earlier ones in this comment. The earlier numbers were taken while other validation jobs (tests, sanitizers, fuzzers) ran on the same host, against a baseline measured hours earlier, so the pinned-host, pageable-host and file results in particular could be skewed. Important Trade-off found by this measurement: this PR gains ~1.13× on device buffers but costs ~4% on pageable-host and file reads (0.96× vs PR 5, consistent in both rounds). Those sources still copy into a reader-owned buffer; the likely cause is the decode kernel now reading the data read-only and writing unescaped strings through a separate target pointer on every path. To be investigated before upstreaming (e.g. keep the old in-place path when the reader owns the data). Methodology (clean re-measurement). All ten builds of the stack (base and PRs 1–9) were measured in one session on the same GPU (host GPU 1), in two interleaved rounds (base, PR 1, …, PR 9, then again), so drift affects every build equally. Each benchmark run started only when no other process was using the GPU and no build/test/sanitizer/fuzzer was running on the host in our environment, and would have been re-run if anything appeared during it; all 160 runs were clean on the first attempt. Each number below is the best of the two rounds per side. CSV reader benchmarks: PR 6/9 (in-place parsing of device buffers)This PR Speedup vs base (cumulative), by input source
Speedup vs parent PR (this PR's own contribution)
Sources: the Full results: every CSV reader benchmark config
|
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_column_selection | column_selection=ALL, num_chunks=1, row_selection=ALL | 2.63 GiB/s | 3.63 GiB/s | 3.44 GiB/s | 1.31× | 0.95× |
| csv_read_column_selection | column_selection=ALTERNATE, num_chunks=1, row_selection=ALL | 1.45 GiB/s | 1.91 GiB/s | 1.81 GiB/s | 1.24× | 0.94× |
| csv_read_column_selection | column_selection=FIRST_HALF, num_chunks=1, row_selection=ALL | 1.49 GiB/s | 1.98 GiB/s | 1.86 GiB/s | 1.25× | 0.94× |
| csv_read_column_selection | column_selection=SECOND_HALF, num_chunks=1, row_selection=ALL | 1.49 GiB/s | 1.98 GiB/s | 1.86 GiB/s | 1.25× | 0.94× |
| csv_read_data_type | data_type=DECIMAL, io=DEVICE_BUFFER | 15.33 GiB/s | 34.77 GiB/s | 41.11 GiB/s | 2.68× | 1.18× |
| csv_read_data_type | data_type=DURATION, io=DEVICE_BUFFER | 12.78 GiB/s | 32.49 GiB/s | 39.17 GiB/s | 3.06× | 1.21× |
| csv_read_data_type | data_type=FLOAT, io=DEVICE_BUFFER | 18.66 GiB/s | 26.57 GiB/s | 29.54 GiB/s | 1.58× | 1.11× |
| csv_read_data_type | data_type=INTEGRAL, io=DEVICE_BUFFER | 17.43 GiB/s | 37.44 GiB/s | 43.57 GiB/s | 2.50× | 1.16× |
| csv_read_data_type | data_type=STRING, io=DEVICE_BUFFER | 4.68 GiB/s | 27.00 GiB/s | 27.37 GiB/s | 5.84× | 1.01× |
| csv_read_data_type | data_type=TIMESTAMP, io=DEVICE_BUFFER | 12.37 GiB/s | 23.85 GiB/s | 30.29 GiB/s | 2.45× | 1.27× |
| csv_read_io | io=FILEPATH | 4.55 GiB/s | 7.11 GiB/s | 6.84 GiB/s | 1.50× | 0.96× |
| csv_read_io | io=HOST_BUFFER | 3.38 GiB/s | 4.09 GiB/s | 3.84 GiB/s | 1.14× | 0.94× |
| csv_read_row_selection | column_selection=ALL, num_chunks=1, row_selection=BYTE_RANGE | 2.56 GiB/s | 3.34 GiB/s | 3.31 GiB/s | 1.29× | 0.99× |
| csv_read_row_selection | column_selection=ALL, num_chunks=1, row_selection=NROWS | 2.54 GiB/s | 3.33 GiB/s | 3.30 GiB/s | 1.30× | 0.99× |
| csv_read_row_selection | column_selection=ALL, num_chunks=1, row_selection=SKIPFOOTER | 2.67 GiB/s | 3.64 GiB/s | 3.59 GiB/s | 1.35× | 0.99× |
| csv_read_row_selection | column_selection=ALL, num_chunks=8, row_selection=BYTE_RANGE | 1.93 GiB/s | 2.75 GiB/s | 2.70 GiB/s | 1.40× | 0.98× |
| csv_read_row_selection | column_selection=ALL, num_chunks=8, row_selection=NROWS | 0.71 GiB/s | 0.78 GiB/s | 0.78 GiB/s | 1.10× | 1.00× |
| csv_read_row_selection | column_selection=ALL, num_chunks=8, row_selection=SKIPFOOTER | 0.45 GiB/s | 0.48 GiB/s | 0.47 GiB/s | 1.03× | 0.97× |
CSV_READER_QUOTING_IO_NVBENCH (12 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_quoting_io | data_size_mb=256, io=DEVICE_BUFFER, quote_pct=QUOTE_0_PCT | 17.11 GiB/s | 36.99 GiB/s | 42.93 GiB/s | 2.51× | 1.16× |
| csv_read_quoting_io | data_size_mb=256, io=DEVICE_BUFFER, quote_pct=QUOTE_100_PCT | 4.87 GiB/s | 26.64 GiB/s | 26.70 GiB/s | 5.48× | 1.00× |
| csv_read_quoting_io | data_size_mb=256, io=DEVICE_BUFFER, quote_pct=QUOTE_25_PCT | 12.75 GiB/s | 42.62 GiB/s | 45.15 GiB/s | 3.54× | 1.06× |
| csv_read_quoting_io | data_size_mb=256, io=FILEPATH, quote_pct=QUOTE_0_PCT | 5.25 GiB/s | 7.92 GiB/s | 7.47 GiB/s | 1.42× | 0.94× |
| csv_read_quoting_io | data_size_mb=256, io=FILEPATH, quote_pct=QUOTE_100_PCT | 4.69 GiB/s | 14.06 GiB/s | 14.06 GiB/s | 3.00× | 1.00× |
| csv_read_quoting_io | data_size_mb=256, io=FILEPATH, quote_pct=QUOTE_25_PCT | 6.47 GiB/s | 12.29 GiB/s | 11.49 GiB/s | 1.78× | 0.93× |
| csv_read_quoting_io | data_size_mb=256, io=HOST_BUFFER, quote_pct=QUOTE_0_PCT | 3.80 GiB/s | 4.30 GiB/s | 4.20 GiB/s | 1.10× | 0.98× |
| csv_read_quoting_io | data_size_mb=256, io=HOST_BUFFER, quote_pct=QUOTE_100_PCT | 4.18 GiB/s | 9.68 GiB/s | 9.11 GiB/s | 2.18× | 0.94× |
| csv_read_quoting_io | data_size_mb=256, io=HOST_BUFFER, quote_pct=QUOTE_25_PCT | 4.99 GiB/s | 7.34 GiB/s | 6.77 GiB/s | 1.36× | 0.92× |
| csv_read_quoting_io | data_size_mb=256, io=PINNED_BUFFER, quote_pct=QUOTE_0_PCT | 9.57 GiB/s | 13.98 GiB/s | 13.88 GiB/s | 1.45× | 0.99× |
| csv_read_quoting_io | data_size_mb=256, io=PINNED_BUFFER, quote_pct=QUOTE_100_PCT | 5.40 GiB/s | 19.58 GiB/s | 19.55 GiB/s | 3.62× | 1.00× |
| csv_read_quoting_io | data_size_mb=256, io=PINNED_BUFFER, quote_pct=QUOTE_25_PCT | 9.77 GiB/s | 20.33 GiB/s | 20.28 GiB/s | 2.08× | 1.00× |
CSV_READER_QUOTING_NVBENCH (6 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_quoting | data_size_mb=256, quote_pct=QUOTE_0_PCT | 17.51 GiB/s | 38.18 GiB/s | 43.93 GiB/s | 2.51× | 1.15× |
| csv_read_quoting | data_size_mb=256, quote_pct=QUOTE_100_PCT | 4.80 GiB/s | 27.03 GiB/s | 27.32 GiB/s | 5.69× | 1.01× |
| csv_read_quoting | data_size_mb=256, quote_p 7438 ct=QUOTE_25_PCT | 13.88 GiB/s | 42.63 GiB/s | 45.61 GiB/s | 3.29× | 1.07× |
| csv_read_quoting | data_size_mb=64, quote_pct=QUOTE_0_PCT | 14.10 GiB/s | 26.14 GiB/s | 29.65 GiB/s | 2.10× | 1.13× |
| csv_read_quoting | data_size_mb=64, quote_pct=QUOTE_100_PCT | 1.37 GiB/s | 8.49 GiB/s | 8.21 GiB/s | 6.00× | 0.97× |
| csv_read_quoting | data_size_mb=64, quote_pct=QUOTE_25_PCT | 4.57 GiB/s | 21.24 GiB/s | 20.93 GiB/s | 4.58× | 0.99× |
CSV_READER_REALISTIC_IO_NVBENCH (12 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_realistic_io | data_size_mb=256, io=DEVICE_BUFFER, profile=ANALYTICS | 25.28 GiB/s | 43.71 GiB/s | 50.60 GiB/s | 2.00× | 1.16× |
| csv_read_realistic_io | data_size_mb=256, io=DEVICE_BUFFER, profile=LOGS | 27.19 GiB/s | 72.54 GiB/s | 83.92 GiB/s | 3.09× | 1.16× |
| csv_read_realistic_io | data_size_mb=256, io=DEVICE_BUFFER, profile=TAXI | 18.91 GiB/s | 45.49 GiB/s | 52.43 GiB/s | 2.77× | 1.15× |
| csv_read_realistic_io | data_size_mb=256, io=FILEPATH, profile=ANALYTICS | 6.63 GiB/s | 9.12 GiB/s | 8.42 GiB/s | 1.27× | 0.92× |
| csv_read_realistic_io | data_size_mb=256, io=FILEPATH, profile=LOGS | 9.85 GiB/s | 15.40 GiB/s | 14.55 GiB/s | 1.48× | 0.94× |
| csv_read_realistic_io | data_size_mb=256, io=FILEPATH, profile=TAXI | 6.37 GiB/s | 9.75 GiB/s | 9.04 GiB/s | 1.42× | 0.93× |
| csv_read_realistic_io | data_size_mb=256, io=HOST_BUFFER, profile=ANALYTICS | 4.83 GiB/s | 5.14 GiB/s | 4.87 GiB/s | 1.01× | 0.95× |
| csv_read_realistic_io | data_size_mb=256, io=HOST_BUFFER, profile=LOGS | 7.16 GiB/s | 8.80 GiB/s | 8.36 GiB/s | 1.17× | 0.95× |
| csv_read_realistic_io | data_size_mb=256, io=HOST_BUFFER, profile=TAXI | 4.77 GiB/s | 5.44 GiB/s | 5.14 GiB/s | 1.08× | 0.95× |
| csv_read_realistic_io | data_size_mb=256, io=PINNED_BUFFER, profile=ANALYTICS | 12.58 GiB/s | 15.99 GiB/s | 15.99 GiB/s | 1.27× | 1.00× |
| csv_read_realistic_io | data_size_mb=256, io=PINNED_BUFFER, profile=LOGS | 17.05 GiB/s | 27.91 GiB/s | 27.99 GiB/s | 1.64× | 1.00× |
| csv_read_realistic_io | data_size_mb=256, io=PINNED_BUFFER, profile=TAXI | 11.20 GiB/s | 16.91 GiB/s | 16.90 GiB/s | 1.51× | 1.00× |
CSV_READER_REALISTIC_NVBENCH (9 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_realistic | data_size_mb=1024, profile=ANALYTICS | 25.11 GiB/s | 49.16 GiB/s | 58.16 GiB/s | 2.32× | 1.18× |
| csv_read_realistic | data_size_mb=1024, profile=LOGS | 31.88 GiB/s | 81.52 GiB/s | 94.63 GiB/s | 2.97× | 1.16× |
| csv_read_realistic | data_size_mb=1024, profile=TAXI | 23.43 GiB/s | 52.80 GiB/s | 62.74 GiB/s | 2.68× | 1.19× |
| csv_read_realistic | data_size_mb=256, profile=ANALYTICS | 25.24 GiB/s | 43.75 GiB/s | 50.90 GiB/s | 2.02× | 1.16× |
| csv_read_realistic | data_size_mb=256, profile=LOGS | 27.37 GiB/s | 73.03 GiB/s | 88.57 GiB/s | 3.24× | 1.21× |
| csv_read_realistic | data_size_mb=256, profile=TAXI | 19.01 GiB/s | 45.58 GiB/s | 53.02 GiB/s | 2.79× | 1.16× |
| csv_read_realistic | data_size_mb=512, profile=ANALYTICS | 25.64 GiB/s | 47.43 GiB/s | 55.56 GiB/s | 2.17× | 1.17× |
| csv_read_realistic | data_size_mb=512, profile=LOGS | 31.05 GiB/s | 83.38 GiB/s | 99.72 GiB/s | 3.21× | 1.20× |
| csv_read_realistic | data_size_mb=512, profile=TAXI | 22.88 GiB/s | 50.24 GiB/s | 58.92 GiB/s | 2.58× | 1.17× |
CSV_READER_SCALE_NVBENCH (5 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_scale | data_size_mb=1024 | 3.54 GiB/s | 4.08 GiB/s | 3.83 GiB/s | 1.08× | 0.94× |
| csv_read_scale | data_size_mb=2048 | 3.52 GiB/s | 4.08 GiB/s | 3.85 GiB/s | 1.09× | 0.94× |
| csv_read_scale | data_size_mb=256 | 3.59 GiB/s | 4.04 GiB/s | 3.79 GiB/s | 1.06× | 0.94× |
| csv_read_scale | data_size_mb=4096 | 3.38 GiB/s | 3.88 GiB/s | 3.85 GiB/s | 1.14× | 0.99× |
| csv_read_scale | data_size_mb=512 | 3.60 GiB/s | 4.05 GiB/s | 3.81 GiB/s | 1.06× | 0.94× |
CSV_READER_TYPE_INFERENCE_IO_NVBENCH (64 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 10.11 GiB/s | 18.02 GiB/s | 19.41 GiB/s | 1.92× | 1.08× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 9.02 GiB/s | 21.35 GiB/s | 22.78 GiB/s | 2.53× | 1.07× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_STRING | 4.40 GiB/s | 21.93 GiB/s | 21.83 GiB/s | 4.96× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=MIXED | 6.59 GiB/s | 17.31 GiB/s | 18.23 GiB/s | 2.77× | 1.05× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 12.31 GiB/s | 22.64 GiB/s | 24.82 GiB/s | 2.02× | 1.10× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 21.14 GiB/s | 47.82 GiB/s | 57.34 GiB/s | 2.71× | 1.20× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_STRING | 11.91 GiB/s | 50.75 GiB/s | 51.33 GiB/s | 4.31× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=MIXED | 21.15 GiB/s | 48.46 GiB/s | 56.73 GiB/s | 2.68× | 1.17× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_FLOAT | 4.03 GiB/s | 6.08 GiB/s | 5.99 GiB/s | 1.49× | 0.99× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_INTEGRAL | 4.12 GiB/s | 6.69 GiB/s | 6.67 GiB/s | 1.62× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_STRING | 3.94 GiB/s | 12.55 GiB/s | 12.48 GiB/s | 3.17× | 0.99× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=MIXED | 3.16 GiB/s | 5.91 GiB/s | 5.95 GiB/s | 1.88× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_FLOAT | 4.37 GiB/s | 6.33 GiB/s | 6.38 GiB/s | 1.46× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_INTEGRAL | 5.98 GiB/s | 8.60 GiB/s | 8.26 GiB/s | 1.38× | 0.96× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_STRING | 7.49 GiB/s | 18.01 GiB/s | 17.98 GiB/s | 2.40× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=MIXED | 5.79 GiB/s | 8.41 GiB/s | 8.39 GiB/s | 1.45× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 3.07 GiB/s | 3.47 GiB/s | 3.61 GiB/s | 1.18× | 1.04× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 3.21 GiB/s | 3.84 GiB/s | 4.00 GiB/s | 1.25× | 1.04× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_STRING | 3.50 GiB/s | 8.49 GiB/s | 8.52 GiB/s | 2.44× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=MIXED | 2.60 GiB/s | 3.58 GiB/s | 3.57 GiB/s | 1.37× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 3.30 GiB/s | 3.64 GiB/s | 3.63 GiB/s | 1.10× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 4.04 GiB/s | 4.54 GiB/s | 4.56 GiB/s | 1.13× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_STRING | 6.36 GiB/s | 10.56 GiB/s | 10.76 GiB/s | 1.69× | 1.02× |
| csv_read_with_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=MIXED | 4.06 GiB/s | 4.69 GiB/s | 4.67 GiB/s | 1.15× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 6.80 GiB/s | 9.56 GiB/s | 9.62 GiB/s | 1.41× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 6.43 GiB/s | 10.85 GiB/s | 10.86 GiB/s | 1.69× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_STRING | 4.36 GiB/s | 16.88 GiB/s | 16.82 GiB/s | 3.86× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=MIXED | 4.90 GiB/s | 9.17 GiB/s | 9.21 GiB/s | 1.88× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 7.73 GiB/s | 10.75 GiB/s | 10.83 GiB/s | 1.40× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 10.97 GiB/s | 15.51 GiB/s | 15.58 GiB/s | 1.42× | 1.00× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_STRING | 10.30 GiB/s | 28.69 GiB/s | 28.90 GiB/s | 2.81× | 1.01× |
| csv_read_with_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=MIXED | 10.97 GiB/s | 15.63 GiB/s | 15.68 GiB/s | 1.43× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 18.66 GiB/s | 26.38 GiB/s | 29.35 GiB/s | 1.57× | 1.11× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 17.43 GiB/s | 38.01 GiB/s | 44.22 GiB/s | 2.54× | 1.16× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=ALL_STRING | 4.91 GiB/s | 26.25 GiB/s | 27.00 GiB/s | 5.50× | 1.03× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=64, type_profile=MIXED | 14.50 GiB/s | 34.73 GiB/s | 40.70 GiB/s | 2.81× | 1.17× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 21.45 GiB/s | 27.69 GiB/s | 31.11 GiB/s | 1.45× | 1.12× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 28.99 GiB/s | 68.07 GiB/s | 90.92 GiB/s | 3.14× | 1.34× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=ALL_STRING | 20.79 GiB/s | 67.95 GiB/s | 67.80 GiB/s | 3.26× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=DEVICE_BUFFER, num_cols=8, type_profile=MIXED | 29.16 GiB/s | 68.79 GiB/s | 90.24 GiB/s | 3.09× | 1.31× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_FLOAT | 5.12 GiB/s | 6.77 GiB/s | 6.71 GiB/s | 1.31× | 0.99× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_INTEGRAL | 5.02 GiB/s | 7.86 GiB/s | 7.93 GiB/s | 1.58× | 1.01× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=ALL_STRING | 4.78 GiB/s | 13.91 GiB/s | 13.95 GiB/s | 2.92× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=64, type_profile=MIXED | 4.60 GiB/s | 7.17 GiB/s | 6.99 GiB/s | 1.52× | 0.98× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_FLOAT | 5.29 GiB/s | 6.94 GiB/s | 6.83 GiB/s | 1.29× | 0.98× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_INTEGRAL | 6.01 GiB/s | 8.79 GiB/s | 9.02 GiB/s | 1.50× | 1.03× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=ALL_STRING | 11.19 GiB/s | 19.91 GiB/s | 19.67 GiB/s | 1.76× | 0.99× |
| csv_read_without_inference_io | data_size_mb=256, io=FILEPATH, num_cols=8, type_profile=MIXED | 6.39 GiB/s | 8.59 GiB/s | 8.79 GiB/s | 1.38× | 1.02× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 3.75 GiB/s | 3.82 GiB/s | 3.96 GiB/s | 1.05× | 1.04× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 3.79 GiB/s | 4.43 GiB/s | 4.48 GiB/s | 1.18× | 1.01× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=ALL_STRING | 4.25 GiB/s | 9.35 GiB/s | 8.92 GiB/s | 2.10× | 0.95× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=64, type_profile=MIXED | 3.33 GiB/s | 4.04 GiB/s | 3.72 GiB/s | 1.12× | 0.92× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 3.90 GiB/s | 3.96 GiB/s | 4.01 GiB/s | 1.03× | 1.01× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 4.30 GiB/s | 4.68 GiB/s | 4.60 GiB/s | 1.07× | 0.98× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=ALL_STRING | 8.50 GiB/s | 11.22 GiB/s | 10.93 GiB/s | 1.29× | 0.97× |
| csv_read_without_inference_io | data_size_mb=256, io=HOST_BUFFER, num_cols=8, type_profile=MIXED | 4.49 GiB/s | 4.84 GiB/s | 4.61 GiB/s | 1.03× | 0.95× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_FLOAT | 9.60 GiB/s | 11.48 GiB/s | 11.45 GiB/s | 1.19× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_INTEGRAL | 9.67 GiB/s | 14.00 GiB/s | 13.97 GiB/s | 1.44× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=ALL_STRING | 5.47 GiB/s | 19.59 GiB/s | 19.56 GiB/s | 3.57× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=64, type_profile=MIXED | 7.96 GiB/s | 12.69 GiB/s | 12.72 GiB/s | 1.60× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_FLOAT | 10.51 GiB/s | 11.69 GiB/s | 11.74 GiB/s | 1.12× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_INTEGRAL | 12.94 GiB/s | 17.19 GiB/s | 17.28 GiB/s | 1.34× | 1.00× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=ALL_STRING | 17.06 GiB/s | 33.18 GiB/s | 33.38 GiB/s | 1.96× | 1.01× |
| csv_read_without_inference_io | data_size_mb=256, io=PINNED_BUFFER, num_cols=8, type_profile=MIXED | 12.86 GiB/s | 17.18 GiB/s | 17.23 GiB/s | 1.34× | 1.00× |
CSV_READER_TYPE_INFERENCE_NVBENCH (32 configs)
| Benchmark | Config | Base | Parent | This PR | vs base | vs parent |
|---|---|---|---|---|---|---|
| csv_read_with_inference | data_size_mb=256, num_cols=64, type_profile=ALL_FLOAT | 10.18 GiB/s | 18.02 GiB/s | 19.39 GiB/s | 1.90× | 1.08× |
| csv_read_with_inference | data_size_mb=256, num_cols=64, type_profile=ALL_INTEGRAL | 9.10 GiB/s | 21.17 GiB/s | 23.62 GiB/s | 2.60× | 1.12× |
| csv_read_with_inference | data_size_mb=256, num_cols=64, type_profile=ALL_STRING | 4.40 GiB/s | 22.01 GiB/s | 22.51 GiB/s | 5.11× | 1.02× |
| csv_read_with_inference | data_size_mb=256, num_cols=64, type_profile=MIXED | 6.80 GiB/s | 17.23 GiB/s | 18.54 GiB/s | 2.73× | 1.08× |
| csv_read_with_inference | data_size_mb=256, num_cols=8, type_profile=ALL_FLOAT | 12.32 GiB/s | 22.50 GiB/s | 24.76 GiB/s | 2.01× | 1.10× |
| csv_read_with_inference | data_size_mb=256, num_cols=8, type_profile=ALL_INTEGRAL | 21.29 GiB/s | 47.89 GiB/s | 56.98 GiB/s | 2.68× | 1.19× |
| csv_read_with_inference | data_size_mb=256, num_cols=8, type_profile=ALL_STRING | 12.00 GiB/s | 50.02 GiB/s | 51.41 GiB/s | 4.28× | 1.03× |
| csv_read_with_inference | data_size_mb=256, num_cols=8, type_profile=MIXED | 21.64 GiB/s | 47.38 GiB/s | 57.11 GiB/s | 2.64× | 1.21× |
| csv_read_with_inference | data_size_mb=64, num_cols=64, type_profile=ALL_FLOAT | 10.15 GiB/s | 13.50 GiB/s | 14.16 GiB/s | 1.40× | 1.05× |
| csv_read_with_inference | data_size_mb=64, num_cols=64, type_profile=ALL_INTEGRAL | 8.33 GiB/s | 15.72 GiB/s | 16.67 GiB/s | 2.00× | 1.06× |
| csv_read_with_inference | data_size_mb=64, num_cols=64, type_profile=ALL_STRING | 1.42 GiB/s | 7.68 GiB/s | 7.60 GiB/s | 5.34× | 0.99× |
| csv_read_with_inference | data_size_mb=64, num_cols=64, type_profile=MIXED | 4.05 GiB/s | 10.47 GiB/s | 11.11 GiB/s | 2.74× | 1.06× |
| csv_read_with_inference | data_size_mb=64, num_cols=8, type_profile=ALL_FLOAT | 11.10 GiB/s | 18.59 GiB/s | 19.85 GiB/s | 1.79× | 1.07× |
| csv_read_with_inference | data_size_mb=64, num_cols=8, type_profile=ALL_INTEGRAL | 18.41 GiB/s | 36.50 GiB/s | 40.37 GiB/s | 2.19× | 1.11× |
| csv_read_with_inference | data_size_mb=64, num_cols=8, type_profile=ALL_STRING | 5.52 GiB/s | 31.79 GiB/s | 30.05 GiB/s | 5.45× | 0.95× |
| csv_read_with_inference | data_size_mb=64, num_cols=8, type_profile=MIXED | 19.86 GiB/s | 34.44 GiB/s | 40.57 GiB/s | 2.04× | 1.18× |
| csv_read_without_inference | data_size_mb=256, num_cols=64, type_profile=ALL_FLOAT | 18.10 GiB/s | 26.35 GiB/s | 29.45 GiB/s | 1.63× | 1.12× |
| csv_read_without_inference | data_size_mb=256, num_cols=64, type_profile=ALL_INTEGRAL | 17.51 GiB/s | 38.19 GiB/s | 44.44 GiB/s | 2.54× | 1.16× |
| csv_read_without_inference | data_size_mb=256, num_cols=64, type_profile=ALL_STRING | 4.70 GiB/s | 27.21 GiB/s | 27.37 GiB/s | 5.82× | 1.01× |
| csv_read_without_inference | data_size_mb=256, num_cols=64, type_profile=MIXED | 14.57 GiB/s | 35.21 GiB/s | 40.67 GiB/s | 2.79× | 1.16× |
| csv_read_without_inference | data_size_mb=256, num_cols=8, type_profile=ALL_FLOAT | 21.55 GiB/s | 27.78 GiB/s | 31.23 GiB/s | 1.45× | 1.12× |
| csv_read_without_inference | data_size_mb=256, num_cols=8, type_profile=ALL_INTEGRAL | 29.30 GiB/s | 68.18 GiB/s | 90.42 GiB/s | 3.09× | 1.33× |
| csv_read_without_inference | data_size_mb=256, num_cols=8, type_profile=ALL_STRING | 21.92 GiB/s | 65.06 GiB/s | 67.65 GiB/s | 3.09× | 1.04× |
| csv_read_without_inference | data_size_mb=256, num_cols=8, type_profile=MIXED | 29.24 GiB/s | 68.05 GiB/s | 89.83 GiB/s | 3.07× | 1.32× |
| csv_read_without_inference | data_size_mb=64, num_cols=64, type_profile=ALL_FLOAT | 15.91 GiB/s | 18.32 GiB/s | 19.64 GiB/s | 1.23× | 1.07× |
| csv_read_without_inference | data_size_mb=64, num_cols=64, type_profile=ALL_INTEGRAL | 14.41 GiB/s | 26.37 GiB/s | 29.34 GiB/s | 2.04× | 1.11× |
| csv_read_without_inference | data_size_mb=64, num_cols=64, type_profile=ALL_STRING | 1.44 GiB/s | 8.64 GiB/s | 8.25 GiB/s | 5.74× | 0.96× |
| csv_read_without_inference | data_size_mb=64, num_cols=64, type_profile=MIXED | 9.73 GiB/s | 23.73 GiB/s | 26.17 GiB/s | 2.69× | 1.10× |
| csv_read_without_inference | data_size_mb=64, num_cols=8, type_profile=ALL_FLOAT | 18.58 GiB/s | 22.56 GiB/s | 25.20 GiB/s | 1.36× | 1.12× |
| csv_read_without_inference | data_size_mb=64, num_cols=8, type_profile=ALL_INTEGRAL | 25.71 GiB/s | 47.25 GiB/s | 56.39 GiB/s | 2.19× | 1.19× |
| csv_read_without_inference | data_size_mb=64, num_cols=8, type_profile=ALL_STRING | 7.32 GiB/s | 38.00 GiB/s | 36.16 GiB/s | 4.94× | 0.95× |
| csv_read_without_inference | data_size_mb=64, num_cols=8, type_profile=MIXED | 25.23 GiB/s | 48.64 GiB/s | 56.48 GiB/s | 2.24× | 1.16× |
Summary
datasource::supports_zero_copy_device_read()(false by default), so that readers can tell a device read that returns a view of data already in device memory from one that copies. Device buffer sources return true, and user sources can opt in."") are unescaped into reader-owned memory, never into the caller's buffer. Scratch memory is allocated only when strings may need it, so peak memory never exceeds that of the old copy.Four commits:
Motivation
For a device buffer source, the reader copied its whole input with a device-to-device copy before parsing, because decoding unescapes quoted strings in place. That copy took about 0.75 ms for the 554 MB TAXI input (about 14% of the read), and it held device memory equal to the input size for the whole read.
Design
supports_zero_copy_device_read()covers onlydevice_read(offset, size, stream), the variant that returns a buffer, and only matters whensupports_device_read()is true.stream, and that it stays valid and unmodified until the buffer is destroyed and the reader's work onstreamis complete.datasource::create(device_span)now say that readers may process the buffer in place, and that the buffer must hold the data when a read starts and must not be modified or freed until the read completes. Host reads of the source are not ordered on the read's stream.device_read(offset, size, stream)now also allows calling it when the source supports zero-copy device reads.is_device_read_preferred()holds, since a view involves no read. Empty ranges (a BOM-only input) are not requested from the source.supports_zero_copy_device_readfollowssupports_device_read()and Arrow'sRandomAccessFile::supports_zero_copy(). It describes a capability of the source, not a per-size preference likeis_device_read_preferred().dynamic_caston the returned buffer type: fragile, and it cannot tell a copying source whose buffer the reader could write.const_castto unescape.unescape_bufferat the same offsets. That buffer is the data itself for reader-owned copies (in place, as before) or scratch memory for views.detect_whitespace_around_quotes).Correctness
[data, data + size)of the caller's buffer:--rmm_mode=cuda) is clean with rows staged in shared memory and without (LIBCUDF_CSV_ROW_STAGING=ALWAYS|NEVER).Testing
"", CRLF and no final terminator;""pairs around slice, word and tile boundaries;DATASOURCE_TEST);Performance (local, RTX PRO 6000, 256 MB, minimum / median of 5 interleaved runs on a shared GPU; official best-of-2 numbers for all sources are in the benchmark comment below)
Notes for reviewers
supports_zero_copy_device_read()adds a virtual function tocudf::io::datasource. Out-of-tree subclasses (for example user datasources,kafka_consumer, and the JNI datasources) keep compiling, since the default is false, but must be rebuilt.datasource_chunk_reader(data_chunk_source_factories.cpp), which copies device chunks today;cudftable.cpp), which copies the packed data of device sources;The JSON reader would not benefit: it normalizes its input in place and concatenates sources.
"", and ANALYTICS and QUOTE_0 have no string column. It costs about 1% of the row-gathering pass.host_reads from device buffer sources avoid pageable device-to-host copies. I measured about 13 µs per read, and two such reads per call (about 0.5% of TAXI at 256 MB), so this PR intentionally leaves them unchanged. The raw run's 230–290 µs per read came from waiting for other work on a shared GPU.This is PR 6 of a stack of draft PRs extracted from an automated (CORAL) optimization run of the CSV reader, reviewed by a separate principal-engineer review pass; it builds on #20.
🤖 Generated with Claude Code