マルチプロセッシングPool.map()を使用すると、<type 'instancemethod'>をピクルできません


218

multiprocessingPool.map()機能を使用して、作業を同時に分割しようとしています。次のコードを使用すると、正常に動作します。

import multiprocessing

def f(x):
    return x*x

def go():
    pool = multiprocessing.Pool(processes=4)        
    print pool.map(f, range(10))


if __name__== '__main__' :
    go()

ただし、よりオブジェクト指向のアプローチで使用すると、機能しません。表示されるエラーメッセージは次のとおりです。

PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed

これは、以下が私のメインプログラムである場合に発生します。

import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()

そして以下は私のsomeClassクラスです:

import multiprocessing

class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(self.f, range(10))

誰もが問題が何であるか、またはそれを簡単に回避する方法を知っていますか?


4
fがネストされた関数の場合、同様のエラーが発生しますPicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg

回答:


122

問題は、マルチプロセッシングが物事をピクルスにしてプロセス間でスリングする必要があり、バインドされたメソッドがピクル可能でないことです。回避策( "簡単"かどうかに関係なく;-)は、プログラムにインフラストラクチャを追加して、そのようなメソッドをpickle化できるようにし、copy_reg標準ライブラリメソッドに登録することです。

たとえば、このスレッドスレッドの終わりに向かって)に対するSteven Bethardの貢献は、を介したメソッドのpickle / unpickleを可能にする完全に実行可能なアプローチの1つを示していcopy_regます。


それは素晴らしいです-ありがとう。とにかく、何らかの方法で進歩したようです:pastebin.ca/1693348のコードを使用して、RuntimeErrorを取得します:最大再帰深度を超えました。私は周りを見回し、フォーラムの投稿の1つで最大深度を1500に(デフォルトの1000から)増やすことを勧めましたが、そこには喜びがありませんでした。正直なところ、なんらかの理由でコードがループ内でピクルされたりアンピクルされたりしない限り、(少なくとも私のコードの)部分が制御不能に再帰している可能性があることを確認できません。スティーブンのコードはOOですか?
ベントリン2009年

1
あなたの_pickle_methodリターンself._unpickle_method、バインドされた方法。したがって、もちろんpickleはこれをpickle化しようとします-そして、あなたが言ったように_pickle_method、再帰的にを呼び出すことでそれを行います。すなわちによってOO、このようにコードをINGの、あなたは必然的に無限再帰を導入しています。Stevenのコードに戻ることをお勧めします(適切でない場合はOOの祭壇で崇拝しないでください。Pythonの多くのことは、より機能的な方法で行うのが最善ですが、これはその1つです)。
Alex Martelli、


15
スーパースーパーレイジーについては、実際のマングルされていないコードを投稿するのに苦労した唯一の回答を参照してください...
Cerin

2
修正するもう一つの方法は、/酸洗問題はディルを使用している回避、私の答えを参照stackoverflow.com/questions/8804830/...
rocksportrocker

74

標準ライブラリの外に飛び出さない限り、マルチプロセッシングとピクリングは機能しなくなり制限があるため、これらのソリューションはすべて醜いです。

multiprocessingcalledのフォークを使用するとpathos.multiprocesssing、マルチプロセッシングのmap関数でクラスとクラスメソッドを直接使用できます。これは、あるdillの代わりに使用されているpicklecPickle、およびdillPythonでほとんど何をシリアル化することができます。

pathos.multiprocessing非同期のマップ関数も提供します…そして、それはmap複数の引数(例えばmap(math.pow, [1,2,3], [4,5,6]))で機能することができます

参照: multiprocessingとdillは何ができるのですか?

および:http : //matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/

>>> import pathos.pools as pp
>>> p = pp.ProcessPool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]

そして明示的に言えば、最初にやりたかったことを正確に実行でき、必要に応じてインタープリタから実行できます。

>>> import pathos.pools as pp
>>> class someClass(object):
...   def __init__(self):
...     pass
...   def f(self, x):
...     return x*x
...   def go(self):
...     pool = pp.ProcessPool(4)
...     print pool.map(self.f, range(10))
... 
>>> sc = someClass()
>>> sc.go()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> 

ここでコードを取得:https : //github.com/uqfoundation/pathos


3
pathos.multiprocessingはもう存在しないため、pathos.ppに基づいてこの回答を更新していただけますか?
Saheel Godhane 2015

10
私はpathos著者です。あなたが参照しているバージョンは数年前のものです。githubのバージョンを試してください。pathos.ppまたはgithub.com/uqfoundation/ppftを使用できます。
Mike McKerns、2015

1
またはgithub.com/uqfoundation/pathos。@SaheelGodhane:新しいリリースは長い間遅れていますが、まもなくリリースされる予定です。
Mike McKerns、2015

3
最初にpip install setuptools、次にpip install git+https://github.com/uqfoundation/pathos.git@master。これにより、適切な依存関係が取得されます。新しいリリースはほぼ準備ができています...今ではほとんどすべてのものがpathosWindows上でも実行され、3.x互換性があります。
マイクマッカーンズ2015

1
@リカ:はい。ブロッキング、反復、非同期マップが利用可能です。
Mike McKerns

35

__call__()内部でメソッドを定義してsomeClass()、を呼び出しsomeClass.go()、そのインスタンスをsomeClass()プールに渡すこともできます。このオブジェクトはピクル可能で、(私にとっては)正常に動作します...


3
これは、Alex Martelliによって提案された手法よりもはるかに簡単ですが、マルチプロセッシングプールに送信できるメソッドは、クラスごとに1つだけです。
2011

6
注意すべきもう1つの詳細は、ピクル化されるのはオブジェクト(クラスインスタンス)だけであり、クラス自体ではないことです。したがって、クラス属性をデフォルト値から変更した場合、これらの変更は別のプロセスに反映されません。回避策は、関数に必要なすべてのものがインスタンス属性として格納されていることを確認することです。
2011

2
@dorvakで簡単な例を示していただけません __call__()か?あなたの答えはもっときれいなものかもしれません-私はこのエラーを理解するのに苦労しています。ちなみに、この回答もマルチプロセッシングの機能を明確にするのに役立ちます:[ stackoverflow.com/a/20789937/305883]
user305883

1
この例を挙げていただけますか?
frmsaul 2016年

1
ある新しい答え、このためのサンプルコードと(現在はこの1以下)掲示が。
アーロン

22

ただし、Steven Bethardのソリューションにはいくつかの制限があります。

クラスメソッドを関数として登録すると、メソッドの処理が完了するたびに、クラスのデストラクタが驚くほど呼び出されます。したがって、メソッドのn回を呼び出すクラスのインスタンスが1つある場合、メンバーは2回の実行の間に消え、メッセージmalloc: *** error for object 0x...: pointer being freed was not allocated(例:メンバーファイルを開く)が表示されるか、またはpure virtual method called, terminate called without an active exception(使用したメンバーオブジェクトの存続期間が私が思ったこと)。プールサイズより大きいnを処理するときに、これが発生しました。ここに短い例があります:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

# --------- see Stenven's solution above -------------
from copy_reg import pickle
from types import MethodType

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)


class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multi-processing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self.process_obj, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __del__(self):
        print "... Destructor"

    def process_obj(self, index):
        print "object %d" % index
        return "results"

pickle(MethodType, _pickle_method, _unpickle_method)
Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once)

出力:

Constructor ...
object 0
object 1
object 2
... Destructor
object 3
... Destructor
object 4
... Destructor
object 5
... Destructor
object 6
... Destructor
object 7
... Destructor
... Destructor
... Destructor
['results', 'results', 'results', 'results', 'results', 'results', 'results', 'results']
... Destructor

__call__[なし、...]の結果から読み取るされていないので、この方法では、それほど等価ではありません。

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multiprocessing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __call__(self, i):
        self.process_obj(i)

    def __del__(self):
        print "... Destructor"

    def process_obj(self, i):
        print "obj %d" % i
        return "result"

Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once), 
# **and** results are empty !

したがって、どちらの方法も満足できません...


7
あなたは、取得Noneのあなたの定義があるため、バック__call__欠けているreturn:それはする必要がありますreturn self.process_obj(i)
torek 2012

1
@Eric同じエラーが発生し、この解決策を試しましたが、「cPickle.PicklingError:Ca n't pickle <type 'function'>:attribute lookup builtin .function failed」という新しいエラーが発生し始めました。その背後にあると思われる理由を知っていますか?
ナマン2015年

15

使用できる別のショートカットがありますが、クラスインスタンスの内容によっては非効率になる場合があります。

みんなが言ったように、問題は、multiprocessingコードが開始したサブプロセスに送信するものをコードでピクルする必要があり、ピッカーがインスタンスメソッドを実行しないことです。

ただし、instance-methodを送信する代わりに、実際のクラスインスタンスと、呼び出す関数の名前をgetattr、instance-methodの呼び出しに使用する通常の関数に送信して、Poolサブプロセスでバインドされたメソッドを作成できます。これは、__call__メソッドの定義と似ていますが、複数のメンバー関数を呼び出すことができる点が異なります。

彼の答えから@EricH。のコードを盗み、少し注釈を付けます(私はそれを再入力したため、すべての名前の変更など、何らかの理由で、これはカットアンドペーストより簡単に見えました:-))すべての魔法の説明のために:

import multiprocessing
import os

def call_it(instance, name, args=(), kwargs=None):
    "indirect caller for instance methods and multiprocessing"
    if kwargs is None:
        kwargs = {}
    return getattr(instance, name)(*args, **kwargs)

class Klass(object):
    def __init__(self, nobj, workers=multiprocessing.cpu_count()):
        print "Constructor (in pid=%d)..." % os.getpid()
        self.count = 1
        pool = multiprocessing.Pool(processes = workers)
        async_results = [pool.apply_async(call_it,
            args = (self, 'process_obj', (i,))) for i in range(nobj)]
        pool.close()
        map(multiprocessing.pool.ApplyResult.wait, async_results)
        lst_results = [r.get() for r in async_results]
        print lst_results

    def __del__(self):
        self.count -= 1
        print "... Destructor (in pid=%d) count=%d" % (os.getpid(), self.count)

    def process_obj(self, index):
        print "object %d" % index
        return "results"

Klass(nobj=8, workers=3)

出力は、実際には、コンストラクターが(元のpidで)1回呼び出され、デストラクターが9回呼び出されることを示しています(作成されたコピーごとに1回=必要に応じてプールワーカープロセスごとに2または3回、さらに元のコピーで1回)処理する)。デフォルトのピッカーがインスタンス全体のコピーを作成し、(半)密かに再設定するため、この場合のように、これは多くの場合、問題ありません。

obj = object.__new__(Klass)
obj.__dict__.update({'count':1})

—そのため、3つのワーカープロセスでデストラクタが8回呼び出されても、デストラクタは毎回1から0までカウントダウンします—もちろん、この方法でも問題が発生する可能性があります。必要に応じて、独自に提供できます__setstate__

    def __setstate__(self, adict):
        self.count = adict['count']

この場合の例です。


1
これは、この問題に対して断然最良の答えです。ピクルできない非表示のデフォルト動作に適用するのが最も簡単だからです
Matt Taylor

12

__call__()内部でメソッドを定義してsomeClass()、を呼び出しsomeClass.go()、そのインスタンスをsomeClass()プールに渡すこともできます。このオブジェクトはピクル可能で、(私にとっては)正常に動作します...

class someClass(object):
   def __init__(self):
       pass
   def f(self, x):
       return x*x

   def go(self):
      p = Pool(4)
      sc = p.map(self, range(4))
      print sc

   def __call__(self, x):   
     return self.f(x)

sc = someClass()
sc.go()

3

上記のparisjohnの解決策は私には問題ありません。さらに、コードは見た目もすっきりとしていて理解しやすいものです。私の場合、Poolを使用して呼び出す関数がいくつかあるので、parisjohnのコードを少し下で変更しました。私が作っ呼び出すいくつかの機能を呼び出すことができるように、関数名は引数からの辞書に渡されますgo()

from multiprocessing import Pool
class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def g(self, x):
        return x*x+1    

    def go(self):
        p = Pool(4)
        sc = p.map(self, [{"func": "f", "v": 1}, {"func": "g", "v": 2}])
        print sc

    def __call__(self, x):
        if x["func"]=="f":
            return self.f(x["v"])
        if x["func"]=="g":
            return self.g(x["v"])        

sc = someClass()
sc.go()

1

これに対する簡単な解決策は、の使用に切り替えることmultiprocessing.dummyです。これはマルチプロセッシングインターフェイスのスレッドベースの実装で、Python 2.7ではこの問題は発生しないようです。ここでは多くの経験はありませんが、この簡単なインポートの変更により、クラスメソッドでapply_asyncを呼び出すことができました。

に関するいくつかの優れたリソースmultiprocessing.dummy

https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.dummy

http://chriskiehl.com/article/parallelism-in-one-line/


1

someClass.fがクラスからデータを継承せず、クラスに何もアタッチしていないこの単純なケースでは、可能な解決策はを分離してf、ピクルできるようにすることです。

import multiprocessing


def f(x):
    return x*x


class someClass(object):
    def __init__(self):
        pass

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(f, range(10))

1

別の関数を使用しないのはなぜですか?

def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)

1

同じ問題に遭遇しましたが、これらのオブジェクトをプロセス間で移動するために使用できるJSONエンコーダーがあることがわかりました。

from pyVmomi.VmomiSupport import VmomiJSONEncoder

これを使用してリストを作成します。

jsonSerialized = json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

次に、マップされた関数で、これを使用してオブジェクトを回復します。

pfVmomiObj = json.loads(jsonSerialized)

0

更新:この記事の執筆日現在、namedTuplesは選択可能です(Python 2.7以降)。

ここでの問題は、子プロセスがオブジェクトのクラス(この場合はクラスP)をインポートできないことです。マルチモデルプロジェクトの場合、クラスPは子プロセスが使用される任意の場所にインポートできます。

簡単な回避策は、globals()に影響を与えてインポート可能にすることです

globals()["P"] = P
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.