-
Notifications
You must be signed in to change notification settings - Fork 37
Performance fix for FieldsIO #538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
4005f26
334a264
5f20c44
bd35b4b
f186407
c99d637
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,7 +45,7 @@ | |
| Warning | ||
| ------- | ||
| To use MPI collective writing, you need to call first the class methods :class:`Rectilinear.initMPI` (cf their docstring). | ||
| Also, `Rectilinear.setHeader` **must be given the global grids coordinates**, wether the code is run in parallel or not. | ||
| Also, `Rectilinear.setHeader` **must be given the global grids coordinates**, whether the code is run in parallel or not. | ||
|
|
||
| > ⚠️ Also : this module can only be imported with **Python 3.11 or higher** ! | ||
| """ | ||
|
|
@@ -54,6 +54,7 @@ | |
| from typing import Type, TypeVar | ||
| import logging | ||
| import itertools | ||
| import warnings | ||
brownbaerchen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| T = TypeVar("T") | ||
|
|
||
|
|
@@ -202,7 +203,7 @@ def initialize(self): | |
| if not self.ALLOW_OVERWRITE: | ||
| assert not os.path.isfile( | ||
| self.fileName | ||
| ), "file already exists, use FieldsIO.ALLOW_OVERWRITE = True to allow overwriting" | ||
| ), f"file {self.fileName!r} already exists, use FieldsIO.ALLOW_OVERWRITE = True to allow overwriting" | ||
|
|
||
| with open(self.fileName, "w+b") as f: | ||
| self.hBase.tofile(f) | ||
|
|
@@ -475,7 +476,7 @@ def toVTR(self, baseName, varNames, idxFormat="{:06d}"): | |
|
|
||
| Example | ||
| ------- | ||
| >>> # Suppose the FieldsIO object is already writen into outputs.pysdc | ||
| >>> # Suppose the FieldsIO object is already written into outputs.pysdc | ||
| >>> import os | ||
| >>> from pySDC.utils.fieldsIO import Rectilinear | ||
| >>> os.makedirs("vtrFiles") # to store all VTR files into a subfolder | ||
|
|
@@ -494,12 +495,13 @@ def toVTR(self, baseName, varNames, idxFormat="{:06d}"): | |
| # MPI-parallel implementation | ||
| # ------------------------------------------------------------------------- | ||
| comm: MPI.Intracomm = None | ||
| _num_collective_IO = None | ||
|
|
||
| @classmethod | ||
| def setupMPI(cls, comm: MPI.Intracomm, iLoc, nLoc): | ||
| """ | ||
| Setup the MPI mode for the files IO, considering a decomposition | ||
| of the 1D grid into contiuous subintervals. | ||
| of the 1D grid into contiguous subintervals. | ||
|
|
||
| Parameters | ||
| ---------- | ||
|
|
@@ -515,6 +517,21 @@ def setupMPI(cls, comm: MPI.Intracomm, iLoc, nLoc): | |
| cls.nLoc = nLoc | ||
| cls.mpiFile = None | ||
|
|
||
| @property | ||
brownbaerchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def num_collective_IO(self): | ||
| """ | ||
| Number of collective IO operations. | ||
| If the distribution is unbalanced, some tasks read/write more data than others, implying that some accesses | ||
| cannot be collective, but need to be of the slower individual kind. | ||
|
|
||
| Returns: | ||
| -------- | ||
| int: Number of collective IO accesses | ||
| """ | ||
| if self._num_collective_IO is None: | ||
| self._num_collective_IO = self.comm.allreduce(self.nVar * np.prod(self.nLoc[:-1]), op=MPI.MIN) | ||
brownbaerchen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return self._num_collective_IO | ||
|
|
||
| @property | ||
| def MPI_ON(self): | ||
| """Wether or not MPI is activated""" | ||
|
|
@@ -541,7 +558,7 @@ def MPI_WRITE(self, data): | |
| """Write data (np.ndarray) in the binary file in MPI mode, at the current file cursor position.""" | ||
| self.mpiFile.Write(data) | ||
|
|
||
| def MPI_WRITE_AT(self, offset, data: np.ndarray): | ||
| def MPI_WRITE_AT(self, offset, data: np.ndarray, collective=True): | ||
| """ | ||
| Write data in the binary file in MPI mode, with a given offset | ||
| **relative to the beginning of the file**. | ||
|
|
@@ -552,10 +569,15 @@ def MPI_WRITE_AT(self, offset, data: np.ndarray): | |
| Offset to write at, relative to the beginning of the file, in bytes. | ||
| data : np.ndarray | ||
| Data to be written in the binary file. | ||
| collective : bool | ||
| Use `MPI.Write_at_all` if true and `MPI.Write_at` if false | ||
| """ | ||
| self.mpiFile.Write_at(offset, data) | ||
| if collective: | ||
| self.mpiFile.Write_at_all(offset, data) | ||
| else: | ||
| self.mpiFile.Write_at(offset, data) | ||
|
|
||
| def MPI_READ_AT(self, offset, data): | ||
| def MPI_READ_AT(self, offset, data, collective=True): | ||
| """ | ||
| Read data from the binary file in MPI mode, with a given offset | ||
| **relative to the beginning of the file**. | ||
|
|
@@ -566,8 +588,13 @@ def MPI_READ_AT(self, offset, data): | |
| Offset to read at, relative to the beginning of the file, in bytes. | ||
| data : np.ndarray | ||
| Array on which to read the data from the binary file. | ||
| collective : bool | ||
| Use `MPI.Read_at_all` if true and `MPI.Read_at` if false | ||
| """ | ||
| self.mpiFile.Read_at(offset, data) | ||
| if collective: | ||
| self.mpiFile.Read_at_all(offset, data) | ||
| else: | ||
| self.mpiFile.Read_at(offset, data) | ||
|
|
||
| def MPI_FILE_CLOSE(self): | ||
| """Close the binary file in MPI mode""" | ||
|
|
@@ -624,9 +651,11 @@ def addField(self, time, field): | |
| self.MPI_WRITE(np.array(time, dtype=T_DTYPE)) | ||
| offset0 += self.tSize | ||
|
|
||
| _num_writes = 0 | ||
| for (iVar, *iBeg) in itertools.product(range(self.nVar), *[range(n) for n in self.nLoc[:-1]]): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not exactly what I suggested ... all IO operations must be collective with _all. It's just that we have to ensure that all processes are calling exactly the same number of time MPI_WRITE_AT or MPI_READ_AT. It means that you have to add after the first for loop : for _ in range(self.num_collective_IO - _num_writes):
self.MPI_WRITE_AT(0, field[:0]) # should produce a no-opThat should avoid the deadlock
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is that better? I expect little performance hit by non-collective IO here because there are few non-collective operations relative to total operations.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still counts ... and you can have a majority of non-collective operation with some decomposition. For instance, a 2D problem of size [10,32] decomposed in the first direction in 6 sub-domains => 4 processes have 2 points, 2 have 1 point. Because you need to write contiguous chunk of data in the file, for the first loop all are doing collective write for the first points, but for the second points 4 processes are doing a non-collective operation. It's just better to always have all process doing collective write, with those who don't write anything doing a no-op, and the MPI implementation is hopefully well done enough to avoid losing anything on that.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I found some inconsistent behaviour when reading and writing nothing on some tasks on my laptop. Namely, if I print something, it goes through, but if I don't, it deadlocks. I don't understand this behaviour. I suggest we leave this with some non-collective IO operations and if you want, you can fix it when you return from vacation.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found where the problem was : Now, the |
||
| offset = offset0 + self.iPos(iVar, iBeg) * self.itemSize | ||
| self.MPI_WRITE_AT(offset, field[iVar, *iBeg]) | ||
| self.MPI_WRITE_AT(offset, field[(iVar, *iBeg)], collective=_num_writes < self.num_collective_IO) | ||
| _num_writes += 1 | ||
| self.MPI_FILE_CLOSE() | ||
|
|
||
| def iPos(self, iVar, iX): | ||
|
|
@@ -669,9 +698,11 @@ def readField(self, idx): | |
| field = np.empty((self.nVar, *self.nLoc), dtype=self.dtype) | ||
|
|
||
| self.MPI_FILE_OPEN(mode="r") | ||
| _num_reads = 0 | ||
| for (iVar, *iBeg) in itertools.product(range(self.nVar), *[range(n) for n in self.nLoc[:-1]]): | ||
| offset = offset0 + self.iPos(iVar, iBeg) * self.itemSize | ||
| self.MPI_READ_AT(offset, field[iVar, *iBeg]) | ||
| self.MPI_READ_AT(offset, field[(iVar, *iBeg)], collective=_num_reads < self.num_collective_IO) | ||
| _num_reads += 1 | ||
| self.MPI_FILE_CLOSE() | ||
|
|
||
| return t, field | ||
|
|
@@ -684,7 +715,7 @@ def initGrid(nVar, gridSizes): | |
| dim = len(gridSizes) | ||
| coords = [np.linspace(0, 1, num=n, endpoint=False) for n in gridSizes] | ||
| s = [None] * dim | ||
| u0 = np.array(np.arange(nVar) + 1)[:, *s] | ||
| u0 = np.array(np.arange(nVar) + 1)[(slice(None), *s)] | ||
| for x in np.meshgrid(*coords, indexing="ij"): | ||
| u0 = u0 * x | ||
| return coords, u0 | ||
|
|
@@ -706,8 +737,7 @@ def writeFields_MPI(fileName, dtypeIdx, algo, nSteps, nVar, gridSizes): | |
| iLoc, nLoc = blocks.localBounds | ||
| Rectilinear.setupMPI(comm, iLoc, nLoc) | ||
| s = [slice(i, i + n) for i, n in zip(iLoc, nLoc)] | ||
| u0 = u0[:, *s] | ||
| print(MPI_RANK, u0.shape) | ||
| u0 = u0[(slice(None), *s)] | ||
|
|
||
| f1 = Rectilinear(DTYPES[dtypeIdx], fileName) | ||
| f1.setHeader(nVar=nVar, coords=coords) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.