在不同进程间共享复杂的 Python 对象的内存

6
我有一个复杂的Python对象,占用大约36GB内存,我想在多个独立的Python进程之间共享它。它以pickle文件的形式存储在磁盘上,目前每个进程都需要单独加载。我想要共享这个对象,以便在可用内存量下执行更多的进程。
这个对象在某种意义上被用作只读数据库。每个进程每秒启动多个访问请求,并且每个请求仅涉及数据的一小部分。
我查看了Radis等解决方案,但最终发现数据需要被序列化为简单的文本形式。将pickle文件本身映射到内存中也不会有帮助,因为每个进程都需要将其提取。因此,我考虑了另外两个可能的解决方案:
  1. 使用共享内存,让每个进程可以访问存储对象的地址。但问题在于进程只能看到一堆字节,无法解释。
  2. 编写管理该对象并通过API调用检索数据的代码。这里我想知道这种解决方案的速度表现。
有没有简单的方法来实现这两个解决方案?也许有更好的解决方案吗?
非常感谢!
1个回答

6
对于复杂对象,没有现成的方法可以直接在进程之间共享内存。如果您有简单的ctypes,可以使用C风格的共享内存,但它不能直接映射到Python对象。
如果您每次只需要数据的一部分而不是整个36GB,则有一个简单的解决方案可行。对此,您可以使用multiprocessing.managers中的SyncManager。使用这个,您设置一个服务器,为您的数据提供代理类(您的数据不存储在类中,代理只提供访问权限)。然后,您的客户端使用BaseManager连接到服务器,并调用代理类中的方法来检索数据。
在幕后,Manager类负责将您请求的数据进行pickling并通过从服务器到客户端的开放端口发送它。因为您每次调用都要进行pickling数据,所以如果您需要整个数据集,这不是有效率的。如果您只需要客户端数据的一小部分,则该方法可以节省很多时间,因为数据只需由服务器加载一次。
该解决方案在速度上与数据库解决方案相当,但如果您希望保持纯Python解决方案,则可以节省很多复杂性和学习数据库的时间。
以下是一些旨在与GloVe词向量配合使用的示例代码。 服务器端
#!/usr/bin/python
import  sys
from    multiprocessing.managers import SyncManager
import  numpy

# Global for storing the data to be served
gVectors = {}

# Proxy class to be shared with different processes
# Don't but the big vector data in here since that will force it to 
# be piped to the other process when instantiated there, instead just
# return the global vector data, from this process, when requested.
class GloVeProxy(object):
    def __init__(self):
        pass

    def getNVectors(self):
        global gVectors
        return len(gVectors)

    def getEmpty(self):
        global gVectors
        return numpy.zeros_like(gVectors.values()[0])

    def getVector(self, word, default=None):
        global gVectors
        return gVectors.get(word, default)

# Class to encapsulate the server functionality
class GloVeServer(object):
    def __init__(self, port, fname):
        self.port = port
        self.load(fname)

    # Load the vectors into gVectors (global)
    @staticmethod
    def load(filename):
        global gVectors
        f = open(filename, 'r')
        for line in f:
            vals = line.rstrip().split(' ')
            gVectors[vals[0]] = numpy.array(vals[1:]).astype('float32')

    # Run the server
    def run(self):
        class myManager(SyncManager): pass  
        myManager.register('GloVeProxy', GloVeProxy)
        mgr = myManager(address=('', self.port), authkey='GloVeProxy01')
        server = mgr.get_server()
        server.serve_forever()

if __name__ == '__main__':
    port  = 5010
    fname = '/mnt/raid/Data/Misc/GloVe/WikiGiga/glove.6B.50d.txt'

    print 'Loading vector data'
    gs = GloVeServer(port, fname)

    print 'Serving data. Press <ctrl>-c to stop.'
    gs.run()

客户端

from   multiprocessing.managers import BaseManager
import psutil   #3rd party module for process info (not strictly required)

# Grab the shared proxy class.  All methods in that class will be availble here
class GloVeClient(object):
    def __init__(self, port):
        assert self._checkForProcess('GloVeServer.py'), 'Must have GloVeServer running'
        class myManager(BaseManager): pass
        myManager.register('GloVeProxy')
        self.mgr = myManager(address=('localhost', port), authkey='GloVeProxy01')
        self.mgr.connect()
        self.glove = self.mgr.GloVeProxy()

    # Return the instance of the proxy class
    @staticmethod
    def getGloVe(port):
        return GloVeClient(port).glove

    # Verify the server is running
    @staticmethod
    def _checkForProcess(name):
        for proc in psutil.process_iter():
            if proc.name() == name:
                return True
        return False

if __name__ == '__main__':
    port = 5010
    glove = GloVeClient.getGloVe(port)

    for word in ['test', 'cat', '123456']:
        print('%s = %s' % (word, glove.getVector(word)))

请注意,psutil库仅用于检查服务器是否正在运行,它不是必需的。请确保将服务器命名为GloVeServer.py,或者在代码中更改psutil的检查,以便它寻找正确的名称。

非常感谢,这非常优雅且接近我所希望找到的东西。正如您所写的,我没有找到适用于复杂类型的解决方案,因此您的解决方案非常适合这种情况。然而,由于速度对我很重要,最终我重新实现了我的对象,以便可以序列化它们,并建立了一个RabbitMQ RPC服务器。再次感谢! - Mega
我还建议,如果你的数据有点表格化,可以考虑使用数据库。我已经在做的很多事情中开始使用它了。一旦你在Python中编写了一个基本模板,添加更多的表格就很简单了,并且对于大多数用例来说,访问速度比上面提到的方法要快得多。 - bivouac0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接