Paralel olarak gruplanmış pandalar DataFrame'e verimli bir şekilde bir işlev uygulama


89

Çok büyük DataFrame(karma veri türleri) gruplarına sıklıkla bir işlev uygulamam gerekir ve birden çok çekirdekten yararlanmak isterim.

Gruplardan bir yineleyici oluşturabilir ve çoklu işlem modülünü kullanabilirim, ancak verimli değildir çünkü süreçler arasında mesajlaşma için her grup ve işlevin sonuçları seçilmelidir.

Asitlemeden kaçınmanın veya hatta DataFrametamamen kopyalanmasını önlemenin bir yolu var mı ? Görünüşe göre, çoklu işlem modüllerinin paylaşılan bellek işlevleri,numpy dizilerle . Başka seçenek var mı?


Bildiğim kadarıyla, rastgele nesneleri paylaşmanın bir yolu yok. Merak ediyorum, asitleme, çoklu işlemden elde edilen kazançtan çok daha fazla zaman alıyor mu? Belki de göreceli asitleme süresini azaltmak için her işlem için daha büyük iş paketleri oluşturma olanağını aramalısınız. Diğer bir olasılık, grupları oluştururken çoklu işlemeyi kullanmak olabilir.
Sebastian Werk

3
Bunun gibi bir şey yapıyorum ama UWSGI, Flask ve preforking kullanarak: Pandalar veri çerçevesini bir işleme yüklüyorum, x kez çatallıyorum (paylaşılan bir bellek nesnesi haline getiriyorum) ve sonra bu işlemleri sonuçları birleştirdiğim başka bir python işleminden çağırıyorum. atm JSON'u
Carst

Bu arada, hiç HDF5'e yığınlarla baktınız mı? (HDF5 eşzamanlı yazma için kaydedilmez, ancak ayrı dosyalara kaydedebilir ve sonunda öğeleri birleştirebilirsiniz)
Carst

7
bu 0.14 için hedeflenecek, bu soruna bakın: github.com/pydata/pandas/issues/5751
Jeff

4
@Jeff, 0.15 = (
pyCthon

Yanıtlar:


12

Yukarıdaki yorumlardan, bunun pandasbir süredir planlandığı anlaşılıyor (ayrıca ilginç görünen bir rosettaproje var az önce fark ettiğim var).

Bununla birlikte, her paralel işlevsellik dahil edilinceye kadar pandas, pandasdoğrudan cython+ OpenMP kullanarak verimli ve bellek kopyalamayan paralel artırmalar yazmanın çok kolay olduğunu fark ettim. ve C ++ .

İşte kullanımı şuna benzer bir paralel grup toplamı yazmaya ilişkin kısa bir örnek:

import pandas as pd
import para_group_demo

df = pd.DataFrame({'a': [1, 2, 1, 2, 1, 1, 0], 'b': range(7)})
print para_group_demo.sum(df.a, df.b)

ve çıktı:

     sum
key     
0      6
1      11
2      4

Not Şüphesiz, bu basit örneğin işlevselliği eninde sonunda bunun bir parçası olacaktır pandas. Bununla birlikte, bazı şeylerin bir süre C ++ ile paralel hale getirilmesi daha doğal olacaktır ve bunu birleştirmenin ne kadar kolay olduğunun farkında olmak önemlidir pandas.


Bunu yapmak için, kodu takip eden basit bir tek kaynaklı dosya uzantısı yazdım.

Bazı içe aktarmalar ve tür tanımlarıyla başlar

from libc.stdint cimport int64_t, uint64_t
from libcpp.vector cimport vector
from libcpp.unordered_map cimport unordered_map

cimport cython
from cython.operator cimport dereference as deref, preincrement as inc
from cython.parallel import prange

import pandas as pd

ctypedef unordered_map[int64_t, uint64_t] counts_t
ctypedef unordered_map[int64_t, uint64_t].iterator counts_it_t
ctypedef vector[counts_t] counts_vec_t

C ++ unordered_maptürü tek bir iş parçacığı ile toplamak içindir vevector tarafından toplamak içindir ve tüm iş parçacıkları tarafından toplamak içindir.

Şimdi işleve sum. Hızlı erişim için yazılı hafıza görünümleriyle başlar :

def sum(crit, vals):
    cdef int64_t[:] crit_view = crit.values
    cdef int64_t[:] vals_view = vals.values

İşlev, yarı eşit olarak dişlere bölerek (burada 4'e kodlanmıştır) ve her bir iş parçacığının kendi aralığındaki girişleri toplamıyla devam eder:

    cdef uint64_t num_threads = 4
    cdef uint64_t l = len(crit)
    cdef uint64_t s = l / num_threads + 1
    cdef uint64_t i, j, e
    cdef counts_vec_t counts
    counts = counts_vec_t(num_threads)
    counts.resize(num_threads)
    with cython.boundscheck(False):
        for i in prange(num_threads, nogil=True): 
            j = i * s
            e = j + s
            if e > l:
                e = l
            while j < e:
                counts[i][crit_view[j]] += vals_view[j]
                inc(j)

İş parçacıkları tamamlandığında, işlev tüm sonuçları (farklı aralıklardan) tek bir sonuçta birleştirir unordered_map:

    cdef counts_t total
    cdef counts_it_t it, e_it
    for i in range(num_threads):
        it = counts[i].begin()
        e_it = counts[i].end()
        while it != e_it:
            total[deref(it).first] += deref(it).second
            inc(it)        

Geriye kalan tek şey bir oluşturmak DataFrameve sonuçları döndürmektir:

    key, sum_ = [], []
    it = total.begin()
    e_it = total.end()
    while it != e_it:
        key.append(deref(it).first)
        sum_.append(deref(it).second)
        inc(it)

    df = pd.DataFrame({'key': key, 'sum': sum_})
    df.set_index('key', inplace=True)
    return df
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.