结论: 似乎HDF5是我的目的。基本上“ HDF5是用于存储和管理数据的数据模型,库和文件格式。”,旨在处理大量数据。它有一个名为python-tables的Python模块。 (链接在下面的答案中)
HDF5在节省数吨和大量数据方面完成了1000%的工作。从2亿行读取/修改数据是一件痛苦的事情,因此这是下一个需要解决的问题。
我正在构建具有大量子目录和文件的目录树。大约有1000万个文件分布在十万个目录中。每个文件都在32个子目录下。
我有一个python脚本来构建这个文件系统并读取&写那些文件。问题是当我达到一百多万个文件时,读写方法变得非常慢。
这是我的函数,它读取文件的内容(文件包含一个整数字符串),向其添加一定数量,然后将其写回原始文件。
def addInFile(path, scoreToAdd):
num = scoreToAdd
try:
shutil.copyfile(path, '/tmp/tmp.txt')
fp = open('/tmp/tmp.txt', 'r')
num += int(fp.readlines()[0])
fp.close()
except:
pass
fp = open('/tmp/tmp.txt', 'w')
fp.write(str(num))
fp.close()
shutil.copyfile('/tmp/tmp.txt', path)
我认为减速的原因是因为有大量的文件。执行此功能1000次,时间不到一秒......但现在达到1分钟。
你怎么建议我解决这个问题?我是否更改了目录树结构?
我需要的是快速访问这个庞大的文件池中的每个文件*
答案 0 :(得分:6)
两个建议:
首先,涉及子目录32深度嵌套的结构本质上存在缺陷。假设你真的有“大约1000万个文件”,那么一个级别的子目录应该是足够的(假设你使用的是现代文件系统)。
第二:您说您有“大约1000万个文件”,并且每个文件“包含一个整数字符串”。假设它们是32位整数并且您直接存储它们而不是字符串,这相当于总数据集大小为40MiB(10M文件*每个文件4个字节)。假设每个文件名长度为32个字节,则为此数据添加另外320MiB的“密钥”。
因此,您将能够轻松地将整个数据集融入内存。我建议这样做,并对主存储器中保存的数据进行操作。除非有任何理由需要精心设计的目录结构,否则我建议将数据存储在一个文件中。
答案 1 :(得分:6)
我知道这不是您问题的直接答案,但它可以直接解决您的问题。
您需要使用HDF5等内容进行研究。它仅适用于具有数百万个单独数据点的分层数据类型。
你真的很幸运,因为HDF5有很棒的Python绑定叫pytables。 我以非常相似的方式使用它并取得了巨大的成功。
答案 2 :(得分:5)
我建议你重新考虑一下你的方法,使用大量极小的文件肯定会给你带来严重的性能问题。根据程序的目的,某种数据库可能更有效率。
如果您正在进行大量I / O操作,您还可以在问题上投入更多硬件并使用SSD或将所有数据保存在RAM中(显式或通过缓存)。仅使用硬盘驱动器就无法在这种情况下获得良好的性能。
我从未使用它,但是Redis是一个持久的键值存储,应该非常快。如果你的数据符合这个模型,我肯定会尝试这个或类似的东西。你会在这个article中找到一些性能数据,它们可以让你知道你可以达到的速度。
答案 3 :(得分:2)
看起来你正在解决这两个问题,
我建议您重新访问正在使用的结构,并使用较小的文件。保持minf(根据经验)比I / O操作小于128K的运行时成本或多或少等于1byte的I / O!
答案 4 :(得分:2)
解决所有这些子目录需要时间。你对文件系统过度征税。
也许不是使用目录树,而是可以将路径信息编码到文件名中,而不是使用如下路径创建文件:
/parent/00/01/02/03/04/05/06/07
/08/09/0A/0B/0C/0D/0E/0F
/10/11/12/13/14/15/16/17
/18/19/1A/1B/1C/1D/1E/1F.txt
...您可以使用如下路径创建文件:
/parent/00_01_02_03_04_05_06_07_
08_09_0A_0B_0C_0D_0E_0F_
10_11_12_13_14_15_16_17_
18_19_1A_1B_1C_1D_1E_1F.txt
...当然,你仍然会有问题,因为现在你的所有1000万个文件都在一个目录中,而根据我的经验(NTFS),一个目录中有超过几千个文件它仍然对文件系统征税过高。
你可以想出一种混合方法:
/parent/00_01_02_03/04_05_06_07
/08_09_0A_0B/0C_0D_0E_0F
/10_11_12_13/14_15_16_17
/18_19_1A_1B/1C_1D_1E_1F.txt
但如果您详尽地创建所有这些目录,那仍然会给您带来麻烦。即使大多数这些目录都是“空的”(因为它们不包含任何 文件 ),操作系统仍然必须为每个目录创建一个INODE记录,这需要在磁盘上占用空间。
相反,只有在有文件放入目录时才应创建目录。此外,如果删除任何给定目录中的所有文件,则删除空目录。
您应该创建多少级别的目录层次结构?在我的小例子中,我将您的32级层次结构转换为8级层次结构,但在进行一些测试之后,您可能会决定稍微不同的映射。这实际上取决于您的数据,以及这些路径在组合解空间中的均匀分布。您需要使用两个约束来优化解决方案:
1)最小化您创建的目录数量,知道每个目录成为底层文件系统中的INODE,并且创建过多的目录将使文件系统崩溃。
2)最小化每个目录中的文件数量,因为每个目录(根据我的经验,超过1000个)拥有太多文件会压倒文件系统。
还需要考虑另外一个因素:磁盘上的存储空间是使用“块”进行寻址和分配的。如果您创建的文件小于最小块大小,则它会占用整个块,从而浪费磁盘空间。在NTFS中,这些块由它们的“簇大小”定义(部分由卷的整体大小决定),通常默认为4kB:
http://support.microsoft.com/kb/140365
因此,如果您创建一个只包含一个字节数据的文件,它仍然会占用4kB的磁盘空间,浪费4095个字节。
在您的示例中,您说您有大约1000万个文件,大约有1GB的数据。如果这是真的,那么每个文件只有大约100个字节长。如果簇大小为4096,则占空间比率约为98%。
如果可能,请尝试合并其中一些文件。我不知道它们包含哪种数据,但如果它是文本格式,您可能会尝试这样做:
[id:01_23_45_67_89_AB_CD_EF]
lorem ipsum dolor sit amet consectetur adipiscing elit
[id:fe_dc_ba_98_76_54_32_10]
ut non lorem quis quam malesuada lacinia
[id:02_46_81_35_79_AC_DF_BE]
nulla semper nunc id ligula eleifend pulvinar
...依此类推。看起来你似乎浪费了所有那些冗长标题的空间,但就磁盘而言,这是一个更节省空间的策略,而不是为所有这些小片段提供单独的文件。这个小例子对三个记录使用了230个字节(包括换行符),所以你可能会尝试在每个文件中放入大约16个记录(记住每个文件略少于4096个字节要好于略多于4096,浪费了一整个额外的磁盘块。)
无论如何,祝你好运!
答案 5 :(得分:1)
你正在复制一个文件,打开它来阅读,关闭它,然后重新打开它进行写入,然后重新复制它。一次性完成它会更快。
编辑:当数字位数小于当前位数时(例如,如果您减去或添加负数),以前的版本有一个错误;此版本修复了它,时间结果几乎不受影响
def addInFile(path, scoreToAdd):
try:
fp = open(path, 'r+')
except IOError as e:
print e
else:
num = str(scoreToAdd + int(fp.read()))
fp.seek(0)
fp.write(num)
fp.truncate(len(num))
finally:
fp.close()
或者,如果你想避免文件丢失并写入缓存,你应该一次性进行复制和求和,然后在另一步中进行覆盖跳舞:
def addInFile(path, scoreToAdd):
try:
orig = open(path, 'r')
tmp = open('/home/lieryan/junks/tmp.txt', 'w')
except IOError as e:
print e
else:
num = int(orig.read())
tmp.write(str(scoreToAdd + num))
finally:
orig.close()
tmp.close()
try:
# make sure /tmp/ and path is in the same partition
# otherwise the fast shutil.move become a slow shutil.copy
shutil.move(path, '/home/lieryan/junks/backup.txt')
shutil.move('/home/lieryan/junks/tmp.txt', path)
os.remove('/home/lieryan/junks/backup.txt')
except (IOError, shutil.Error) as e:
print e
另外,不要使用裸露的例外。
或者,如何将最低叶片中的所有256个文件分组为一个更大的文件?然后,您可以在一个缓存中一次读取多个数字。如果您使用了固定宽度的文件,那么您可以快速使用seek()来获取O(1)中文件中的任何条目。
一些时间,在同一个文件上写1000次:
(所有函数在其错误处理路径上未经测试)
答案 6 :(得分:0)
如果您使用Linux并获得大容量内存(64GB +),请尝试tmpfs
,它的确能像挂载磁盘一样工作,您无需更改代码或购买其他SSD。