本文从本人简书博客同步过来
在上一篇中我们介绍了 mpi4py 中的客户端-服务器编程方法,下面我们将介绍 mpi4py 中的 memory 对象及内存操作。
Python 是一种比较高级的动态编程语言,通过其提供的高级对象和语法,我们一般不需要直接同底层的内存操作打交道,比如说,在 Python 中,我们一般不会如在 C/C++ 等语言中一样先分配内存区域,然后使用分配的内存,使用完后再释放内存,Python 会为我们自动进行内存管理,并在合适的时候自动进行垃圾回收,以释放一些不再使用的对象所占用的内存空间。
MPI 标准一般是用像 C 这样较低级的语言实现的,在 MPI 的很多操作中都涉及对内存的直接使用,如消息传递的内存缓冲区,单边通信中的动态窗口创建及内存加载/卸载等,因此 MPI 中提供了若干对内存的操作和管理函数。
mpi4py 对 MPI 标准实现进行了封装,提供了比较高级的 Python 使用接口,使我们能够非常方便地在 Python 语言中以 Python 的高级语法形式使用 MPI。使用 mpi4py,对内存的直接底层操作是应该尽量避免的,因为这并不符合 Python 的编程哲学和编程方式。因此,mpi4py 提供了 MPI.memory 对象,封装和隐藏了对内存的底层操作,为我们提供了比较高级的符合 Python 编程方式的方法接口。如果涉及到内存操作,我们应该尽量使用 MPI.memory 对象。当然,mpi4py 也提供了若干内存操作函数,这些是对 MPI 标准中对应函数的直接封装和调用。
MPI.memory.frombuffer(obj, bint readonly=False)
MPI.memory 类的静态方法,由参数 obj
的缓冲区创建并返回一个 MPI.memory 对象,readonly
指定创建的 MPI.memory 对象是否是只读的。obj
必须是实现了缓冲区协议(buffer protocal)的对象。
MPI.memory.fromaddress(address, nbytes, bint readonly=False)
MPI.memory 类的静态方法,由内存地址 address
开始的连续 nbytes
字节的一块内存区创建并返回一个 MPI.memory 对象,readonly
指定创建的 MPI.memory 对象是否是只读的。
MPI.memory.tobytes(self)
将该 MPI.memory 对象所包含的内存区的数据作为一个 byte string 返回。
MPI.memory.release(self)
释放该 MPI.memory 对象所包含的内存区。
MPI.memory.__len__(self)
返回该 MPI.memory 对象所包含的内存区的大小(以字节为单位计算),对一个没有包含内存区的 MPI.memory 对象返回 0。
MPI.memory.__getitem__(self, object item)
通过 m[index] 操作返回当前 MPI.memory 对象内存缓冲区 index 处的数据。
MPI.memory.__setitem__(self, object item, object value)
通过 m[index] = value 操作设置当前 MPI.memory 对象内存缓冲区 index 处的数据为 value。
MPI.memory.address
当前 MPI.mempry 对象内存缓冲区的地址。
MPI.memory.nbytes
当前 MPI.mempry 对象内存缓冲区的大小(以字节为单位计算)。
MPI.memory.readonly
当前 MPI.mempry 对象内存缓冲区是否为只读。
MPI.Alloc_mem(Aint size, Info info=INFO_NULL)
在内存中分配 size
大小(以字节为单位计算)的区域,返回由分配区域创建的 MPI.memory 对象。
MPI.Get_address(location)
获取并返回内存中某个位置 location
的地址。
MPI.Free_mem(mem)
释放由 MPI.Alloc_mem 分配的内存区。注意:只能用该函数释放由 MPI.Alloc_mem 分配的内存空间,如果用该函数释放一个不是由 MPI.Alloc_mem 分配的 MPI.memory 对象则会出错。
MPI.memory 对象实现了 Python 的缓冲区协议(buffer protocal, PEP 3118),因此可以使用 Python 的 buffer 函数获取其缓冲区,或者通过 memoryview 对象来获取和使用其内存缓冲区中的数据(可以避免额外的复制操作)。目前,Python 已经不推荐使用 buffer 函数,将来可能会废弃 buffer 函数,因此建议使用 memoryview 对象。它们的函数/方法接口如下:
buffer(object[, offset[, size]])
class memoryview(obj)
memoryview.tobytes()
memoryview.tolist()
另外 memoryview 对象有属性 format,itemsize,shape,ndim,strides,readonly。
buffer 函数和 memoryview 对象都是 Python 内置的对象,读者可以参考 Python 的相关文档,在此不作进一步介绍。
下面给出使用例程。
# mem.py
"""
Demonstrates memory operations.
Run this with 1 processes like:
$ mpiexec -n 1 python mem.py
or
$ python mem.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.rank
# create a MPI.memory object from a byte string
mem = MPI.memory.frombuffer(b"abc", readonly=True)
print 'mem.address:', mem.address
print 'mem.nbytes:', mem.nbytes
print 'mem.readonly:', mem.readonly
# get the read-only buffer of mem
buf = buffer(mem)
print 'buffer:', buf
# create a memory view of mem
mv = memoryview(mem)
print 'mv[0]:', mv[0]
try:
# try to modify a readonly buffer
mv[0] = 'x'
except TypeError as e:
print e
# release the memory object
mem.release()
print
# create a MPI.memory object from a byte array, which stands for ASCII code a, b, c
mem = MPI.memory.frombuffer(bytearray([97, 98, 99]), readonly=False)
addr = mem.address
nbytes = mem.nbytes
# create a new MPI.memory object which shares the memory of mem
mem1 = MPI.memory.fromaddress(addr, nbytes, readonly=False)
# get the read-only buffer of mem
buf = buffer(mem)
print 'buffer:', buf
try:
# try to change the read-only buffer object
buf[0] = b'x'
except TypeError as e:
print e
# create a memory view of mem
mv = memoryview(mem)
# create a memory view of mem1
mv1 = memoryview(mem1)
print 'before change: mv[0] = %s, mv1[0] = %s' % (mv[0], mv1[0])
# change mv[0]
mv[0] = 'x'
print 'after change: mv[0] = %s, mv1[0] = %s' % (mv[0], mv1[0])
# release the memory object
mem.release()
print
# allocate a memory of 40 bytes, return a MPI.memory object
mem = MPI.Alloc_mem(10*4)
print 'len(mem):', len(mem)
print 'mem.address:', MPI.Get_address(mem)
# create a numpy array from the allocated memory
# NOTE: use copy = False here to avoid the copy
buf = np.array(mem, dtype='B', copy=False)
# cast to be int array
npary = np.ndarray(buffer=buf, dtype='i', shape=(10,))
# now you can operate the memory buffer by the usual numpy array operations
npary[:] = np.arange(10)
print 'npary.tobytes:', npary.tobytes()
# release the memory object
MPI.Free_mem(mem)
print 'len(mem) after free:', len(mem)
运行结果如下:
$ python mem.py
mem.address: 139804154439796
mem.nbytes: 3
mem.readonly: True
buffer: abc
mv[0]: a
cannot modify read-only memory
buffer: abc
buffer is read-only
before change: mv[0] = a, mv1[0] = a
after change: mv[0] = x, mv1[0] = x
len(mem): 40
mem.address: 50202096
npary.tobytes:
len(mem) after free: 0
以上介绍了 mpi4py 中的 memory 对象及内存操作,在下一篇中我们将介绍 mpi4py 中的简单并行 I/O 操作。