我经常需要将函数应用于非常大的组DataFrame
(混合数据类型),并想利用多个内核。
我可以从组中创建一个迭代器并使用多处理模块,但是这样做效率不高,因为每个组和函数的结果都必须经过腌制才能在进程之间进行消息传递。
有什么方法可以避免酸洗,甚至避免DataFrame
完全复制?看起来多处理模块的共享内存功能仅限于numpy
数组。还有其他选择吗?
我经常需要将函数应用于非常大的组DataFrame
(混合数据类型),并想利用多个内核。
我可以从组中创建一个迭代器并使用多处理模块,但是这样做效率不高,因为每个组和函数的结果都必须经过腌制才能在进程之间进行消息传递。
有什么方法可以避免酸洗,甚至避免DataFrame
完全复制?看起来多处理模块的共享内存功能仅限于numpy
数组。还有其他选择吗?
Answers:
从上面的评论来看,这似乎计划了pandas
一段时间(还有一个我刚刚注意到的有趣的rosetta
项目)。
但是,直到将所有并行功能都集成到中pandas
,我才注意到,pandas
直接使用cython
+ OpenMP和C ++ 编写高效且非内存复制的并行增强非常容易。
这是编写并行groupby-sum的简短示例,其用法如下所示:
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)
输出为:
sum
key
0 6
1 11
2 4
注意毫无疑问,此简单示例的功能最终将成为的一部分pandas
。但是,在C ++中使用某些东西在一段时间内会变得更加自然,并且重要的是要意识到将其组合到中是多么容易pandas
。
为此,我编写了一个简单的单一源文件扩展名,其代码如下。
它从一些导入和类型定义开始
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_map
类型用于单个线程求和,而C vector
用于所有线程求和。
现在要功能了sum
。它从键入的内存视图开始以快速访问:
def sum(crit, vals):
cdef int64_t[:] crit_view = crit.values
cdef int64_t[:] vals_view = vals.values
该函数通过将半等值除以线程(在此硬编码为4),并使每个线程将其范围内的条目相加来继续:
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)
线程完成后,该函数将所有结果(来自不同范围)合并为一个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)
剩下的就是创建一个DataFrame
并返回结果:
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