Áp dụng hiệu quả một hàm cho DataFrame gấu trúc được nhóm theo nhóm song song


89

Tôi thường cần áp dụng một hàm cho các nhóm rất lớn DataFrame(gồm các kiểu dữ liệu hỗn hợp) và muốn tận dụng nhiều lõi.

Tôi có thể tạo một trình lặp từ các nhóm và sử dụng mô-đun đa xử lý, nhưng nó không hiệu quả vì mọi nhóm và kết quả của hàm phải được chọn để nhắn tin giữa các quy trình.

Có cách nào để tránh ngâm hoặc thậm chí tránh sao chép DataFramehoàn toàn không? Có vẻ như các chức năng bộ nhớ dùng chung của các mô-đun đa xử lý được giới hạn trong numpycác mảng. Có sự lựa chọn nào khác không?


Theo tôi được biết, không có chuyện chia sẻ các đối tượng tùy tiện. Tôi đang tự hỏi, nếu quá trình ngâm muối mất nhiều thời gian hơn so với thu được thông qua quá trình đa chế biến. Có lẽ bạn nên tìm kiếm khả năng tạo các gói công việc lớn hơn cho mỗi quy trình để giảm thời gian xử lý tương đối. Một khả năng khác là sử dụng đa xử lý khi bạn tạo các nhóm.
Sebastian Werk

3
Tôi làm điều gì đó tương tự nhưng bằng cách sử dụng UWSGI, Flask và preforking: Tôi tải khung dữ liệu gấu trúc vào một quy trình, phân nhánh nó x lần (biến nó thành một đối tượng bộ nhớ được chia sẻ) và sau đó gọi các quy trình đó từ một quy trình python khác, nơi tôi nối kết quả. atm Tôi sử dụng JSON như một quy trình giao tiếp, nhưng điều này sắp diễn ra (vẫn còn mang tính thử nghiệm cao): pandas.pydata.org/pandas-docs/dev/io.html#msgpack-experimental
Carst

Nhân tiện, bạn đã bao giờ nhìn vào HDF5 với phân khúc chưa? (HDF5 không phải là tiết kiệm cho các văn bản đồng thời, nhưng bạn cũng có thể lưu vào tập tin riêng biệt và trong những thứ cuối concatenate)
Carst

7
điều này sẽ được nhắm mục tiêu cho 0,14, hãy xem vấn đề này: github.com/pydata/pandas/issues/5751
Jeff

4
@Jeff đã được đẩy lên 0,15 = (
pyCthon

Câu trả lời:


12

Từ những nhận xét ở trên, có vẻ như điều này đã được lên kế hoạch cho pandasmột thời gian (cũng có một rosettadự án thú vị mà tôi vừa mới nhận thấy).

Tuy nhiên, cho đến khi mọi chức năng song song được tích hợp vào pandas, tôi nhận thấy rằng rất dễ dàng để viết các phép tăng cường song song hiệu quả & không sao chép bộ nhớ để pandastrực tiếp sử dụng cython+ OpenMP và C ++.

Dưới đây là một ví dụ ngắn về cách viết một tổng của nhóm song song, có cách sử dụng như sau:

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)

và đầu ra là:

     sum
key     
0      6
1      11
2      4

Lưu ý Không nghi ngờ gì nữa, chức năng của ví dụ đơn giản này cuối cùng sẽ là một phần của pandas. Tuy nhiên, một số thứ sẽ tự nhiên hơn khi song song hóa trong C ++ trong một thời gian, và điều quan trọng là phải biết việc kết hợp điều này vào dễ dàng như thế nào pandas.


Để thực hiện việc này, tôi đã viết một phần mở rộng tệp nguồn đơn đơn giản có mã sau.

Nó bắt đầu với một số nhập khẩu và định nghĩa loại

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

Loại C ++ unordered_maplà để tính tổng theo một luồng duy nhất và vectorlà để tính tổng theo tất cả các luồng.

Bây giờ đến chức năng sum. Nó bắt đầu với các chế độ xem bộ nhớ đã nhập để truy cập nhanh:

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

Hàm tiếp tục bằng cách chia nửa đều cho các luồng (ở đây được mã hóa cứng thành 4) và mỗi luồng tính tổng các mục trong phạm vi của nó:

    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)

Khi các chủ đề đã hoàn thành, hàm hợp nhất tất cả các kết quả (từ các phạm vi khác nhau) thành một 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)        

Tất cả những gì còn lại là tạo một DataFramevà trả về kết quả:

    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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.