加快读取多个 pickle 文件
Speed up reading multiple pickle files
我有很多 pickle 文件。目前我循环阅读它们,但这需要很多时间。我想加快速度,但不知道该怎么做。
多处理不起作用,因为为了将数据从子进程传输到主进程,数据需要序列化(pickled)和反序列化。
由于 GIL,使用线程也无济于事。
我认为解决方案是一些用 C 编写的库,它获取要读取的文件列表,然后运行多个线程(没有 GIL)。身边有这样的吗?
更新
回答您的问题:
- 文件是以机器学习为目的的数据处理的部分产品
- 有
pandas.Series
个对象,但事先不知道数据类型
- 我想要很多文件,因为我们想轻松地选择任何子集
- 我想要许多小文件而不是一个大文件,因为一个大文件的反序列化需要更多内存(在某个时间点我们已经序列化字符串和反序列化对象)
- 文件的大小可能相差很大
- 我使用 python 3.7 所以我相信它实际上是 cPickle
- 使用 pickle 非常灵活,因为我不必担心底层类型 - 我可以保存任何东西
我认为您应该尝试使用与 open()
类似但速度更快的 mmap(内存映射文件)。
注意:如果您的每个文件都很大,则使用 mmap,否则如果文件很小,请使用常规方法。
我写了一个示例,你可以试试。
import mmap
from time import perf_counter as pf
def load_files(filelist):
start = pf() # for rough time calculations
for filename in filelist:
with open(filename, mode="r", encoding="utf8") as file_obj:
with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_file_obj:
data = pickle.load(mmap_file_obj)
print(data)
print(f'Operation took {pf()-start} sec(s)')
这里mmap.ACCESS_READ
是二进制打开文件的方式。 open
返回的 file_obj
仅用于获取 file descriptor
用于通过 mmap
打开流到文件作为内存映射文件。
正如您在 python open
returns 的文档中看到的,简称 file descriptor
或 fd
。所以我们不必对 file_obj
操作做任何明智的操作。我们只需要它的 fileno()
方法来获取它的文件描述符。此外,我们不会在 mmap_file_obj
之前关闭 file_obj
。请好好看看。我们首先关闭 mmap
块。
正如您在评论中所说。
open (file, flags[, mode])
Open the file file and set various flags according to flags and possibly its mode according to mode.
The default mode is 0777 (octal), and the current umask value is first masked out.
Return the file descriptor for the newly opened file.
试一试,看看它对您的运营有多大影响
你可以阅读更多关于 mmap here. And about file descriptor here
我同意评论中指出的内容,即由于 python 本身的限制(主要是 GIL 锁,正如您所指出的)并且可能根本没有更快地加载信息超越你现在所做的。或者,如果有办法,它可能既需要很高的技术含量,但最终只会适度提高速度。
您可以尝试多处理:
import os,pickle
pickle_list=os.listdir("pickles")
output_dict=dict.fromkeys(pickle_list, '')
def pickle_process_func(picklename):
with open("pickles/"+picklename, 'rb') as file:
dapickle=pickle.load(file)
#if you need previus files output wait for it
while(!output_dict[pickle_list[pickle_list.index(picklename)-1]]):
continue
#thandosomesh
print("loaded")
output_dict[picklename]=custom_func_i_dunno(dapickle)
from multiprocessing import Pool
with Pool(processes=10) as pool:
pool.map(pickle_process_func, pickle_list)
I think that the solution would be some library written in C that
takes a list of files to read and then runs multiple threads (without
GIL). Is there something like this around?
简而言之:没有。 pickle
显然对足够多的人来说已经足够好了,因此没有与 pickle 协议完全兼容的主要替代实现。截至 python 3 的某个时间,cPickle
已与 pickle
合并,并且无论如何都不会释放 GIL,这就是线程无法帮助您的原因(在 [ 中搜索 Py_BEGIN_ALLOW_THREADS
=20=] 你什么也找不到)。
如果您的数据可以重新构建为更简单的数据格式(如 csv)或二进制格式(如 numpy
的 npy),那么读取数据时的开销会更少 cpu。 Pickle 的构建首先是为了灵活性,而不是速度或紧凑性。速度越慢越复杂的规则的一个可能例外是使用 h5py
的 HDF5 格式,它可能相当复杂,我已经习惯了最大化 sata ssd 的带宽。
最后你提到你有很多 pickle 文件,这本身可能会造成不小的开销。每次打开新文件时,操作系统都会产生一些开销。您可以方便地通过简单地将 pickle 文件附加在一起来组合它们。然后你可以调用 Unpickler.load()
直到你到达文件的末尾。这是使用 shutil
将两个 pickle 文件组合在一起的快速示例
import pickle, shutil, os
#some dummy data
d1 = {'a': 1, 'b': 2, 1: 'a', 2: 'b'}
d2 = {'c': 3, 'd': 4, 3: 'c', 4: 'd'}
#create two pickles
with open('test1.pickle', 'wb') as f:
pickle.Pickler(f).dump(d1)
with open('test2.pickle', 'wb') as f:
pickle.Pickler(f).dump(d2)
#combine list of pickle files
with open('test3.pickle', 'wb') as dst:
for pickle_file in ['test1.pickle', 'test2.pickle']:
with open(pickle_file, 'rb') as src:
shutil.copyfileobj(src, dst)
#unpack the data
with open('test3.pickle', 'rb') as f:
p = pickle.Unpickler(f)
while True:
try:
print(p.load())
except EOFError:
break
#cleanup
os.remove('test1.pickle')
os.remove('test2.pickle')
os.remove('test3.pickle')
考虑通过 h5py
instead of pickle
. The performance is generally much better than pickle
with numerical data in Pandas
and numpy
data structures and it supports most common data types 和压缩使用 HDF5。
我有很多 pickle 文件。目前我循环阅读它们,但这需要很多时间。我想加快速度,但不知道该怎么做。
多处理不起作用,因为为了将数据从子进程传输到主进程,数据需要序列化(pickled)和反序列化。
由于 GIL,使用线程也无济于事。
我认为解决方案是一些用 C 编写的库,它获取要读取的文件列表,然后运行多个线程(没有 GIL)。身边有这样的吗?
更新 回答您的问题:
- 文件是以机器学习为目的的数据处理的部分产品
- 有
pandas.Series
个对象,但事先不知道数据类型 - 我想要很多文件,因为我们想轻松地选择任何子集
- 我想要许多小文件而不是一个大文件,因为一个大文件的反序列化需要更多内存(在某个时间点我们已经序列化字符串和反序列化对象)
- 文件的大小可能相差很大
- 我使用 python 3.7 所以我相信它实际上是 cPickle
- 使用 pickle 非常灵活,因为我不必担心底层类型 - 我可以保存任何东西
我认为您应该尝试使用与 open()
类似但速度更快的 mmap(内存映射文件)。
注意:如果您的每个文件都很大,则使用 mmap,否则如果文件很小,请使用常规方法。
我写了一个示例,你可以试试。
import mmap
from time import perf_counter as pf
def load_files(filelist):
start = pf() # for rough time calculations
for filename in filelist:
with open(filename, mode="r", encoding="utf8") as file_obj:
with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_file_obj:
data = pickle.load(mmap_file_obj)
print(data)
print(f'Operation took {pf()-start} sec(s)')
这里mmap.ACCESS_READ
是二进制打开文件的方式。 open
返回的 file_obj
仅用于获取 file descriptor
用于通过 mmap
打开流到文件作为内存映射文件。
正如您在 python open
returns 的文档中看到的,简称 file descriptor
或 fd
。所以我们不必对 file_obj
操作做任何明智的操作。我们只需要它的 fileno()
方法来获取它的文件描述符。此外,我们不会在 mmap_file_obj
之前关闭 file_obj
。请好好看看。我们首先关闭 mmap
块。
正如您在评论中所说。
open (file, flags[, mode])
Open the file file and set various flags according to flags and possibly its mode according to mode.
The default mode is 0777 (octal), and the current umask value is first masked out.
Return the file descriptor for the newly opened file.
试一试,看看它对您的运营有多大影响 你可以阅读更多关于 mmap here. And about file descriptor here
我同意评论中指出的内容,即由于 python 本身的限制(主要是 GIL 锁,正如您所指出的)并且可能根本没有更快地加载信息超越你现在所做的。或者,如果有办法,它可能既需要很高的技术含量,但最终只会适度提高速度。
您可以尝试多处理:
import os,pickle
pickle_list=os.listdir("pickles")
output_dict=dict.fromkeys(pickle_list, '')
def pickle_process_func(picklename):
with open("pickles/"+picklename, 'rb') as file:
dapickle=pickle.load(file)
#if you need previus files output wait for it
while(!output_dict[pickle_list[pickle_list.index(picklename)-1]]):
continue
#thandosomesh
print("loaded")
output_dict[picklename]=custom_func_i_dunno(dapickle)
from multiprocessing import Pool
with Pool(processes=10) as pool:
pool.map(pickle_process_func, pickle_list)
I think that the solution would be some library written in C that takes a list of files to read and then runs multiple threads (without GIL). Is there something like this around?
简而言之:没有。 pickle
显然对足够多的人来说已经足够好了,因此没有与 pickle 协议完全兼容的主要替代实现。截至 python 3 的某个时间,cPickle
已与 pickle
合并,并且无论如何都不会释放 GIL,这就是线程无法帮助您的原因(在 [ 中搜索 Py_BEGIN_ALLOW_THREADS
=20=] 你什么也找不到)。
如果您的数据可以重新构建为更简单的数据格式(如 csv)或二进制格式(如 numpy
的 npy),那么读取数据时的开销会更少 cpu。 Pickle 的构建首先是为了灵活性,而不是速度或紧凑性。速度越慢越复杂的规则的一个可能例外是使用 h5py
的 HDF5 格式,它可能相当复杂,我已经习惯了最大化 sata ssd 的带宽。
最后你提到你有很多 pickle 文件,这本身可能会造成不小的开销。每次打开新文件时,操作系统都会产生一些开销。您可以方便地通过简单地将 pickle 文件附加在一起来组合它们。然后你可以调用 Unpickler.load()
直到你到达文件的末尾。这是使用 shutil
import pickle, shutil, os
#some dummy data
d1 = {'a': 1, 'b': 2, 1: 'a', 2: 'b'}
d2 = {'c': 3, 'd': 4, 3: 'c', 4: 'd'}
#create two pickles
with open('test1.pickle', 'wb') as f:
pickle.Pickler(f).dump(d1)
with open('test2.pickle', 'wb') as f:
pickle.Pickler(f).dump(d2)
#combine list of pickle files
with open('test3.pickle', 'wb') as dst:
for pickle_file in ['test1.pickle', 'test2.pickle']:
with open(pickle_file, 'rb') as src:
shutil.copyfileobj(src, dst)
#unpack the data
with open('test3.pickle', 'rb') as f:
p = pickle.Unpickler(f)
while True:
try:
print(p.load())
except EOFError:
break
#cleanup
os.remove('test1.pickle')
os.remove('test2.pickle')
os.remove('test3.pickle')
考虑通过 h5py
instead of pickle
. The performance is generally much better than pickle
with numerical data in Pandas
and numpy
data structures and it supports most common data types 和压缩使用 HDF5。