July 15, 2026
As applications process increasingly larger data objects and modern storage devices provide ever greater parallelism, large-buffer reads are becoming an important I/O method connecting upper-layer data processing with high-speed storage. In modern systems where multiple processes, containers, and service instances coexist, the page cache continues to provide data sharing and reuse across instances. Adapting it to larger read granularities and greater storage parallelism is therefore not merely a local performance issue for individual applications, but also a matter of system-wide resource efficiency. Large buffers not only amortize the fixed overheads of system calls and the VFS, but also provide the kernel with a larger window for organizing work and submitting I/O. However, Linux buffered read primarily exploits only the former benefit. As the range of a single read grows, numerous fine-grained page-cache operations are concentrated within that read. The conventional interleaved path amplifies metadata-access and serial orchestration overheads, and struggles to consistently translate the larger read window into a concentrated set of in-flight requests, thereby failing to fully utilize the parallelism of modern SSDs.
This paper presents MARS, a multi-stage accelerated read stack for synchronous large-buffer buffered reads. MARS treats a large-range read as a complete unit of work within the kernel and stages page-cache operations according to their data structures and dependencies. It exploits the I/O wait window before data becomes ready to handle user-buffer page faults and perform reorderable data copies early. It then uses opportunistic kernel workers to copy the remaining data in parallel and, when the storage backend provides sufficient parallelism, optionally submits I/O in parallel. We implement MARS in Linux 6.6.58. Experiments show that, for MiB-scale fio reads, MARS improves bandwidth by up to 6.56\(\times\) over Linux. On a RAID0 array of five NVMe SSDs, it achieves 36.87 GiB/s for 128 MiB random reads, 4.44\(\times\) the bandwidth of Linux. In real applications, MARS accelerates DuckDB/Parquet queries and ExecuTorch model loading by 1.80–2.15\(\times\) and 3.17–3.61\(\times\), respectively.
Modern data-intensive applications are moving file data at increasingly larger granularity. AI model weights, multimodal inputs, video, images, speech data, scientific observation data, columnar tables, training samples, and checkpoint files are often stored as large files and read into user space in batches at runtime. At the same time, the data objects processed by applications themselves are continuously growing: video and image objects are evolving toward higher resolution, while observation, simulation, and sequencing data in scientific applications are also expanding. As data objects grow larger, using larger buffers and larger transfer granularity to amortize the fixed cost of the I/O stack becomes a natural system design choice.
Meanwhile, the underlying storage hardware has also changed significantly. This change is reflected not only in lower access latency, but also in the ability of modern storage devices to process a large number of requests concurrently under sufficient queue depth. The wide deployment of NVMe SSDs, PCIe multi-queue devices, and multi-disk RAID arrays gives storage backends much higher parallel processing capability than in the traditional disk era. For example, KIOXIA’s CM7 series is rated at up to 2.7 million 4-KiB random-read IOPS[1], indicating that fully releasing the performance of high-speed storage devices increasingly depends on whether the host side can continuously provide enough outstanding requests. For such storage backends, the key question is no longer only whether the latency of a single device access is low enough, but whether the operating system can provide enough sufficiently concentrated requests at the right time, so as to form effective queue depth and release backend parallelism. Therefore, the meaning of a large-buffer read has also changed: it is not only a way to reduce the number of system calls, but also provides the kernel with a larger I/O submission window, giving the kernel an opportunity to derive more I/O requests that can be submitted in parallel from a single request.
The third change comes from the memory system. Single-node server memory capacity continues to grow, from hundreds of GB in earlier dual-socket servers to several TB supported by a single socket in newer server platforms; the memory system is carrying larger working sets. At the same time, remote memory, CXL, and memory pooling technologies further expand the amount of memory that the system can manage. Larger memory capacity makes it easier for applications to use large buffers to access large files, and also gives the operating-system page cache enough space to retain and reuse data over larger ranges.
These changes have already made large-buffer access a practical pressure that the current I/O stack must face, and this pressure will continue to grow as data objects, storage parallelism, and memory capacity keep increasing. Larger data objects require larger transfer granularity, higher-parallelism storage backends require larger submission windows, and larger memory systems allow the operating system to carry larger cache working sets. Therefore, large-buffer file access is a long-term trend jointly driven by upper-layer applications and lower-layer hardware.
The traditional value of a large-buffer read lies in reducing the number of times an application crosses the operating-system I/O stack. For a fixed total amount of data, increasing the transfer size of each read can amortize the cost of
system-call entry, parameter checking, VFS dispatch, and part of page-cache management. Similar ideas also appear in various I/O aggregation and batched submission mechanisms, such as scatter-gather/vector I/O interfaces, MPI-IO request aggregation, and
the batched submission and completion path of io_uring. They all show, from different layers, that the fixed cost caused by frequently crossing the kernel I/O stack is a problem that modern I/O systems must address.
Our experiments also confirm this point. As shown in Figure 1, Linux buffered random read improves rapidly as the read size increases in the small-request range: bandwidth increases from 54.46 MiB/s with
4 KiB requests to 868.6 MiB/s with 2 MiB requests. This also explains why mechanisms such as vector I/O, user-space I/O aggregation, MPI-IO aggregation, and io_uring batched submission have long focused on reducing the number of requests and
kernel entries: in the small-to-medium request range, amortizing fixed path costs can indeed bring significant benefits. However, when the request size continues to increase to 8 MiB, 32 MiB, and 128 MiB, Linux bandwidth basically stays at 869–875 MiB/s.
This plateau shows that large buffers do solve the fixed-cost problem of crossing the I/O stack many times, but they do not automatically solve the problem of how to organize page-cache work inside a single large read. In other words, a large buffer has
two roles: reducing the number of I/O-stack crossings, and providing a larger in-kernel work window. Existing Linux buffered read has made good use of the former, but has not fully utilized the latter; that is, it has not sufficiently translated a larger
request window into better page-cache path organization, more concentrated I/O submission, and higher utilization of backend parallelism.
This paper chooses to study the Linux page-cache path rather than simply bypassing it. The page cache has long been the core mechanism of buffered read/write. It provides transparent caching, readahead, unified consistency semantics, and compatibility with the standard POSIX file interface. More importantly, in modern server environments, the page cache is not merely an acceleration layer for a single process, but a system-level data reuse mechanism. Once a piece of file data enters the page cache, it can be reused by multiple processes, containers, and service instances under the same kernel. In virtualized data paths involving shared images and the host file system, the page cache can also affect the reuse efficiency of the same read-only data across multiple instances. If every application, container, or agent bypasses the page cache and instead uses Direct I/O, user-space storage stacks, or private caches, then the local I/O path of an individual thread may become faster, but the whole system may pay the cost of duplicated caching, memory waste, fragmented caching policies, and increased operational complexity. Therefore, in an era where multiple processes, containers, tenants, and agents coexist, making the shared page-cache path adapt to large buffers and highly parallel storage is not merely a local performance problem for a single application, but also a system-level resource-efficiency problem.
However, the plateau of existing Linux buffered read exposes two structural limitations. First, Linux can amortize the fixed cost of system calls and VFS by increasing the read size, but once requests enter the large-buffer range, the bottleneck shifts to the software organization cost inside a single read. A large read appears to the user as only one system call, but inside the kernel it is still decomposed into many fine-grained operations, including page-cache management, page allocation, index updates, I/O submission, waiting, and data copying. The generic Linux path interleaves these operations in a control flow designed for general workloads. This design is simple and robust, but in large-buffer scenarios it amplifies metadata accesses, cache-locality loss, and serial organization overhead.
Second, a large read itself provides a larger potential I/O submission window, but the existing synchronous buffered read path cannot always translate this window into a sufficiently deep device queue. Ideally, the kernel should be able to construct and submit a batch of I/O requests from a single large request, so that modern NVMe devices or multi-disk arrays can obtain enough outstanding work. However, Linux synchronous buffered read is still driven by a generic loop, where I/O submission, waiting, and copying are interleaved with other page-cache operations. As a result, it is difficult to stably translate the large window provided by the application into more concentrated I/O submission and more effective backend-parallelism utilization.
Synchronous read also exposes another long-standing problem: waiting. For a cache-miss-heavy large-buffer read, the issuing thread must wait for the underlying I/O to complete before it can continue data copying and return to user space. The mainstream
path of high-performance I/O exposes this waiting to applications, allowing applications to overlap waiting time with other I/O or computation through asynchronous I/O, io_uring, polling, buffer registration, and application-level pipelines.
This path is effective, but it also moves complexity upward into user space. Applications not only need to manage queue depth, buffer lifetime, and completion-event matching, but also need to maintain I/O/computation pipelines and bear higher debugging,
observability, and performance-attribution costs. For carefully designed storage runtimes or deep-learning systems, this complexity may be acceptable. However, a large number of applications have already been built on the ecosystem of synchronous
read/write and page cache. They cannot be rewritten into asynchronous pipelines merely for a single I/O hotspot, nor can they easily give up the sharing and reuse capability provided by the page cache. More importantly, asynchronous I/O can hide waiting at
the application level, but it does not directly shorten the service time of a single synchronous buffered read itself.
Therefore, this paper explores a complementary path: instead of handing all waiting and scheduling complexity to applications, it lets the kernel absorb part of the work inside synchronous buffered read. This paper focuses on the following question: in
the era of high-speed storage, multicore systems, multiple instances, and large data objects, can traditional synchronous buffered read continue to serve as a simple, shared, and general I/O interface while approaching modern hardware capability? More
specifically, when an application has already issued a large-buffer read, can the kernel use the work granularity and waiting window already present inside this request, while remaining compatible with the synchronous read ecosystem and
preserving page-cache sharing capability, so that the page-cache path can adapt to large buffers and high-speed parallel storage, and reduce the service time of synchronous read as exposed to the application?
Such reorganization cannot simply mechanically merge multiple steps. Page-cache read is responsible for cache sharing, concurrency coordination, and read completion at the same time. Any internal reordering must correctly handle cache hits, cache misses, not-yet-ready pages, concurrent insertion, user-buffer faults, and copy progress. In addition, naively running several complete read paths in parallel would increase contention on shared page-cache structures and would not necessarily expose a deeper storage queue. The challenge is therefore to identify the points in a large read that can be staged, moved, overlapped, or parallelized without turning the synchronous read interface into an application-managed asynchronous pipeline.
This paper presents MARS, a Multi-stage Accelerated Read Stack for large-buffer synchronous buffered read. MARS does not require applications to rewrite their I/O path into an asynchronous pipeline. Instead, it treats one large read as a sufficiently large in-kernel work unit, and reorganizes the originally interleaved work in the page-cache layer into a staged pipeline. MARS first scans the XArray in a concentrated manner and classifies existing up-to-date folios, missing ranges, and not-yet-up-to-date folios. It then batch-allocates folios, batch-inserts them into the page cache, and centrally assembles and submits I/O. During storage waiting, MARS overlaps user-buffer fault-in and some reorderable early/out-of-order copy work with the waiting window. For work that still remains on the critical path, MARS uses lightweight in-kernel worker offloading to accelerate copying-related stages, and when the backend has sufficient parallelism, it further releases device parallelism through optional parallel I/O submission.
The core idea of MARS is not to bypass the page cache, nor to push all I/O complexity to applications, but to reorganize the execution order of a single synchronous large read inside the kernel. Here, a large buffer is not only a parameter passed by the application; it is also the granularity for batching, overlapping, and parallelization inside the kernel. In this way, MARS directly reduces part of the software organization overhead, moves another part of unavoidable work into the device waiting window, and exposes more concentrated I/O work to storage devices when the backend provides parallelism, thereby reducing the waiting and software organization costs exposed on the critical path of synchronous read.
The contributions of this paper are as follows:
We propose a staged execution and cost-attribution framework for large-buffer buffered read, and use it to characterize the bottleneck shift in Linux buffered read. Increasing the read size can amortize the fixed cost of system calls and VFS for small to medium requests, but after the MiB-scale region, Linux buffered random read quickly enters a bandwidth plateau. To explain this phenomenon, we decompose a large read into stages including XArray lookup/classification, folio allocation, page-cache insertion, I/O submission, PG_uptodate wait, user-buffer fault-in, and copy, and analyze the dependency relationships, data-structure locality, and reorderability of each stage. This framework transforms the performance problem of large-buffer read from “the request is not large enough” into the problem of “how to organize page-cache work inside a single large read”, and provides the basis for the subsequent design.
We propose the staged page-cache pipeline of MARS. MARS reorganizes lookup, classification, allocation, insertion, I/O submission, wait, and copy according to data-structure locality and dependency relationships, reducing the software organization overhead caused by interleaved execution and forming a more concentrated I/O submission window.
We propose a waiting-window hiding mechanism for synchronous buffered read. Using the storage waiting window that already exists in cache-miss-heavy large reads, MARS overlaps user-buffer fault-in, early/out-of-order copy, and other reorderable work with the waiting window. Inside the kernel, it hides part of the software work and waiting cost that would otherwise be exposed on the critical path of synchronous read.
We design lightweight in-kernel offloading and optional parallel I/O submission mechanisms. For copy and I/O submission work that still remains on the critical path, MARS uses lightweight worker offloading to reduce the serial burden on the synchronous calling thread. When the underlying backend has higher parallelism, MARS further releases the outstanding-request processing capability of NVMe/RAID0 through optional parallel submission. This allows MARS to reduce critical-path software cost on a single device, and further release storage parallelism on multi-device/RAID0 backends.
Experimental results show that MARS achieves up to 6.56\(\times\) bandwidth improvement in fio large-buffer random read, and exceeds 5.5\(\times\) for all tested requests of 16 MiB and above. Equivalently, for fixed-size large reads, the per-request service time is reduced by about 82%–85%. With only the staged page-cache pipeline enabled, the measured path time of a 16 MiB read already decreases from 10.04 ms to 3.97 ms. In the same mechanism experiment, the wait/uptodate-attributed time decreases from 4.48 ms to 2.86 ms. On a 5-NVMe RAID0 array, MARS increases 128 MiB random-read bandwidth to 4.44\(\times\) that of Linux through concentrated/parallel I/O submission, reaching 36.87 GiB/s. In real applications, MARS accelerates DuckDB/Parquet queries by 1.80\(\times\)–2.15\(\times\), ExecuTorch model loading by 3.17\(\times\)–3.61\(\times\), and TensorFlow TFRecord input pipelines by 1.33\(\times\)–1.37\(\times\).
To facilitate reproducibility and further research, we will open-source MARS and the scripts used in our evaluation upon acceptance at a peer-reviewed venue.
The rest of this paper is organized as follows. Section 2 introduces the Linux buffered read path, the motivation for large-buffer access, and the plateau phenomenon of the existing path. Section 3 introduces the staged page-cache pipeline, waiting-window hiding, and lightweight in-kernel offloading mechanisms of MARS. Section 4 presents microbenchmarks, mechanism analysis, ablation experiments, and real-application evaluation. Section 5 compares related work, and Section 6 concludes.
In Linux, a synchronous buffered read() traverses the VFS, page cache, file-system layer, block layer, device driver, and storage device. The VFS dispatches a file descriptor to the corresponding file object and read path; the page cache
looks up or creates the cache pages that hold file data; the file system maps file offsets to underlying block addresses; the block layer organizes, merges, and schedules I/O requests; and the device driver ultimately submits those requests to the device.
After the device completes the I/O, it notifies the host through interrupts or polling. The completion path then propagates back to the page cache, and only after the data becomes ready can the read path continue copying it into the user buffer.
The page cache is the core layer studied in this work. Linux uses an XArray to maintain the mapping from file-page indices to folios. The XArray can be viewed as a tree-based index in the page cache that locates cached objects according to file offsets.
A folio is the basic page-cache object that carries file data and page state; it may cover one or more contiguous pages and records the state associated with that data range. On a cache miss, a new folio is typically allocated from the buddy allocator.
PG_uptodate is a key state flag in the read path and indicates whether the data in a folio is valid. A newly allocated or still-being-filled folio cannot be copied safely. The upper-level read path can access its data only after the
lower-level I/O completion path marks it up-to-date and unlocks it.
At a high level, a buffered read proceeds as follows. The kernel first queries the XArray using the file offset. If the target folio already exists and is up-to-date, its contents are copied directly into the user buffer. If the folio is missing, the
kernel allocates a new folio, inserts it into the XArray, and submits a read request through the block layer and device driver. After the device completes the request, the completion callback updates the folio state and sets PG_uptodate. The
waiting read path then observes that the data is ready and can finally perform the copy and return. If the folio already exists but is not yet up-to-date, the current read must not issue a duplicate I/O request; instead, it waits for the existing fill
operation to complete.
A MiB-scale read covers many folios. Increasing the read size reduces the number of system calls, but it does not eliminate the XArray, allocation, submission, waiting, and copy work within each large read. In other words, a large buffer both exposes the internal organization costs of the page-cache path and provides a larger in-kernel work window that can be exploited through batching and parallelism.
To analyze the main costs within this window, we use eBPF to attribute the key stages of a native Linux 16 MiB buffered read, as shown in Figure 2. The bars report eBPF-attributed time for selected key paths and are intended to reflect the relative magnitude of the major costs; they are not a complete or mutually exclusive decomposition of end-to-end latency.
Total path denotes the main-path time of one 16 MiB read. Wait/uptodate denotes the data-readiness wait observed at the page-cache layer: the I/O has already been submitted, but the target folio has not yet been filled and marked up-to-date by the device and completion path, so the read path must continue waiting. Folio allocation denotes obtaining a new folio from the page allocator. XArray lookup and XArray insertion denote querying and publishing page-cache index entries, respectively. I/O submit denotes constructing and submitting lower-level read requests. Copy to user denotes copying data from folios into the user buffer. The results show that the total path takes about 10 ms, while Wait/uptodate is the largest individual component at about 4.5 ms. In addition to waiting, folio allocation, XArray insertion, copy to user, I/O submission, and XArray lookup also contribute visible software costs.
Wait/uptodate is important not only because it is the largest component in the figure, but also because it directly explains why synchronous I/O-intensive applications often exhibit low CPU utilization. The calling thread blocks until the data becomes
ready, so it can neither continue the remaining copy work of the current read nor execute subsequent application computation. The basic idea behind asynchronous I/O, io_uring, and application-level pipelines is to expose this waiting period to
the application so that other requests or computation can overlap device response time. MARS follows a complementary approach: it does not require applications to be rewritten as asynchronous pipelines, but instead attempts to exploit the same waiting
window inside a synchronous buffered read. At the same time, when CPU resources are available, MARS can use lightweight workers to process kernel work that can be executed early or in parallel.
Based on this cost breakdown, MARS divides the costs of a large buffered read into three categories. The first category is metadata-management cost, including XArray lookup and insertion, folio allocation, and page-cache state maintenance. These operations repeatedly access a limited set of kernel metadata and therefore offer opportunities to improve locality through concentrated execution; accordingly, MARS uses a staged and batched page-cache pipeline to reduce these costs. The second category is data-movement cost, including user-buffer page-fault handling and copying data from folios to the user buffer. These costs primarily scale with the amount of data and cannot be eliminated by metadata batching. MARS therefore moves device-independent fault-in work and part of the early copy into the waiting window, and parallelizes the copy work that remains exposed in end-to-end latency. The third category is storage-wait cost, represented by Wait/uptodate. This cost is strongly related to device processing time, but it also depends on whether the kernel can provide enough outstanding requests to a highly parallel backend. MARS therefore uses concentrated I/O submission to turn the large single-request window into a more concentrated supply of block-layer requests and more in-flight I/O, and optionally performs parallel submission when the backend provides sufficient parallelism, thereby reducing the waiting time exposed by synchronous reads. These observations motivate MARS to treat a large buffered read as an in-kernel work unit and to reorganize its metadata management, data movement, and I/O submission around batching, overlap, and parallelism.
As discussed above, although a large-buffer read reduces the number of system calls, it still internally triggers a large number of fine-grained operations, including XArray lookup and insertion, folio allocation, page-cache state maintenance, I/O submission, and data copying. The XArray is a multi-level index structure derived from the radix tree, and its lookup and insertion operations repeatedly access index nodes and their slots. Folio allocation accesses the free lists and related page metadata organized by zone, order, and migration type in the buddy allocator. Relative to MiB-scale user data, this control metadata forms a smaller, repeatedly accessed working set. However, the existing Linux path interleaves these metadata operations with block-layer processing and user-data copying while processing one or a small number of folios. Consequently, the kernel frequently switches among the data structures it accesses, while large-scale data copying disrupts metadata locality. Rather than replacing existing kernel data structures, MARS concentrates lookup, allocation, insertion, submission, waiting, and copying into their own dedicated execution stages. Meanwhile, before entering the waiting stage, this staged processing accumulates a batch of folios to be read, creating a more concentrated I/O submission window for the block layer.
To obtain a complete view of the page-cache state for a single read, MARS first scans the index interval in the address_space XArray corresponding to the requested range of file pages and constructs three logical work sets. Set A contains
ready cached folios, namely, folios that already exist in the XArray and are marked PG_uptodate. They require no read I/O and can directly participate in subsequent copying. Set B contains missing index ranges, namely, contiguous index
intervals within the requested file-page range that miss in the page cache. These ranges must undergo folio allocation, XArray insertion, and I/O submission before their file data becomes available. Shadow entries likewise contain no resident folio that
can be read, so MARS classifies them into Set B. During subsequent insertion, the existing filemap_add_folio() path replaces a shadow entry with a new folio while preserving Linux’s original workingset-refault handling; MARS therefore does not
need to create a fourth downstream set for shadow entries. Set C contains resident not-ready folios, namely, folios that already exist in the XArray but are not yet PG_uptodate. They must either wait for an existing fill operation or enter the
I/O reissue path after their state is revalidated.
Figure 3 shows how Sets A, B, and C flow through the six execution stages. In the Classify stage, MARS scans the XArray and constructs the three logical work sets described above. In the Allocate stage, MARS traverses all missing ranges in Set B and collectively prepares folios for the file pages that can be handled in the current iteration. Only after allocation processing for the entire set completes does execution enter the Insert stage. This stage traverses all folios prepared by the previous stage and inserts them into the XArray according to their file-page indices. The Submit stage then processes the output of the Insert stage, organizing and issuing the underlying read requests in a concentrated manner according to the file-system and readahead windows. In the Wait stage, MARS brings together the folios in Set B for which read requests have been issued and the existing not-ready folios in Set C. For the latter, MARS either waits for an existing in-flight I/O or sends the folio to the reissue path according to the result of state revalidation. Finally, in the Copy stage, MARS copies Set A, the ready folios in Set C, and the filled folios produced from Set B to their corresponding locations in the user buffer. This process changes the temporal organization of the operations while continuing to reuse Linux’s existing XArray, folio, filemap/readahead, and block-layer read mechanisms.
To ensure the correctness of this internal reordering, MARS maintains the following invariants across the stages. First, MARS allows only folios that have passed the PG_uptodate check to participate in copying. Second, MARS does not
uniformly treat the folios in Set C as new cache misses. When another execution flow is filling a folio, the current request synchronizes with that flow through the folio lock and rechecks the data state after acquiring the lock or completing the wait.
Only a folio that remains not ready after revalidation and must be read again enters the reissue path, thereby avoiding duplicate submission for an existing in-flight I/O. Finally, the Copy stage limits the actual copy range to the valid file interval.
When a request extends beyond the end of the file, MARS returns only the bytes actually read within \([\texttt{ki\_pos},\texttt{i\_size})\), thereby correctly handling a short read at EOF.
This staged reorganization derives its benefits primarily from two sources. First, MARS executes XArray lookup and insertion, buddy allocation, and page-cache state maintenance in concentrated phases, reducing frequent switching among different metadata structures and preventing large-scale user-data copying from prematurely disrupting the metadata working set. This reduces metadata-organization time in the page-cache path. Second, MARS issues read requests in a concentrated manner before entering the waiting stage, allowing more requests to be in flight concurrently and thereby shortening the data-readiness wait observed by the page-cache layer. The higher the parallelism of the underlying device, the more opportunities for device-level parallelism concentrated submission can expose and exploit.
For large-buffer reads that miss in the page cache, waiting for the storage device to complete data retrieval typically dominates end-to-end latency. Asynchronous I/O interfaces expose this wait to users, allowing applications to construct pipelines of computation and I/O. MARS instead fills the existing wait window with kernel computations that do not depend on data readiness, thereby overlapping software processing with device I/O. Specifically, MARS handles user-buffer page faults in advance and copies data that are already ready, while also using the same window to prebuild XArray nodes for subsequent file ranges.
The first operation handles user-buffer page faults in advance. When the target user pages are not resident, copying file data into the user buffer triggers page-fault handling, including allocating physical pages, establishing page-table mappings, and checking whether the target range is writable. If this work does not begin until the Copy stage, its cost is serialized with the actual data copy. However, filling a page-cache folio with file data does not depend on whether the target user page is resident; similarly, establishing a writable mapping for the user buffer does not depend on whether the device has returned the file data. Therefore, after submitting the read I/O for the current request, MARS proactively triggers fault-in for the target user pages. This moves page-fault handling into the window in which MARS waits for the folios to become ready, so the final Copy stage no longer incurs this cost serially.
The second operation copies ready data early during the wait. While the read path is waiting for some not-ready folios, other data covered by the same read may already be eligible for copying. These data include cache-hit folios that are already ready
in the Classify stage (Set A), as well as folios whose fills complete during the wait (Sets B and C). The existing Linux buffered-read path does not copy ready folios early while waiting for the remaining data to become ready. MARS instead checks the state
of each folio during the wait. Once a folio has passed the PG_uptodate check and can be accessed safely, MARS copies its data to the corresponding position in the user buffer according to its file offset, thereby overlapping the copy with the
data-readiness wait for the remaining folios.
To copy data out of order while preserving the progress semantics of a sequential read, MARS separates the physical data copy from the logical advancement of the read. During early copying, MARS constructs a temporary iov_iter from the
folio’s file offset and uses it to locate the target region in the user buffer. It does not advance the main iov_iter or modify the file position recorded in the iocb; meanwhile, the system separately records the ranges already
copied early from the three sets. Upon entering the final Copy stage, MARS still checks, in file-offset order, the contiguous successful prefix that can be returned. For folios that have already been copied, this stage advances only the main iterator, file
position, and completed byte count, without copying the data again. For folios that are ready but have not been copied early, it performs the normal data copy. Consequently, physical data can be written to their target positions in the order in which the
folios actually become ready, while the system-call return value and file position are still committed according to a contiguous file prefix.
Out-of-order copying introduces a behavioral difference when a lower-level I/O error occurs. Linux copies in file-offset order and accounts only for the successfully copied contiguous prefix in the return value, so the user buffer beyond that prefix normally remains untouched. Before detecting an error at an earlier offset, MARS may have already copied later-ready folios; it still preserves the return-value and file-position semantics of a contiguous prefix, but may modify the user buffer beyond that prefix. Users requiring Linux’s sequential-copy behavior can disable this optimization through debugfs.
In addition to the two primary mechanisms serving the current request, MARS also uses the wait window for auxiliary XArray prebuilding. After submitting the I/O for the current request, MARS uses the current access position to create, in advance, the XArray nodes that may be needed for page-cache insertions in subsequent file ranges, allowing later large-buffer reads to reuse the established tree structure. To avoid repeatedly prebuilding the same region during sequential access, MARS uses a Bloom filter[2] to record prebuilding hints at the granularity of an inode and a 1-GiB file window. It skips a redundant operation when the hint is present; otherwise, it performs the prebuilding and updates the Bloom filter. The Bloom filter participates only in performance decisions: a false positive can at most cause the system to miss one prebuilding opportunity and does not affect read correctness. This idea can also be extended to on-demand index metadata such as file-system extent trees. To avoid further intrusion into file-system implementations, the current prototype prebuilds only the page-cache XArray.
Consider the final Copy stage as an example: it originally handles user-page faults and data copying serially. MARS moves page-fault handling into the wait window, and all cache-hit folios are eligible for early copying. Cache-miss folios can also be copied incrementally as their I/O completes; given a sufficiently long wait, most of this copying can finish before the final Copy stage. Therefore, once all data are ready, the final stage primarily commits the logical progress of data already copied and processes the remaining data that were not copied early, thereby overlapping software work that was originally serialized after the data wait with I/O execution.
A large-buffer read not only amplifies data-readiness waits, but also turns cache-layer operations that were originally short into long serial stages. For example, copying a large number of folios into the user buffer and organizing and submitting read I/O for cache-miss data both contain many independently executable subtasks, yet the existing Linux path normally performs them sequentially in the thread that issued the read. As the buffer grows, this work can make a single CPU the bottleneck of the read path. Meanwhile, while I/O-intensive applications wait for the device, the system may contain CPUs that can provide immediate assistance. MARS therefore divides large cache-layer operations into multiple work units, which are executed cooperatively by the master that issued the read and the currently available per-CPU workers.
MARS builds its workers on Linux’s smpboot per-CPU thread infrastructure, which manages CPU hotplug, and augments it with lightweight available-worker discovery and a master–worker collaboration protocol. The framework has four design
goals: opportunistically use CPUs that can currently provide assistance; observe worker availability and work execution progress; minimize interference with ordinary tasks on other CPUs; and ensure that the master ultimately completes every work item
regardless of whether a worker successfully participates. Each worker uses the SCHED_IDLE scheduling policy so that ordinary tasks on its CPU run with priority. To balance task responsiveness and energy consumption, MARS also provides a
configurable, bounded optimistic-polling mechanism that is disabled by default and allows an idle worker to wait briefly for subsequent work before sleeping. When no CPU can provide immediate assistance, MARS directly selects the master-only path and
neither creates nor dispatches worker work. Read correctness therefore does not depend on idle CPUs, and a busy system incurs only the small cost of availability checks and parallelism calculation.
Figure 4 illustrates the execution flow of the framework. When the master reaches a parallelizable stage, it first partitions the operation into work units and determines the parallelism degree. It then selects collaborators from the currently available workers, dispatches some work units to them, and executes its own retained share locally. MARS uses bitmaps to discover candidate CPUs that are currently running worker logic and have not taken another work item, and then uses worker state and a lock to confirm that each candidate remains available. The opcode in a work item maps the task to the corresponding operator callbacks for execution, restoration, and re-execution. After finishing its own share, the master reads the state of each work item, synchronizes with the workers, and collects their results. If the candidate set is empty or dispatch fails, the master still executes the corresponding task instead of queuing it to wait for a worker.
To prevent opportunistic execution from compromising task completion, MARS maintains separate states for workers and work items and establishes a concurrency protocol constrained by atomic operations and memory barriers. The FREE,
PENDING, and RUNNING worker states indicate whether a worker can accept a task and whether it has begun execution. The UNFINISHED, PARTIAL, FINISHED, CANCELED, and
CANCELED-BY-MASTER work states record execution ownership and completion progress. A worker may begin processing only after atomically changing a work item from UNFINISHED to PARTIAL. If the master changes it to
CANCELED-BY-MASTER first, the late worker abandons execution and the master re-executes the entire work item, ensuring that the same work is not processed by both sides. For a work item that has entered PARTIAL, the master waits
for the worker to publish a terminal state. On normal completion, the worker writes the result before changing the state to FINISHED. If it exits midway, it first saves in the work item the minimum execution progress required for the master to
continue the operator, and then changes the state to CANCELED. This saved context is not a complete thread context, but an operator-specific progress ledger. Write and read barriers around state publication ensure that the corresponding result
or restoration context is visible when the master observes the terminal state. After observing CANCELED, the master invokes the operator’s restoration callback to complete the remaining work from the saved point; if the work item has no
restorable context, it invokes the re-execution callback. CANCELED and CANCELED-BY-MASTER are only temporary ownership-transfer states, and the master converges the corresponding work item to FINISHED after
restoration or re-execution. Consequently, all work items have completed before the function returns: the master always participates in execution and retains final completion responsibility, while workers serve only as opportunistic accelerators.
MARS further determines the parallelism degree in two steps according to the task size and current resource state. The first step determines the maximum number of executors worth using for an operator. Let the total workload be \(B\) and the processing bandwidth of one executor be \(S\). Then \(M=B/S\) denotes the ideal processing time with one executor, and \(D\) denotes the combined startup and dispatch overhead introduced by each additional executor. Under a simplified model in which executors divide the work approximately evenly, the completion time with \(n\) executors is \[T_n = \frac{M+(n-1)D}{n}.\] MARS limits parallelism using a minimum acceptable marginal gain \(r\), requiring the addition of the \(n\)th executor to satisfy \[\frac{T_{n-1}-T_n}{T_{n-1}} = \frac{M-D}{n[M+(n-2)D]} > r.\] The positive root of this inequality gives the theoretical boundary \[n_+ = \frac{r(2D-M)+\sqrt{r^2(M-2D)^2+4rD(M-D)}}{2rD}.\] The implementation discretizes this boundary and constrains it by the machine’s CPU count and the operator’s implementation limit to obtain \(n_{\max}\). In the simplified model that ignores shared-memory bandwidth limits, \(n_{\max}\) increases with the workload and eventually approaches \(1/r\); this result provides only a theoretical guideline. Different operators have different values of \(D\), while the effective parallelism degree is also constrained by the target machine’s memory-bandwidth saturation point and NUMA topology. The workload thresholds and parallelism caps for each operator must therefore be calibrated for the target platform.
The second step selects the number of executors to use for this invocation within \(n_{\max}\) according to system idleness. The intuition is that the more idle the system is, the more of the available gain MARS is willing to pursue. The cumulative gain from increasing the number of executors from one to \(n\) is \[G(n) = T_1-T_n = (M-D)\left(1-\frac{1}{n}\right).\] The total gain available between one executor and the upper bound can therefore be viewed as a line composed of the marginal gains at successive parallelism levels: the front segment corresponds to the largest gain obtained by adding the second executor, while the subsequent segments become progressively shorter as parallelism increases. When the system is busy, MARS obtains only the most significant gains near the front; as the system becomes more idle, it becomes willing to pursue the smaller gains near the end. Let the system contain \(N\) CPUs, let \(q\) be the number of currently available workers, let \(f=q/N\), and define \(K=\min(n_{\max},q+1,N)\), where the executor count includes the master. When \(q=0\), the system directly returns \(n=1\). Otherwise, MARS selects the smallest integer \(n\) satisfying \[\frac{G(n)}{G(K)} \geq f^\alpha,\] namely, \[n = \left\lceil \frac{K}{K-(K-1)f^\alpha} \right\rceil.\] The common factor \(M-D\) cancels in the gain ratio, so after the upper bound has been determined, the actual allocation depends only on currently available resources. When \(\alpha=1\), the fraction of gain to pursue grows linearly with system idleness. A concave mapping with \(0<\alpha<1\) yields \(f^\alpha\geq f\) and therefore uses idle CPUs more aggressively. A smaller \(\alpha\) increases the parallelism degree at the same idleness level, whereas a larger \(r\) lowers \(n_{\max}\) and makes the policy more conservative.
Parallel copy is the primary application of this framework. MARS divides the remaining copy work approximately evenly among the master and workers. The master also executes one share, and each executor is responsible for non-overlapping file and
user-buffer ranges. Because a per-CPU worker is a kernel thread, a copy work item stores the master’s mm_struct. Following the semantics of Linux’s kthread_use_mm(), the worker temporarily adopts this address space and accesses
the user buffer whose mappings were proactively established in Section 3.2. The worker disables preemption while executing the operator callback, preventing it from switching to another thread while the work item
remains in PARTIAL. At each folio boundary, however, it checks for scheduling requests. Once an ordinary task needs the CPU, the worker saves a lightweight context consisting of the number of bytes copied, the file position, and the folio
cursor, leaves the non-preemptible region, and yields the processor; the master then restores the context and resumes copying. Task partitioning counts only copy work that remains unfinished. Data copied early by Section 3.2 is not copied again here; the master only commits the corresponding logical read progress. Finally, the master aggregates the copy results of all work items and advances the read position.
Parallel I/O submission reuses the same framework but adopts different task partitioning and parallelism policies. MARS organizes the folios awaiting submission into multiple readahead batches and distributes the batches between the master and workers,
allowing multiple executors to construct and submit read requests concurrently. The goal is not to reduce the cost of the submission code itself, but to form multiple in-flight requests more quickly and thereby shorten the data-readiness wait observed by
the cache layer on backends that can exploit parallel I/O. However, a worker executing in a non-preemptible region cannot enter an ordinary submission path that may sleep. MARS therefore reuses Linux’s existing IOMAP_NOWAIT and
REQ_NOWAIT semantics and adds a lightweight marker that propagates MARS’s non-preemptible execution context into iomap and the block layer. This path uses trylock operations and nonblocking bio allocation. Each worker uses an independent
cursor to continuously record its assigned folio range, actual submission progress, the first unfinished position, and the folios it still owns; this cursor is the restoration context of the submission operator. If any operation cannot complete
immediately, the worker uses the first unfinished folio as the takeover boundary and returns the remaining range to the master. The master switches to the ordinary sleepable path at that boundary and continues submission without reprocessing the portion
already submitted successfully.
More in-flight requests do not always shorten the wait: the benefit depends on the file-system path, request window, and parallel capability of the underlying device, while excessive parallelism can increase contention. Parallel submission is therefore explicitly enabled only for sufficiently large requests, and its parallelism degree is configured independently rather than reusing the automatic selection policy for copy. The evaluation later analyzes the relationship among the parallelism degree, number of in-flight requests, and wait time separately.
This common framework allows MARS to apply opportunistic acceleration selectively to the staged path. Parallel copy shortens the serial critical path of large-scale user-data copying, while parallel submission forms in-flight requests more quickly on suitable backends and thus has the opportunity to reduce the data-readiness wait. Both mechanisms share the same per-CPU workers and completion protocol, eliminating the need to build a separate execution framework for each operator. MARS does not parallelize every stage of the staged path. After the staged reorganization in Section 3.1, memory allocation and XArray insertion account for only a small fraction of end-to-end latency; the overall benefit of parallelizing them would be nearly negligible, so MARS does not parallelize these stages. MARS applies opportunistic parallelism only to copy, which retains substantial serial cost, and to I/O submission, which can benefit from additional in-flight requests.
This section answers four questions: Do the mechanisms in MARS create the intended optimization opportunities? Can MARS improve the performance of large-buffer file reads? Do these benefits carry over to real applications? How much does each design contribute?
We conduct our experiments on Huawei Cloud Kunpeng ultra-high-I/O Ki2 instances. Except for the multi-device experiment, all experiments run on a ki2.8xlarge.8 instance. This instance has 32 ARM64 vCPUs, 256 GiB of memory, and two 3.2 TiB local NVMe SSDs; we use one SSD and deploy XFS on it. To evaluate parallel I/O submission on a storage backend with greater device-level parallelism, we use another Ki2 instance equipped with five approximately 3.2 TiB local NVMe SSDs and configure them as RAID0 with a 1 MiB chunk size. All experiments except the parallel-submission experiment run on the single-SSD platform.
We implement MARS in Linux 6.6.58 and use unmodified Linux at the same version as the baseline. Unless otherwise specified, we repeat each configuration five times and report the mean; before each cold-cache run, we execute sync and clear
the page cache. The mechanism experiments use eBPF or internal MARS statistics to measure cache-layer costs and operation ratios, the microbenchmarks report read bandwidth, and the real-application experiments report end-to-end execution time. The release
will include the MARS source code and the scripts used to configure workloads, run experiments, and process results.
Figure 5 uses eBPF to attribute cache-layer time for 16 MiB cold-cache random reads, comparing native Linux with MARS configured to enable only the staged cache-layer reorganization. The attributed measurements may overlap and therefore cannot be summed to reconstruct the total read time.
MARS substantially reduces metadata-management costs, including XArray lookup, folio allocation, and XArray insertion; for example, XArray lookup decreases from 96.3 \(\mu\)s to 1.1 \(\mu\)s. The staged organization also allows MARS to collect cache misses before submitting a larger batch of requests to the storage layer, thereby increasing the number of in-flight I/Os and reducing I/O wait/uptodate handling
from 4479.9 \(\mu\)s to 2864.3 \(\mu\)s. In contrast, copy-to-user does not benefit from the staged reorganization. Data copying cannot exploit the locality created by metadata batching,
showing that reorganizing the cache layer alone is insufficient to eliminate the copy cost of large-buffer reads. Each configuration reports the mean of ten accepted runs; here, read_pages denotes the Linux path that performs batched readahead
and submits read I/O to the lower layer. Following a rule fixed before the rerun, when the eBPF-attributed time of this path exceeds 20% of the total read time, we treat the run as a probe-accounting anomaly and repeat it; all excluded raw records are
retained.
Figure 6 (a) uses a kernel microbenchmark to separate explicit fault-in of the user buffer from the subsequent copy cost. The microbenchmark allocates and fills a set of temporary folios to emulate ready data, then separately measures user-buffer fault-in and copying through the actual MARS copy path; it accesses neither a storage device nor the page-cache XArray. For 8, 32, and 128 MiB buffers, explicit fault-in costs 35%–56% as much as the subsequent copy and is therefore not negligible. Moving page-fault handling into the hardware-I/O wait window prevents it from further extending the copy path after the data becomes ready.
Cache-hit folios are already ready and can clearly be copied while other data is pending. Figure 6 (b) therefore evaluates the less obvious all-miss case: when every folio in a large read initially requires I/O, can folios that complete early be copied while the remaining I/O is still outstanding? Before each run, we clear the page cache and then issue one contiguous-range read of 16, 64, or 128 MiB, using eBPF to count the bytes copied during the wait stage. Even in this all-miss setting, MARS copies 71%–78% of the requested data early; for a 128 MiB read, an average of approximately 100.2 MiB is copied within the wait window. When early copy is disabled, the same statistic drops to zero. These results show that out-of-order early copy handles not only the obviously ready cache-hit data, but also exploits differences in I/O completion time among cache-miss data to overlap most of its copying with the wait.
Figure 7 studies whether parallel I/O submission can convert more in-flight requests into a shorter wait. Using fio[3], we run single-job synchronous random reads on a single NVMe SSD and on a RAID0 array of five NVMe SSDs. The read sizes are 8, 32, and 128 MiB, and each run reads 1 GiB of data. Through debugfs, we fix the I/O submission degree from Section 3.3 to either 0 or 4: MARS serial submits requests using only the master, whereas MARS parallel uses four executors—the master and three workers.
The single SSD cannot effectively absorb the additional submission parallelism. For 128 MiB reads, four executors reduce bandwidth from 5.54 GiB/s under serial submission to 5.08 GiB/s; parallel submission increases cache-layer I/O wait time on the single SSD and consequently degrades performance. RAID0, by contrast, can promptly service the additional in-flight requests: at the same read size, parallel submission increases bandwidth from 26.64 GiB/s to 36.87 GiB/s, converting the higher submission concurrency into a shorter wait. Thus, more in-flight requests do not necessarily imply a shorter wait; the effectiveness of parallel submission depends jointly on request size, submission parallelism, and the device-level parallelism available in the storage backend. These results show a non-monotonic relationship among the MARS submission degree, the in-flight request depth formed at the block layer, and cache-layer I/O wait time. In future work, we plan to design a backend-aware adaptive controller that dynamically selects the MARS submission degree based on block-layer queue state and observed wait time.
Figure 8 compares the basic read performance of Linux and full MARS. fio uses a single job to issue synchronous buffered reads, performing either sequential or random reads from a 1 GiB file in each run; the read sizes are 2, 16, 64, and 128 MiB. Linux and MARS use the same workload configurations, and Linux retains its default readahead policy.
For sequential reads, Linux remains at approximately 1.88 GiB/s across all read sizes, whereas MARS continues to scale with the read window and reaches 5.73 GiB/s, a 3.06\(\times\) speedup, at 128 MiB. For random reads, Linux similarly plateaus at approximately 0.85 GiB/s, while MARS reaches 5.58 GiB/s and a 6.56\(\times\) speedup at 128 MiB. MARS optimizes within each explicit large-range read, does not depend on the ordering of adjacent requests, and has no sequential-read-specific readahead; it can therefore exploit large read windows in both access patterns and ultimately reaches similar bandwidth.
Figure 9 further evaluates a 128 MiB random pread that contains both cache-hit and cache-miss data. We divide a 1 GiB file into eight 128 MiB windows and fix a random access permutation for each
run. Before timed execution, a normal open is used to prewarm the first 0%, 25%, 50%, or 75% of each window; Linux or MARS then reads all eight windows in the same random order, excluding prewarming time from the result.
At the four cache-hit ratios, MARS achieves 3.59\(\times\), 3.62\(\times\), 3.70\(\times\), and 3.45\(\times\) the bandwidth of Linux, respectively. These results show that the benefits of MARS do not depend on an all-miss workload: the staged path handles hit and miss data within the same read, while wait masking advances the copy of ready data as the miss portion waits for I/O.
We evaluate the end-to-end benefits of MARS using three classes of real applications that read large objects. All formal runs use the single-SSD platform; before each run, we clear the page cache and use identical data, queries, or models for Linux and MARS.
DuckDB is an embedded database for analytical workloads that can query Apache Parquet files directly[4], [5]. We select three public datasets: LibriSpeech, which contains approximately 1,000 hours of audiobook speech[6]; Earnings-21, a 39-hour long-form speech benchmark composed of real earnings calls[7]; and RSHR-Bench, which embeds ultra-high-resolution remote-sensing images directly in Parquet files[8], [9]. They cover different object sizes and access scales. We measure the complete workload execution time, including DuckDB query execution, Parquet data reading and parsing, and application-side consumption of the returned binary objects.
As shown in Figure 11, MARS achieves speedups of 2.15\(\times\), 1.80\(\times\), and 2.13\(\times\) on the three workloads, respectively; for example, it reduces RSHR-Bench execution time from 10.66 s to 5.00 s.
ExecuTorch is PyTorch’s official model deployment and inference framework for mobile and edge devices[10]. We use its
FileDataLoader to load 2.30, 3.21, and 5.99 GiB PTE models—Llama 1B, Qwen 1.7B, and Llama 3B—covering different model-file sizes[11],
[12]. The test program first creates a file loader using FileDataLoader::from(), then measures the time required by Program::load(..., Verification::Minimal) to load the PTE
program; the measurement excludes file-loader creation, tokenization, model execution, and first-token generation.
Figure 12 shows that MARS achieves speedups of 3.22\(\times\), 3.17\(\times\), and 3.61\(\times\) on the three models, respectively; for the 5.99 GiB model, it reduces loading time from 4.48 s to 1.24 s.
We use TensorFlow[13] with a synthetic TFRecord file[14] containing 112 records with 8 MiB payloads, totaling approximately 896 MiB, which allows us to precisely control record and input-buffer sizes. We measure the time for the TensorFlow input pipeline to sequentially read and decode all records; the experiment does not include model training or inference.
| Buffer | Linux (s) | (s) | Reduction |
|---|---|---|---|
| 16 MiB | 1.676 | 1.258 | 24.9% |
| 64 MiB | 1.699 | 1.257 | 26.0% |
| 128 MiB | 1.698 | 1.241 | 26.9% |
As Table 1 shows, with a 16 MiB input buffer, MARS reduces execution time from 1.676 s to 1.258 s, corresponding to a 1.33\(\times\) speedup and a 24.9% time reduction. With 64 and 128 MiB buffers, the time reductions are 26.0% and 26.9%, respectively, showing that the benefit does not depend on a particular input-buffer size.
These results show that the benefits of MARS extend beyond the system-call boundary and reduce real-application execution time. Application speedups are generally lower than the fio bandwidth gains because non-I/O work, such as query processing, format parsing, and decoding, is not accelerated by MARS.
Figure 10 incrementally enables the MARS designs on a single NVMe SSD to isolate their contributions. All configurations run single-threaded cold-cache random reads with parallel I/O submission disabled. MARS-base retains only the staged cache-layer path and disables early page-fault handling, out-of-order early copy, and parallel copy; +masking additionally enables early page-fault handling, out-of-order early copy, and XArray prebuilding; MARS-final further enables opportunistic parallel copy. Therefore, the difference between MARS-final and +masking mainly reflects parallel copy rather than parallel submission.
The staged cache-layer reorganization provides most of the initial benefit. For 128 MiB reads, MARS-base increases bandwidth from 0.85 GiB/s on Linux to 4.17 GiB/s; adding masking further improves bandwidth by 27.6% to 5.33 GiB/s, and opportunistic parallel copy ultimately raises it to 5.57 GiB/s. Full MARS achieves speedups of 5.49\(\times\), 6.04\(\times\), and 6.56\(\times\) over Linux for 16, 32, and 128 MiB reads, respectively. The ablation results show that staged reorganization first removes substantial metadata-management and interleaved-path overhead, masking moves page-fault handling and most copying into the I/O wait window, and parallel copy further shortens the residual copying that remains on the critical path after the wait. The three designs address different sources of latency and complement one another for large-buffer reads.
Characterizing I/O-stack bottlenecks under fast storage. As SSD latency decreases and device parallelism increases, kernel software paths account for a growing fraction of end-to-end I/O time. Prior studies have measured this shift at different layers.
Borge et al. analyzed performance anomalies caused by SSD hardware behavior and their interaction with software mechanisms[15]; Ren et al. compared the performance of POSIX I/O, libaio, SPDK, and io_uring under different workloads, revealing the strengths and bottlenecks of the general-purpose Linux I/O stack on highly parallel
devices[16]; Lee et al. further found on a many-core platform that operations such as memory management and page
insertion can become major software overheads in buffered I/O[17]. Studies of ultra-low-latency devices also show that the
relative costs of request merging, block-layer scheduling, and completion handling increase as device latency falls[18], [19]. These studies characterize the evolving I/O bottlenecks under fast storage, whereas MARS further targets a single large-range buffered read and optimizes
the complete read path from page-cache lookup to data copying.
High-performance I/O alternatives to conventional buffered read. To reduce system-call and general-purpose kernel-path overheads, existing systems often construct shorter I/O paths by changing interfaces, submission semantics, or caching policies.
io_uring uses submission and completion queues shared between user space and the kernel, and supports mechanisms such as batched submission, registered resources, and polling to amortize system-call and request-management overheads[20]; SPDK instead moves the NVMe driver and storage processing into user space and pursues low latency and high throughput through polling and
kernel bypass[16]. Direct I/O avoids cache-management overhead by bypassing the page cache, but also gives up
capabilities such as cache hits and kernel readahead; Qian et al. therefore dynamically select between buffered I/O and Direct I/O according to request size, lock contention, and memory pressure[21]. Another class of work merges, reorganizes, or reorders fine-grained requests in libraries, runtimes, or middleware to reduce the number of
submissions and improve device-access granularity[22]–[26]. These methods can bypass some overheads of the conventional path, but typically require applications to adopt new interfaces, manage asynchronous requests, or change how they use caching.
MARS preserves the ordinary buffered-read interface and page-cache semantics while reorganizing execution within a single large-range synchronous read.
Cache-layer path and data-movement optimizations. Prior work reduces cache-layer software overheads in operations such as page allocation, page indexing, and data copying. Page-allocation optimizations mainly fall into two categories. The first modifies the general-purpose memory-allocation subsystem, for example by improving page placement and contiguous-memory allocation in the buddy allocator through anti-fragmentation and huge-page support[27], [28], or by redesigning the page-frame allocator, as LLFree does, to improve multicore scalability[29]. The second builds dedicated page pools for the storage path; for example, StreamCache organizes cache pools and their metadata by file to improve page-allocation locality[30]. For small-granularity I/O on ultra-low-latency devices, Async I/O Stack maintains for each CPU a pool of free pages whose DMA mappings have been established in advance, and replenishes the page pool while the device processes I/O, thereby moving page allocation and DMA mapping out of the critical path for common requests[31]. This design can substantially reduce small-granularity read latency, but DMA mapping is not a major end-to-end bottleneck in the large-buffer reads targeted by MARS. Dedicated page pools reserve and continuously maintain a portion of pages in exchange for lower latency or higher bandwidth, so their capacity must balance critical-path benefits against memory available to the rest of the system. HugeMap instead uses huge pages to reduce mmap-management overhead on fast storage[32]. For the XArray index and shared state of the Linux page cache, ScaleCache restructures page-cache data structures to alleviate synchronization bottlenecks with multiple SSDs and multiple writers[33], while some cache designs separate the management of clean and dirty pages to reduce contention between foreground index operations and background state maintenance[30]. Async I/O Stack further defers page-cache insertion until after device requests have been submitted, allowing insertion to overlap device execution. This ordering allows concurrent threads to submit duplicate I/O for the same file block; at request completion, the system retains only one cached page and reclaims the others[31]. The design trades possible duplicate requests and completion-stage processing for a shorter critical path for small-granularity I/O. MARS targets a read that covers a large number of folios and forms a concentrated submission window through set-oriented stages, without intentionally deferring page-cache visibility until after I/O submission.
For data copying, Linux provides mmap, sendfile, and splice, allowing applications to avoid some user-space round-trip copies through mapping or in-kernel buffer forwarding. Fbufs, early UNIX zero-copy frameworks, and IO-Lite instead reduce repeated copying between protocol stacks and applications by reorganizing the ownership and sharing of cross-domain buffers[34]–[36]. Z-READ transparently performs zero-copy reads by unmapping the user buffer and remapping the corresponding virtual addresses to kernel pages; its follow-up work further uses batching, private-PTE detection, and historical-behavior analysis to reduce the costs of page-table modification and copy-on-write fallback[37], [38]. Copier structures memory copying as a general-purpose asynchronous OS service for user applications and kernel subsystems, using the Copy–Use window, fine-grained progress, and out-of-order execution to mask copy latency[39]. MARS likewise allows other execution units to perform copying on behalf of the calling thread, but it uses page-cache state and I/O completion order to copy all cache-hit data and most cache-miss data early, after which the master and opportunistic workers complete the remainder in parallel. In its system-call optimizations, Copier uses csync to defer the copy-completion synchronization point from system-call return until the data is actually used, thereby relaxing the return-completion convention of traditional synchronous system calls; in contrast, on its normal successful path, MARS still guarantees that the bytes returned by read are ready. The two systems respectively target a general-purpose asynchronous copy service and copy optimization within large-range buffered reads in the I/O stack. Other zero-copy mechanisms typically depend on mapping semantics, new interfaces, or dedicated buffer management. PaCaR optimizes the page cache from another dimension: it replicates cached pages on demand across NUMA nodes, allowing threads on remote nodes to read from local replicas while maintaining replica consistency and reclamation relationships under writes and memory pressure[40].
Block-layer submission, polling, and parallel execution. Another class of work reduces request-scheduling and completion overheads below the page cache. Systems for fast or ultra-low-latency devices reassess the benefits of block-layer request merging and scheduling, and reduce software processing through streamlined scheduling paths, direct submission, or lightweight swap paths[19], [31], [41]. Polling trades CPU time for shorter completion-notification latency, while hybrid polling adjusts sleep and polling timing according to whether the device is idle or busy to balance latency and CPU utilization[42]–[44]. CFIO uses conflict-free lanes, batched scheduling, and parallel I/O pipelines to exploit device-internal parallelism[45], while Linux blk-mq also reduces shared-queue contention through multiple hardware queues[46]. MARS does not replace block-layer scheduling or device-completion mechanisms; its parallel submission organizes a more concentrated submission window at the page-cache layer, allowing existing lower-layer queues and device parallelism to receive enough in-flight requests.
This paper investigates how to reduce the service time of synchronous large-buffer buffered reads without bypassing the page cache or shifting the complexity of asynchronous I/O orchestration to applications. Our central insight is that a large buffer is not merely a data buffer passed from an application to the kernel; it can also serve as a work window in which the kernel organizes batching, wait masking, and parallel execution. Based on this insight, we design and implement MARS, which reorganizes the cache-layer execution within a single large-range read.
Mechanism and ablation experiments show that staged restructuring not only reduces metadata-management and interleaved-execution overheads, but also increases the number of in-flight requests and shortens data waits by creating a more concentrated I/O submission window. Wait-window masking further hides user-buffer page faults and most data copying, while opportunistic parallelism shortens the remaining copy path and, on backends with sufficient device parallelism, further reduces wait time through parallel submission. Experiments with fio, mixed cache-hit ratios, and real applications further demonstrate that these benefits are not limited to fully cache-cold microbenchmarks, but also translate into end-to-end speedups for large-object queries, model loading, and data-input pipelines. Overall, MARS shows that reorganizing cache-layer work within synchronous reads enables conventional buffered read to better exploit large buffers, multicore processors, and modern parallel storage devices while preserving interface simplicity and the sharing benefits of the page cache.
tf.train.Example.” https://www.tensorflow.org/tutorials/load_data/tfrecord.