mirror of
https://github.com/HDFGroup/hdf5.git
synced 2025-01-12 15:04:59 +08:00
314 lines
15 KiB
Markdown
314 lines
15 KiB
Markdown
|
# HDF5 Parallel Compression
|
||
|
|
||
|
## Introduction
|
||
|
|
||
|
When an HDF5 dataset is created, the application can specify
|
||
|
optional data filters to be applied to the dataset (as long as
|
||
|
the dataset uses a chunked data layout). These filters may
|
||
|
perform compression, shuffling, checksumming/error detection
|
||
|
and more on the dataset data. The filters are added to a filter
|
||
|
pipeline for the dataset and are automatically applied to the
|
||
|
data during dataset writes and reads.
|
||
|
|
||
|
Prior to the HDF5 1.10.2 release, a parallel HDF5 application
|
||
|
could read datasets with filters applied to them, but could
|
||
|
not write to those datasets in parallel. The datasets would
|
||
|
have to first be written in a serial HDF5 application or from
|
||
|
a single MPI rank in a parallel HDF5 application. This
|
||
|
restriction was in place because:
|
||
|
|
||
|
- Updating the data in filtered datasets requires management
|
||
|
of file metadata, such as the dataset's chunk index and file
|
||
|
space for data chunks, which must be done collectively in
|
||
|
order for MPI ranks to have a consistent view of the file.
|
||
|
At the time, HDF5 lacked the collective coordination of
|
||
|
this metadata management.
|
||
|
|
||
|
- When multiple MPI ranks are writing independently to the
|
||
|
same chunk in a dataset (even if their selected portions of
|
||
|
the chunk don't overlap), the whole chunk has to be read,
|
||
|
unfiltered, modified, re-filtered and then written back to
|
||
|
disk. This read-modify-write style of operation would cause
|
||
|
conflicts among the MPI ranks and lead to an inconsistent
|
||
|
view of the file.
|
||
|
|
||
|
Introduced in the HDF5 1.10.2 release, the parallel compression
|
||
|
feature allows an HDF5 application to write in parallel to
|
||
|
datasets with filters applied to them, as long as collective
|
||
|
I/O is used. The feature introduces new internal infrastructure
|
||
|
that coordinates the collective management of the file metadata
|
||
|
between MPI ranks during dataset writes. It also accounts for
|
||
|
multiple MPI ranks writing to a chunk by assigning ownership to
|
||
|
one of the MPI ranks, at which point the other MPI ranks send
|
||
|
their modifications to the owning MPI rank.
|
||
|
|
||
|
The parallel compression feature is always enabled when HDF5
|
||
|
is built with parallel enabled, but the feature may be disabled
|
||
|
if the necessary MPI-3 routines are not available. Therefore,
|
||
|
HDF5 conditionally defines the macro `H5_HAVE_PARALLEL_FILTERED_WRITES`
|
||
|
which an application can check for to see if the feature is
|
||
|
available.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
Using the parallel compression feature is very similar to using
|
||
|
compression in serial HDF5, except that dataset writes **must**
|
||
|
be collective:
|
||
|
|
||
|
```
|
||
|
hid_t dxpl_id = H5Pcreate(H5P_DATASET_XFER);
|
||
|
H5Pset_dxpl_mpio(dxpl_id, H5FD_MPIO_COLLECTIVE);
|
||
|
H5Dwrite(..., dxpl_id, ...);
|
||
|
```
|
||
|
|
||
|
The following are two simple examples of using the parallel compression
|
||
|
feature:
|
||
|
|
||
|
[ph5_filtered_writes.c](https://github.com/HDFGroup/hdf5/blob/develop/examples/ph5_filtered_writes.c)
|
||
|
|
||
|
[ph5_filtered_writes_no_sel.c](https://github.com/HDFGroup/hdf5/blob/develop/examples/ph5_filtered_writes_no_sel.c)
|
||
|
|
||
|
The former contains simple examples of using the parallel
|
||
|
compression feature to write to compressed datasets, while the
|
||
|
latter contains an example of how to write to compressed datasets
|
||
|
when one or MPI ranks don't have any data to write to a dataset.
|
||
|
Remember that the feature requires these writes to use collective
|
||
|
I/O, so the MPI ranks which have nothing to contribute must still
|
||
|
participate in the collective write call.
|
||
|
|
||
|
## Incremental file space allocation support
|
||
|
|
||
|
HDF5's [file space allocation time](https://portal.hdfgroup.org/display/HDF5/H5P_SET_ALLOC_TIME)
|
||
|
is a dataset creation property that can have significant effects
|
||
|
on application performance, especially if the application uses
|
||
|
parallel HDF5. In a serial HDF5 application, the default file space
|
||
|
allocation time for chunked datasets is "incremental". This means
|
||
|
that allocation of space in the HDF5 file for data chunks is
|
||
|
deferred until data is first written to those chunks. In parallel
|
||
|
HDF5, the file space allocation time was previously always forced
|
||
|
to "early", which allocates space in the file for all of a dataset's
|
||
|
data chunks at creation time (or during the first open of a dataset
|
||
|
if it was created serially). This would ensure that all the necessary
|
||
|
file space was allocated so MPI ranks could perform independent I/O
|
||
|
operations on a dataset without needing further coordination of file
|
||
|
metadata as described previously.
|
||
|
|
||
|
While this strategy has worked in the past, it has some noticeable
|
||
|
drawbacks. For one, the larger the chunked dataset being created,
|
||
|
the more noticeable overhead there will be during dataset creation
|
||
|
as all of the data chunks are being allocated in the HDF5 file.
|
||
|
Further, these data chunks will, by default, be [filled](https://portal.hdfgroup.org/display/HDF5/H5P_SET_FILL_VALUE)
|
||
|
with HDF5's default fill data value, leading to extraordinary
|
||
|
dataset creation overhead and resulting in pre-filling large
|
||
|
portions of a dataset that the application might have been planning
|
||
|
to overwrite anyway. Even worse, there will be more initial overhead
|
||
|
from compressing that fill data before writing it out, only to have
|
||
|
it read back in, unfiltered and modified the first time a chunk is
|
||
|
written to. In the past, it was typically suggested that parallel
|
||
|
HDF5 applications should use [H5Pset_fill_time](https://portal.hdfgroup.org/display/HDF5/H5P_SET_FILL_TIME)
|
||
|
with a value of `H5D_FILL_TIME_NEVER` in order to disable writing of
|
||
|
the fill value to dataset chunks, but this isn't ideal if the
|
||
|
application actually wishes to make use of fill values.
|
||
|
|
||
|
With [improvements made](https://www.hdfgroup.org/2022/03/parallel-compression-improvements-in-hdf5-1-13-1/)
|
||
|
to the parallel compression feature for the HDF5 1.13.1 release,
|
||
|
"incremental" file space allocation is now the default for datasets
|
||
|
created in parallel *only if they have filters applied to them*.
|
||
|
"Early" file space allocation is still supported for these datasets
|
||
|
if desired and is still forced for datasets created in parallel that
|
||
|
do *not* have filters applied to them. This change should significantly
|
||
|
reduce the overhead of creating filtered datasets in parallel HDF5
|
||
|
applications and should be helpful to applications that wish to
|
||
|
use a fill value for these datasets. It should also help significantly
|
||
|
reduce the size of the HDF5 file, as file space for the data chunks
|
||
|
is allocated as needed rather than all at once.
|
||
|
|
||
|
## Performance Considerations
|
||
|
|
||
|
Since getting good performance out of HDF5's parallel compression
|
||
|
feature involves several factors, the following is a list of
|
||
|
performance considerations (generally from most to least important)
|
||
|
and best practices to take into account when trying to get the
|
||
|
optimal performance out of the parallel compression feature.
|
||
|
|
||
|
### Begin with a good chunking strategy
|
||
|
|
||
|
[Starting with a good chunking strategy](https://portal.hdfgroup.org/display/HDF5/Chunking+in+HDF5)
|
||
|
will generally have the largest impact on overall application
|
||
|
performance. The different chunking parameters can be difficult
|
||
|
to fine-tune, but it is essential to start with a well-performing
|
||
|
chunking layout before adding compression and parallel I/O into
|
||
|
the mix. Compression itself adds overhead and may have side
|
||
|
effects that necessitate further adjustment of the chunking
|
||
|
parameters and HDF5 application settings. Consider that the
|
||
|
chosen chunk size becomes a very important factor when compression
|
||
|
is involved, as data chunks have to be completely read and
|
||
|
re-written to perform partial writes to the chunk.
|
||
|
|
||
|
[Improving I/O performance with HDF5 compressed datasets](http://portal.hdfgroup.org/display/HDF5/Improving+IO+Performance+When+Working+with+HDF5+Compressed+Datasets)
|
||
|
is a useful reference for more information on getting good
|
||
|
performance when using a chunked dataset layout.
|
||
|
|
||
|
### Avoid chunk sharing
|
||
|
|
||
|
Since the parallel compression feature has to assign ownership
|
||
|
of data chunks to a single MPI rank in order to avoid the
|
||
|
previously described read-modify-write issue, an HDF5 application
|
||
|
may need to take care when determining how a dataset will be
|
||
|
divided up among the MPI ranks writing to it. Each dataset data
|
||
|
chunk that is written to by more than 1 MPI rank will incur extra
|
||
|
MPI overhead as one of the ranks takes ownership and the other
|
||
|
ranks send it their data and information about where in the chunk
|
||
|
that data belongs. While not always possible to do, an HDF5
|
||
|
application will get the best performance out of parallel compression
|
||
|
if it can avoid writing in a way that causes more than 1 MPI rank
|
||
|
to write to any given data chunk in a dataset.
|
||
|
|
||
|
### Collective metadata operations
|
||
|
|
||
|
The parallel compression feature typically works with a significant
|
||
|
amount of metadata related to the management of the data chunks
|
||
|
in datasets. In initial performance results gathered from various
|
||
|
HPC machines, it was found that the parallel compression feature
|
||
|
did not scale well at around 8k MPI ranks and beyond. On further
|
||
|
investigation, it became obvious that the bottleneck was due to
|
||
|
heavy filesystem pressure from the metadata management for dataset
|
||
|
data chunks as they changed size (as a result of data compression)
|
||
|
and moved around in the HDF5 file.
|
||
|
|
||
|
Enabling collective metadata operations in the HDF5 application
|
||
|
(as in the below snippet) showed significant improvement in
|
||
|
performance and scalability and is generally always recommended
|
||
|
unless application performance shows negative benefits by doing
|
||
|
so.
|
||
|
|
||
|
```
|
||
|
...
|
||
|
hid_t fapl_id = H5Pcreate(H5P_FILE_ACCESS);
|
||
|
H5Pset_fapl_mpio(fapl_id, MPI_COMM_WORLD, MPI_INFO_NULL);
|
||
|
H5Pset_all_coll_metadata_ops(fapl_id, 1);
|
||
|
H5Pset_coll_metadata_write(fapl_id, 1);
|
||
|
hid_t file_id = H5Fcreate("file.h5", H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id);
|
||
|
...
|
||
|
```
|
||
|
|
||
|
### Align chunks in the file
|
||
|
|
||
|
The natural layout of an HDF5 file may cause dataset data
|
||
|
chunks to end up at addresses in the file that do not align
|
||
|
well with the underlying file system, possibly leading to
|
||
|
poor performance. As an example, Lustre performance is generally
|
||
|
good when writes are aligned with the chosen stripe size.
|
||
|
The HDF5 application can use [H5Pset_alignment](https://portal.hdfgroup.org/display/HDF5/H5P_SET_ALIGNMENT)
|
||
|
to have a bit more control over where objects in the HDF5
|
||
|
file end up. However, do note that setting the alignment
|
||
|
of objects generally wastes space in the file and has the
|
||
|
potential to dramatically increase its resulting size, so
|
||
|
caution should be used when choosing the alignment parameters.
|
||
|
|
||
|
[H5Pset_alignment](https://portal.hdfgroup.org/display/HDF5/H5P_SET_ALIGNMENT)
|
||
|
has two parameters that control the alignment of objects in
|
||
|
the HDF5 file, the "threshold" value and the alignment
|
||
|
value. The threshold value specifies that any object greater
|
||
|
than or equal in size to that value will be aligned in the
|
||
|
file at addresses which are multiples of the chosen alignment
|
||
|
value. While the value 0 can be specified for the threshold
|
||
|
to make every object in the file be aligned according to
|
||
|
the alignment value, this isn't generally recommended, as it
|
||
|
will likely waste an excessive amount of space in the file.
|
||
|
|
||
|
In the example below, the chosen dataset chunk size is
|
||
|
provided for the threshold value and 1MiB is specified for
|
||
|
the alignment value. Assuming that 1MiB is an optimal
|
||
|
alignment value (e.g., assuming that it matches well with
|
||
|
the Lustre stripe size), this should cause dataset data
|
||
|
chunks to be well-aligned and generally give good write
|
||
|
performance.
|
||
|
|
||
|
```
|
||
|
hid_t fapl_id = H5Pcreate(H5P_FILE_ACCESS);
|
||
|
H5Pset_fapl_mpio(fapl_id, MPI_COMM_WORLD, MPI_INFO_NULL);
|
||
|
/* Assuming Lustre stripe size is 1MiB, align data chunks
|
||
|
in the file to address multiples of 1MiB. */
|
||
|
H5Pset_alignment(fapl_id, dataset_chunk_size, 1048576);
|
||
|
hid_t file_id = H5Fcreate("file.h5", H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id);
|
||
|
```
|
||
|
|
||
|
### File free space managers
|
||
|
|
||
|
As data chunks in a dataset get written to and compressed,
|
||
|
they can change in size and be relocated in the HDF5 file.
|
||
|
Since parallel compression usually involves many data chunks
|
||
|
in a file, this can create significant amounts of free space
|
||
|
in the file over its lifetime and eventually cause performance
|
||
|
issues.
|
||
|
|
||
|
An HDF5 application can use [H5Pset_file_space_strategy](http://portal.hdfgroup.org/display/HDF5/H5P_SET_FILE_SPACE_STRATEGY)
|
||
|
with a value of `H5F_FSPACE_STRATEGY_PAGE` to enable the paged
|
||
|
aggregation feature, which can accumulate metadata and raw
|
||
|
data for dataset data chunks into well-aligned, configurably
|
||
|
sized "pages" for better performance. However, note that using
|
||
|
the paged aggregation feature will cause any setting from
|
||
|
[H5Pset_alignment](https://portal.hdfgroup.org/display/HDF5/H5P_SET_ALIGNMENT)
|
||
|
to be ignored. While an application should be able to get
|
||
|
comparable performance effects by [setting the size of these pages](http://portal.hdfgroup.org/display/HDF5/H5P_SET_FILE_SPACE_PAGE_SIZE) to be equal to the value that
|
||
|
would have been set for [H5Pset_alignment](https://portal.hdfgroup.org/display/HDF5/H5P_SET_ALIGNMENT),
|
||
|
this may not necessarily be the case and should be studied.
|
||
|
|
||
|
Note that [H5Pset_file_space_strategy](http://portal.hdfgroup.org/display/HDF5/H5P_SET_FILE_SPACE_STRATEGY)
|
||
|
has a `persist` parameter. This determines whether or not the
|
||
|
file free space manager should include extra metadata in the
|
||
|
HDF5 file about free space sections in the file. If this
|
||
|
parameter is `false`, any free space in the HDF5 file will
|
||
|
become unusable once the HDF5 file is closed. For parallel
|
||
|
compression, it's generally recommended that `persist` be set
|
||
|
to `true`, as this will keep better track of file free space
|
||
|
for data chunks between accesses to the HDF5 file.
|
||
|
|
||
|
```
|
||
|
hid_t fcpl_id = H5Pcreate(H5P_FILE_CREATE);
|
||
|
/* Use persistent free space manager with paged aggregation */
|
||
|
H5Pset_file_space_strategy(fcpl_id, H5F_FSPACE_STRATEGY_PAGE, 1, 1);
|
||
|
/* Assuming Lustre stripe size is 1MiB, set page size to that */
|
||
|
H5Pset_file_space_page_size(fcpl_id, 1048576);
|
||
|
...
|
||
|
hid_t file_id = H5Fcreate("file.h5", H5F_ACC_TRUNC, fcpl_id, fapl_id);
|
||
|
```
|
||
|
|
||
|
### Low-level collective vs. independent I/O
|
||
|
|
||
|
While the parallel compression feature requires that the HDF5
|
||
|
application set and maintain collective I/O at the application
|
||
|
interface level (via [H5Pset_dxpl_mpio](https://portal.hdfgroup.org/display/HDF5/H5P_SET_DXPL_MPIO)),
|
||
|
it does not require that the actual MPI I/O that occurs at
|
||
|
the lowest layers of HDF5 be collective; independent I/O may
|
||
|
perform better depending on the application I/O patterns and
|
||
|
parallel file system performance, among other factors. The
|
||
|
application may use [H5Pset_dxpl_mpio_collective_opt](https://portal.hdfgroup.org/display/HDF5/H5P_SET_DXPL_MPIO_COLLECTIVE_OPT)
|
||
|
to control this setting and see which I/O method provides the
|
||
|
best performance.
|
||
|
|
||
|
```
|
||
|
hid_t dxpl_id = H5Pcreate(H5P_DATASET_XFER);
|
||
|
H5Pset_dxpl_mpio(dxpl_id, H5FD_MPIO_COLLECTIVE);
|
||
|
H5Pset_dxpl_mpio_collective_opt(dxpl_id, H5FD_MPIO_INDIVIDUAL_IO); /* Try independent I/O */
|
||
|
H5Dwrite(..., dxpl_id, ...);
|
||
|
```
|
||
|
|
||
|
### Runtime HDF5 Library version
|
||
|
|
||
|
An HDF5 application can use the [H5Pset_libver_bounds](http://portal.hdfgroup.org/display/HDF5/H5P_SET_LIBVER_BOUNDS)
|
||
|
routine to set the upper and lower bounds on library versions
|
||
|
to use when creating HDF5 objects. For parallel compression
|
||
|
specifically, setting the library version to the latest available
|
||
|
version can allow access to better/more efficient chunk indexing
|
||
|
types and data encoding methods. For example:
|
||
|
|
||
|
```
|
||
|
...
|
||
|
hid_t fapl_id = H5Pcreate(H5P_FILE_ACCESS);
|
||
|
H5Pset_libver_bounds(fapl_id, H5F_LIBVER_LATEST, H5F_LIBVER_LATEST);
|
||
|
hid_t file_id = H5Fcreate("file.h5", H5F_ACC_TRUNC, H5P_DEFAULT, fapl_id);
|
||
|
...
|
||
|
```
|