pshmem


Namepshmem JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/tskisner/pshmem
SummaryParallel shared memory and locking with MPI
upload_time2024-03-17 21:56:20
maintainer
docs_urlNone
authorTheodore Kisner
requires_python>=3.8.0
licenseBSD
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # MPI design patterns with shared memory

This is a small package that implements parallel design patterns using MPI one-sided and
shared memory constructs.

## Installation and Requirements

This package needs a recent version of the `mpi4py` package in order to be useful.
However, the classes also accept a value of `None` for the communicator, in which case a
trivial local implementation is used.  The code uses other widely available packages
(like numpy) and requires a recent Python3 installation.  You can install the code from
a git checkout with:

    pip install .

Or:

    python3 setup.py install

Or directly from github.

## MPIShared Class

This class implements a pattern where a shared array is allocated on each node.
Processes can update pieces of the shared array with the synchronous "set()" method.
During this call, the data from the desired process is first replicated to all nodes,
and then one process on each node copies that piece into the shared array.

All processes on all nodes can freely read data from the node-local copy of the shared
array.

### Example

You can use `MPIShared` as a context manager or by explicitly creating and freeing
memory.  Here is an example of creating a shared memory object that is replicated across
nodes:

```python
import numpy as np
from mpi4py import MPI

from pshmem import MPIShared

comm = MPI.COMM_WORLD

with MPIShared((3, 5), np.float64, comm) as shm:
    # A copy of the data exists on every node and is initialized to zero.
    # There is a numpy array "view" of that memory available with slice notation
    # or by accessing the "data" member:
    if comm.rank == 0:
        # You can get a summary of the data by printing it:
        print("String representation:\n")
        print(shm)
        print("\n===== Initialized Data =====")
    for p in range(comm.size):
        if p == comm.rank:
            print("rank {}:\n".format(p), shm.data, flush=True)
        comm.barrier()

    set_data = None
    set_offset = None
    if comm.rank == 0:
        set_data = np.arange(6, dtype=np.float64).reshape((2, 3))
        set_offset = (1, 1)

    # The set() method is collective, but the inputs only matter on one rank
    shm.set(set_data, offset=set_offset, fromrank=0)

    # You can also use the usual '[]' notation.  However, this call must do an
    # additional pre-communication to detect which process the data is coming from.
    # And this line is still collective and must be called on all processes:
    shm[set_offset] = set_data

    # This updated data has now been replicated to the shared memory on all nodes.
    if comm.rank == 0:
        print("======= Updated Data =======")
    for p in range(comm.size):
        if p == comm.rank:
            print("rank {}:\n".format(p), shm.data, flush=True)
        comm.barrier()

    # You can read from the node-local copy of the data from all processes,
    # using either the "data" member or slice access:
    if comm.rank == comm.size - 1:
        print("==== Read-only access ======")
        print("rank {}: shm[2, 3] = {}".format(comm.rank, shm[2, 3]), flush=True)
        print("rank {}: shm.data = \n{}".format(comm.rank, shm.data), flush=True)

```

Putting the above code into a file `test.py` and running this on 4 processes gives:

```
mpirun -np 4 python3 test.py

String representation:

<MPIShared
  replicated on 1 nodes, each with 4 processes (4 total)
  shape = (3, 5), dtype = float64
  [ [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] ]
>

===== Initialized Data =====
rank 0:
 [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
rank 1:
 [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
rank 2:
 [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
rank 3:
 [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
======= Updated Data =======
rank 0:
 [[0. 0. 0. 0. 0.]
 [0. 0. 1. 2. 0.]
 [0. 3. 4. 5. 0.]]
rank 1:
 [[0. 0. 0. 0. 0.]
 [0. 0. 1. 2. 0.]
 [0. 3. 4. 5. 0.]]
rank 2:
 [[0. 0. 0. 0. 0.]
 [0. 0. 1. 2. 0.]
 [0. 3. 4. 5. 0.]]
rank 3:
 [[0. 0. 0. 0. 0.]
 [0. 0. 1. 2. 0.]
 [0. 3. 4. 5. 0.]]
==== Read-only access ======
rank 3: shm[2, 3] = 5.0
rank 3: shm.data =
[[0. 0. 0. 0. 0.]
 [0. 0. 1. 2. 0.]
 [0. 3. 4. 5. 0.]]
 ```

Note that if you are not using a context manager, then you should be careful to close
and delete the object like this:

```python
shm = MPIShared((3, 5), np.float64, comm=comm)
# Do stuff
shm.close()
del shm
```

## MPILock Class

This implements a MUTEX lock across an arbitrary communicator.  A memory buffer on a
single process acts as a waiting list where processes can add themselves (using
one-sided calls).  The processes pass a token to transfer ownership of the lock.  The
token is passed in order of request.

### Example

A typical use case is where we want to serialize some operation across a large number of
processes that reside on different nodes.  For example, perhaps we are making requests
to the external network from a computing center and we do not want to saturate that with
all processes simultaneously.  Or perhaps we are writing to a shared data file which
does not support parallel writes and we have a sub-communicator of writing processes
which take turns updating the filesystem.  We can instantiate a lock on any
communicator, so it is possible to split the world communicator into groups and have
some operation serialized just within that group:

```python
with MPILock(MPI.COMM_WORLD) as mpilock:
    mpilock.lock()
    # Do something here.  Only one process at a time will do this.
    mpilock.unlock()
```

## Tests

After installation, you can run some tests with:

    mpirun -np 4 python3 -c 'import pshmem.test; pshmem.test.run()'

If you have mpi4py available but would like to explicitly disable the use of MPI in the
tests, you can set an environment variable:

    MPI_DISABLE=1 python3 -c 'import pshmem.test; pshmem.test.run()'

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/tskisner/pshmem",
    "name": "pshmem",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Theodore Kisner",
    "author_email": "work@theodorekisner.com",
    "download_url": "https://files.pythonhosted.org/packages/84/54/65bcfe0a58e205c73d2bc3962336530f058228365eeddc51490e1c58c1bc/pshmem-1.1.0.tar.gz",
    "platform": null,
    "description": "# MPI design patterns with shared memory\n\nThis is a small package that implements parallel design patterns using MPI one-sided and\nshared memory constructs.\n\n## Installation and Requirements\n\nThis package needs a recent version of the `mpi4py` package in order to be useful.\nHowever, the classes also accept a value of `None` for the communicator, in which case a\ntrivial local implementation is used.  The code uses other widely available packages\n(like numpy) and requires a recent Python3 installation.  You can install the code from\na git checkout with:\n\n    pip install .\n\nOr:\n\n    python3 setup.py install\n\nOr directly from github.\n\n## MPIShared Class\n\nThis class implements a pattern where a shared array is allocated on each node.\nProcesses can update pieces of the shared array with the synchronous \"set()\" method.\nDuring this call, the data from the desired process is first replicated to all nodes,\nand then one process on each node copies that piece into the shared array.\n\nAll processes on all nodes can freely read data from the node-local copy of the shared\narray.\n\n### Example\n\nYou can use `MPIShared` as a context manager or by explicitly creating and freeing\nmemory.  Here is an example of creating a shared memory object that is replicated across\nnodes:\n\n```python\nimport numpy as np\nfrom mpi4py import MPI\n\nfrom pshmem import MPIShared\n\ncomm = MPI.COMM_WORLD\n\nwith MPIShared((3, 5), np.float64, comm) as shm:\n    # A copy of the data exists on every node and is initialized to zero.\n    # There is a numpy array \"view\" of that memory available with slice notation\n    # or by accessing the \"data\" member:\n    if comm.rank == 0:\n        # You can get a summary of the data by printing it:\n        print(\"String representation:\\n\")\n        print(shm)\n        print(\"\\n===== Initialized Data =====\")\n    for p in range(comm.size):\n        if p == comm.rank:\n            print(\"rank {}:\\n\".format(p), shm.data, flush=True)\n        comm.barrier()\n\n    set_data = None\n    set_offset = None\n    if comm.rank == 0:\n        set_data = np.arange(6, dtype=np.float64).reshape((2, 3))\n        set_offset = (1, 1)\n\n    # The set() method is collective, but the inputs only matter on one rank\n    shm.set(set_data, offset=set_offset, fromrank=0)\n\n    # You can also use the usual '[]' notation.  However, this call must do an\n    # additional pre-communication to detect which process the data is coming from.\n    # And this line is still collective and must be called on all processes:\n    shm[set_offset] = set_data\n\n    # This updated data has now been replicated to the shared memory on all nodes.\n    if comm.rank == 0:\n        print(\"======= Updated Data =======\")\n    for p in range(comm.size):\n        if p == comm.rank:\n            print(\"rank {}:\\n\".format(p), shm.data, flush=True)\n        comm.barrier()\n\n    # You can read from the node-local copy of the data from all processes,\n    # using either the \"data\" member or slice access:\n    if comm.rank == comm.size - 1:\n        print(\"==== Read-only access ======\")\n        print(\"rank {}: shm[2, 3] = {}\".format(comm.rank, shm[2, 3]), flush=True)\n        print(\"rank {}: shm.data = \\n{}\".format(comm.rank, shm.data), flush=True)\n\n```\n\nPutting the above code into a file `test.py` and running this on 4 processes gives:\n\n```\nmpirun -np 4 python3 test.py\n\nString representation:\n\n<MPIShared\n  replicated on 1 nodes, each with 4 processes (4 total)\n  shape = (3, 5), dtype = float64\n  [ [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.] ]\n>\n\n===== Initialized Data =====\nrank 0:\n [[0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]]\nrank 1:\n [[0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]]\nrank 2:\n [[0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]]\nrank 3:\n [[0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]\n [0. 0. 0. 0. 0.]]\n======= Updated Data =======\nrank 0:\n [[0. 0. 0. 0. 0.]\n [0. 0. 1. 2. 0.]\n [0. 3. 4. 5. 0.]]\nrank 1:\n [[0. 0. 0. 0. 0.]\n [0. 0. 1. 2. 0.]\n [0. 3. 4. 5. 0.]]\nrank 2:\n [[0. 0. 0. 0. 0.]\n [0. 0. 1. 2. 0.]\n [0. 3. 4. 5. 0.]]\nrank 3:\n [[0. 0. 0. 0. 0.]\n [0. 0. 1. 2. 0.]\n [0. 3. 4. 5. 0.]]\n==== Read-only access ======\nrank 3: shm[2, 3] = 5.0\nrank 3: shm.data =\n[[0. 0. 0. 0. 0.]\n [0. 0. 1. 2. 0.]\n [0. 3. 4. 5. 0.]]\n ```\n\nNote that if you are not using a context manager, then you should be careful to close\nand delete the object like this:\n\n```python\nshm = MPIShared((3, 5), np.float64, comm=comm)\n# Do stuff\nshm.close()\ndel shm\n```\n\n## MPILock Class\n\nThis implements a MUTEX lock across an arbitrary communicator.  A memory buffer on a\nsingle process acts as a waiting list where processes can add themselves (using\none-sided calls).  The processes pass a token to transfer ownership of the lock.  The\ntoken is passed in order of request.\n\n### Example\n\nA typical use case is where we want to serialize some operation across a large number of\nprocesses that reside on different nodes.  For example, perhaps we are making requests\nto the external network from a computing center and we do not want to saturate that with\nall processes simultaneously.  Or perhaps we are writing to a shared data file which\ndoes not support parallel writes and we have a sub-communicator of writing processes\nwhich take turns updating the filesystem.  We can instantiate a lock on any\ncommunicator, so it is possible to split the world communicator into groups and have\nsome operation serialized just within that group:\n\n```python\nwith MPILock(MPI.COMM_WORLD) as mpilock:\n    mpilock.lock()\n    # Do something here.  Only one process at a time will do this.\n    mpilock.unlock()\n```\n\n## Tests\n\nAfter installation, you can run some tests with:\n\n    mpirun -np 4 python3 -c 'import pshmem.test; pshmem.test.run()'\n\nIf you have mpi4py available but would like to explicitly disable the use of MPI in the\ntests, you can set an environment variable:\n\n    MPI_DISABLE=1 python3 -c 'import pshmem.test; pshmem.test.run()'\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "Parallel shared memory and locking with MPI",
    "version": "1.1.0",
    "project_urls": {
        "Homepage": "https://github.com/tskisner/pshmem"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a5b28840d0be009d64b2199998782d084317402819f94add0fe10217e4482bf9",
                "md5": "508b0891cb103e7186186731170b4e66",
                "sha256": "3331a5c0200aa5e7ea0176e552eee720b3530481dc40da533023408de9e170ed"
            },
            "downloads": -1,
            "filename": "pshmem-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "508b0891cb103e7186186731170b4e66",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8.0",
            "size": 19295,
            "upload_time": "2024-03-17T21:56:22",
            "upload_time_iso_8601": "2024-03-17T21:56:22.909600Z",
            "url": "https://files.pythonhosted.org/packages/a5/b2/8840d0be009d64b2199998782d084317402819f94add0fe10217e4482bf9/pshmem-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "845465bcfe0a58e205c73d2bc3962336530f058228365eeddc51490e1c58c1bc",
                "md5": "57add8cdba8c69dd59404f25f18042e1",
                "sha256": "72445b872f5a0d85ef5ac0dbfaedc4413f5a66aa97244b1823d4d81f19a9492e"
            },
            "downloads": -1,
            "filename": "pshmem-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "57add8cdba8c69dd59404f25f18042e1",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8.0",
            "size": 37636,
            "upload_time": "2024-03-17T21:56:20",
            "upload_time_iso_8601": "2024-03-17T21:56:20.857135Z",
            "url": "https://files.pythonhosted.org/packages/84/54/65bcfe0a58e205c73d2bc3962336530f058228365eeddc51490e1c58c1bc/pshmem-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-17 21:56:20",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "tskisner",
    "github_project": "pshmem",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pshmem"
}
        
Elapsed time: 0.24384s