-
Notifications
You must be signed in to change notification settings - Fork 0
Batch IO and File Helpers
github-actions[bot] edited this page Jul 14, 2026
·
2 revisions
Elio provides high-level file I/O abstractions built on top of io_uring for maximum throughput.
Batch I/O allows positioned segments to be submitted as multiple
pread/pwrite operations in a single syscall (io_uring_submit()),
reducing kernel round-trip overhead and improving throughput for scattered
reads/writes. Segments with negative offsets use the file descriptor's current
position and are executed in segment order to preserve file-position semantics.
#include <elio/elio.hpp>
using namespace elio::io;
coro::task<void> read_multiple_segments(int fd) {
char buf1[16] = {0}, buf2[16] = {0}, buf3[16] = {0};
std::array<batch_read_segment, 3> segments;
segments[0] = {0, buf1, 5}; // Read 5 bytes at offset 0
segments[1] = {10, buf2, 5}; // Read 5 bytes at offset 10
segments[2] = {20, buf3, 5}; // Read 5 bytes at offset 20
auto results = co_await batch_read(fd, segments);
for (size_t i = 0; i < results.size(); ++i) {
if (results[i] > 0) {
// Success: bytes read
} else {
// Error: negative errno
}
}
}coro::task<void> write_multiple_segments(int fd) {
const char* data1 = "Hello";
const char* data2 = "World";
const char* data3 = "!";
std::array<batch_write_segment, 3> segments;
segments[0] = {0, data1, 5}; // Write at offset 0
segments[1] = {10, data2, 5}; // Write at offset 10
segments[2] = {20, data3, 1}; // Write at offset 20
auto results = co_await batch_write(fd, segments);
}- Single syscall submission: Positioned segments are prepared in the SQ ring and submitted together
-
Current-position support: Negative offsets use ordered
read()/write()calls so file-position updates are deterministic - Per-segment completion tracking: Each segment gets its own result (bytes read/written, or negative errno)
- Tagged user_data: The completion queue encodes both the batch state pointer and segment index, allowing out-of-order CQEs to be matched correctly
-
Epoll fallback: When io_uring is unavailable, falls back to sequential synchronous
pread/pwrite
- Segment offsets must not overlap (undefined behavior if they do)
- Maximum segments per batch is limited by the io_uring SQ ring depth
- If the SQ ring is full mid-batch, remaining segments get
-EAGAIN
High-level coroutine functions for common file operations:
auto content = co_await read_file("/path/to/file.txt");
if (content) {
std::cout << *content << std::endl;
} else {
std::cerr << "Failed to read file" << std::endl;
}bool ok = co_await write_file("/path/to/file.txt", "Hello, World!");bool ok = co_await append_file("/path/to/log.txt", "New log entry\n");// Check if file exists
if (file_exists("/path/to/file.txt")) {
std::cout << "File exists" << std::endl;
}
// Get file size
if (auto size = file_size("/path/to/file.txt")) {
std::cout << "Size: " << *size << " bytes" << std::endl;
}auto entries = read_dir("/path/to/directory");
if (entries) {
for (const auto& entry : *entries) {
if (entry.is_dir) {
std::cout << "[DIR] " << entry.name << std::endl;
} else if (entry.is_file) {
std::cout << "[FILE] " << entry.name << std::endl;
} else if (entry.is_symlink) {
std::cout << "[LINK] " << entry.name << std::endl;
}
}
}Large files are read/written in 1MB chunks to avoid excessive memory allocation in a single io_uring request. The chunking is transparent — the caller sees the entire file content.