Example #1
0
    def to_hdf5(self, f, dataset, create=False):
        """Parallel write into a contiguous HDF5 dataset.

        Parameters
        ----------
        filename : str, h5py.File or h5py.Group
            File to write dataset into.
        dataset : string
            Name of dataset to write into. Should not exist.
        """

        ## Naive non-parallel implementation to start

        import h5py

        if not h5py.get_config().mpi:
            if isinstance(f, basestring):
                self._to_hdf5_serial(f, dataset, create)
                return
            else:
                raise ValueError(
                    "Argument must be a filename if h5py does not have MPI support"
                )

        mode = 'a' if create else 'r+'

        fh = misc.open_h5py_mpi(f, mode, self.comm)

        dset = fh.create_dataset(dataset, shape=self.global_shape, dtype=self.dtype)

        start = self.local_offset[self.axis]
        end = start + self.local_shape[self.axis]

        # Construct slices for axis
        sl = [slice(None, None)] * self.axis
        sl += [slice(start, end)]
        sl = tuple(sl)

        # Check that there are no null slices, otherwise we need to turn off
        # collective IO to work around an h5py issue (#965)
        no_null_slices = self.global_shape[self.axis] >= self.comm.size

        if fh.is_mpi and no_null_slices:
            with dset.collective:
                dset[sl] = self[:]
        else:
            dset[sl] = self[:]

        if fh.opened:
            fh.close()
Example #2
0
    def to_hdf5(
        self,
        f,
        dataset,
        create=False,
        chunks=None,
        compression=None,
        compression_opts=None,
    ):
        """Parallel write into a contiguous HDF5 dataset.

        Parameters
        ----------
        filename : str, h5py.File or h5py.Group
            File to write dataset into.
        dataset : string
            Name of dataset to write into. Should not exist.
        """

        import h5py

        if not h5py.get_config().mpi:
            if isinstance(f, basestring):
                self._to_hdf5_serial(f, dataset, create)
                return
            else:
                raise ValueError(
                    "Argument must be a filename if h5py does not have MPI support"
                )

        mode = "a" if create else "r+"

        fh = misc.open_h5py_mpi(f, mode, self.comm)

        start = self.local_offset[self.axis]
        end = start + self.local_shape[self.axis]

        # Construct slices for axis
        sel = ([slice(None, None)] * self.axis) + [slice(start, end)]
        sel = _expand_sel(sel, self.ndim)

        # Check that there are no null slices, otherwise we need to turn off
        # collective IO to work around an h5py issue (#965)
        no_null_slices = self.global_shape[self.axis] >= self.comm.size

        # Split the axis to get the IO size under ~2GB (only if MPI-IO)
        split_axis, partitions = self._partition_io(skip=(not fh.is_mpi))

        # Only use collective IO if:
        # - there are no null slices (h5py bug)
        # - we are not distributed over axis=0 as there is no advantage for
        #   collective IO which is usually slow
        # - unless we want to use compression/chunking
        # TODO: change if h5py bug fixed
        # TODO: better would be a test on contiguous IO size
        use_collective = (fh.is_mpi and no_null_slices
                          and (self.axis > 0 or compression is not None))

        if fh.is_mpi and not use_collective:
            # Need to disable compression if we can't use collective IO
            chunks, compression, compression_opts = None, None, None

        dset = fh.create_dataset(
            dataset,
            shape=self.global_shape,
            dtype=self.dtype,
            chunks=chunks,
            compression=compression,
            compression_opts=compression_opts,
        )

        # Read using collective MPI-IO if specified
        with dset.collective if use_collective else DummyContext():

            # Loop over partitions of the IO and perform them
            for part in partitions:
                islice, fslice = _partition_sel(sel, split_axis,
                                                self.global_shape[split_axis],
                                                part)
                dset[islice] = self[fslice]

        if fh.opened:
            fh.close()
Example #3
0
    def from_hdf5(cls, f, dataset, comm=None, axis=0, sel=None):
        """Read MPIArray from an HDF5 dataset in parallel.

        Parameters
        ----------
        f : filename, or `h5py.File` object
            File to read dataset from.
        dataset : string
            Name of dataset to read from. Must exist.
        comm : MPI.Comm, optional
            MPI communicator to distribute over. If `None` optional, use
            `MPI.COMM_WORLD`.
        axis : int, optional
            Axis over which the read should be distributed. This can be used
            to select the most efficient axis for the reading.
        sel : tuple, optional
            A tuple of slice objects used to make a selection from the array
            *before* reading. The output will be this selection from the dataset
            distributed over the given axis.

        Returns
        -------
        array : MPIArray
        """
        fh = misc.open_h5py_mpi(f, 'r', comm)

        dset = fh[dataset]
        dshape = dset.shape  # Shape of the underlying dataset
        naxis = len(dshape)
        dtype = dset.dtype

        # Check that the axis is valid and wrap to an actual position
        if axis < -naxis or axis >= naxis:
            raise ValueError("Distributed axis %i not in range (%i, %i)" % (axis, -naxis, naxis-1))
        axis = naxis + axis if axis < 0 else axis

        # Ensure sel is defined to cover all axes
        if sel is None:
            sel = [slice(None)] * naxis
        if len(sel) < naxis:
            sel = list(sel) + [slice(None)] * (naxis - len(sel))
        sel = list(sel)

        # Figure out the final array size and create it
        gshape = []
        for l, sl in zip(dshape, sel):
            start, end, stride = sl.indices(l)
            n = 1 + (end - start - 1) // stride
            gshape.append(n)
        dist_arr = cls(gshape, axis=axis, comm=comm, dtype=dtype)

        # Get the local start and end indices
        lstart = dist_arr.local_offset[axis]
        lend = lstart + dist_arr.local_shape[axis]

        # Create the slice object into the dataset by resolving the rank's slice on the sel
        dstart, dend, dstride = sel[axis].indices(dshape[axis])
        sel[axis] = slice(dstart + lstart * dstride, dstart + lend * dstride, dstride)
        sel = tuple(sel)

        # Check that there are no null slices, otherwise we need to turn off
        # collective IO to work around an h5py issue (#965)
        no_null_slices = dist_arr.global_shape[axis] >= dist_arr.comm.size

        # Read using MPI-IO if possible
        if fh.is_mpi and no_null_slices:
            with dset.collective:
                dist_arr[:] = dset[sel]
        else:
            dist_arr[:] = dset[sel]

        if fh.opened:
            fh.close()

        return dist_arr
Example #4
0
    def from_hdf5(cls, f, dataset, comm=None, axis=0, sel=None):
        """Read MPIArray from an HDF5 dataset in parallel.

        Parameters
        ----------
        f : filename, or `h5py.File` object
            File to read dataset from.
        dataset : string
            Name of dataset to read from. Must exist.
        comm : MPI.Comm, optional
            MPI communicator to distribute over. If `None` optional, use
            `MPI.COMM_WORLD`.
        axis : int, optional
            Axis over which the read should be distributed. This can be used
            to select the most efficient axis for the reading.
        sel : tuple, optional
            A tuple of slice objects used to make a selection from the array
            *before* reading. The output will be this selection from the dataset
            distributed over the given axis.

        Returns
        -------
        array : MPIArray
        """
        from mpi4py import MPI

        # Don't both using MPI where the axis is not zero. It's probably just slower.
        # TODO: with tuning this might not be true. Keep an eye on this.
        use_mpi = axis > 0

        # Read the file. Opening with MPI if requested, and we can
        fh = misc.open_h5py_mpi(f, "r", use_mpi=use_mpi, comm=comm)

        dset = fh[dataset]
        dshape = dset.shape  # Shape of the underlying dataset
        naxis = len(dshape)
        dtype = dset.dtype

        # Check that the axis is valid and wrap to an actual position
        if axis < -naxis or axis >= naxis:
            raise ValueError("Distributed axis %i not in range (%i, %i)" %
                             (axis, -naxis, naxis - 1))
        axis = naxis + axis if axis < 0 else axis

        # Ensure sel is defined to cover all axes
        sel = _expand_sel(sel, naxis)

        # Figure out the final array size and create it
        gshape = []
        for l, sl in zip(dshape, sel):
            gshape.append(_len_slice(sl, l))
        dist_arr = cls(gshape, axis=axis, comm=comm, dtype=dtype)

        # Get the local start and end indices
        lstart = dist_arr.local_offset[axis]
        lend = lstart + dist_arr.local_shape[axis]

        # Create the slice object into the dataset by resolving the rank's slice on the sel
        sel[axis] = _reslice(sel[axis], dshape[axis], slice(lstart, lend))
        sel = tuple(sel)

        # Split the axis to get the IO size under ~2GB (only if MPI-IO)
        split_axis, partitions = dist_arr._partition_io(skip=(not fh.is_mpi))

        # Check that there are no null slices, otherwise we need to turn off
        # collective IO to work around an h5py issue (#965)
        no_null_slices = dist_arr.global_shape[axis] >= dist_arr.comm.size

        # Only use collective IO if:
        # - there are no null slices (h5py bug)
        # - we are not distributed over axis=0 as there is no advantage for
        #   collective IO which is usually slow
        # TODO: change if h5py bug fixed
        # TODO: better would be a test on contiguous IO size
        # TODO: do we need collective IO to read chunked data?
        use_collective = fh.is_mpi and no_null_slices and axis > 0

        # Read using collective MPI-IO if specified
        with dset.collective if use_collective else DummyContext():

            # Loop over partitions of the IO and perform them
            for part in partitions:
                islice, fslice = _partition_sel(sel, split_axis,
                                                gshape[split_axis], part)
                dist_arr[fslice] = dset[islice]

        if fh.opened:
            fh.close()

        return dist_arr