Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ Result<> ReadNIfTIFile::operator()()
return MakeErrorResult(-34720, fmt::format("Could not open NIfTI file for reading: '{}'", m_InputValues->InputFilePath.string()));
}

// Enlarge zlib's internal buffer before the first read/seek so the file is
// refilled in large chunks instead of zlib's 8 KB default. This drastically
// reduces the number of latency-bound network round-trips when the input
// lives on a NAS. See nifti::k_GzReadBufferSize for the rationale.
gzbuffer(gz, static_cast<unsigned int>(nx::core::nifti::k_GzReadBufferSize));

const z_off_t targetOffset = static_cast<z_off_t>(md.voxOffset);
if(gzseek(gz, targetOffset, SEEK_SET) != targetOffset)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ Result<NiftiMetadata> ReadNiftiHeader(const std::filesystem::path& filePath, boo
return MakeErrorResult<NiftiMetadata>(-34700, fmt::format("Could not open NIfTI file for reading: '{}'", pathStr));
}

gzbuffer(gz, k_GzReadBufferSize);

nifti_1_header hdr{};
int bytesRead = gzread(gz, &hdr, static_cast<unsigned int>(k_HeaderSize));
int bytesRead = gzread(gz, &hdr, k_HeaderSize);
gzclose(gz);

if(bytesRead != static_cast<int>(k_HeaderSize))
Expand Down
21 changes: 19 additions & 2 deletions src/Plugins/SimplnxCore/src/SimplnxCore/utils/NiftiUtilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* `byteSwapRequired` from the metadata to do its own stream reads.
*
* The helpers here are deliberately independent of the simplnx filter
* framework so they can be reused from unit tests, a future writer,
* framework, so they can be reused from unit tests, a future writer,
* or a command-line tool.
*/

Expand All @@ -36,7 +36,7 @@ namespace nx::core::nifti
* @brief Size of the NIfTI-1 header in bytes. Always 348 for a valid
* NIfTI-1 file regardless of byte order.
*/
inline constexpr usize k_HeaderSize = 348;
inline constexpr unsigned int k_HeaderSize = 348;

/**
* @brief Minimum legal value of the `vox_offset` field in a single-file
Expand All @@ -46,6 +46,23 @@ inline constexpr usize k_HeaderSize = 348;
*/
inline constexpr usize k_MinVoxOffset = 352;

/**
* @brief Size (in bytes) of zlib's internal input/output buffer, set via
* `gzbuffer()` immediately after every `gzopen()`.
*
* zlib defaults to an 8 KB internal buffer, which means it refills from
* the underlying file in 8 KB chunks of compressed data — one `read()`
* syscall each. On a local disk that is negligible, but on network
* attached storage (NAS) every refill is a separate network round-trip,
* so a multi-GB `.nii.gz` triggers hundreds of thousands of small,
* latency-bound reads. Raising the buffer to 4 MiB cuts the number of
* round-trips by ~512x while costing only a few × this size in working
* memory (negligible next to the voxel volume). Larger values yield
* diminishing returns once the buffer exceeds the link's
* bandwidth-delay product and the client's own read-ahead.
*/
inline constexpr unsigned int k_GzReadBufferSize = 4194304;

/**
* @brief Magic bytes that identify the single-file `.nii` / `.nii.gz`
* format. Trailing null is included per the spec.
Expand Down
Loading