所以我有代码从最初拥有超过1400万个文件的目录中获取文件列表。这是一个六核机器,20 GB内存运行Ubuntu 14.04桌面,只需抓取一个文件列表需要几个小时 - 我还没有实际计时。
在过去一周左右的时间里,我运行的代码只是收集这个文件列表,打开每个文件以确定它何时被创建,并根据月份和年份将其移动到目录中创建了。 (这些文件都是scp'd和rsync'd所以操作系统提供的时间戳此时没有意义,因此打开文件。)
当我第一次开始运行此循环时,它在大约90秒内移动了1000个文件。然后在这样的几个小时后,90秒变为2.5分钟,然后是4秒,然后是5秒,然后是9秒,最后是15分钟。所以我关闭它并重新开始。
我注意到今天一旦完成了收集超过9百万个文件的列表,那么移动1000个文件就会立即花费15分钟。我只是再次关闭该过程并重新启动机器,因为移动1000个文件的时间已经攀升到超过90分钟。
我希望找到一些方法来执行while + list.pop()
样式策略,以便随着循环的进展释放内存。然后发现了一些SO帖子,说可以用for i in list: ... list.remove(...)
完成,但这是一个糟糕的主意。
以下是代码:
from basicconfig.startup_config import *
arc_dir = '/var/www/data/visits/'
def step1_move_files_to_archive_dirs(files):
"""
:return:
"""
cntr = 0
for f in files:
cntr += 1
if php_basic_files.file_exists(f) is False:
continue
try:
visit = json.loads(php_basic_files.file_get_contents(f))
except:
continue
fname = php_basic_files.basename(f)
try:
dt = datetime.fromtimestamp(visit['Entrance Time'])
except KeyError:
continue
mYr = dt.strftime("%B_%Y")
# Move the lead to Monthly archive
arc_path = arc_dir + mYr + '//'
if not os.path.exists(arc_path):
os.makedirs(arc_path, 0777)
if not os.path.exists(arc_path):
print "Directory: {} was not created".format(arc_path)
else:
# Move the file to the archive
newFile = arc_path + fname
#print "File moved to {}".format(newFile)
os.rename(f, newFile)
if cntr % 1000 is 0:
print "{} files moved ({})".format(cntr, datetime.fromtimestamp(time.time()).isoformat())
def step2_combine_visits_into_1_file():
"""
:return:
"""
file_dirs = php_basic_files.glob(arc_dir + '*')
for fd in file_dirs:
arc_files = php_basic_files.glob(fd + '*.raw')
arc_fname = arc_dir + php_basic_str.str_replace('/', '', php_basic_str.str_replace(arc_dir, '', fd)) + '.arc'
try:
arc_file_data = php_basic_files.file_get_contents(arc_fname)
except:
arc_file_data = {}
for f in arc_files:
uniqID = moduleName = php_adv_str.fetchBefore('.', php_basic_files.basename(f))
if uniqID not in arc_file_data:
visit = json.loads(php_basic_files.file_get_contents(f))
arc_file_data[uniqID] = visit
php_basic_files.file_put_contents(arc_fname, json.dumps(arc_file_data))
def main():
"""
:return:
"""
files = php_basic_files.glob('/var/www/html/ver1/php/VisitorTracking/data/raw/*')
print "Num of Files: {}".format(len(files))
step1_move_files_to_archive_dirs(files)
step2_combine_visits_into_1_file()
注意:
basicconfig本质上是我对环境的一些常量,以及一些常用的库,比如所有的php_basic_ *库。 (在使用Python之前我使用了PHP多年,因此我构建了一个库来模仿我用来更快地运行Python的更常用函数。)
步骤1 def是程序到目前为止。 step2 def可以并且可能应该并行运行。但是,我认为I / O是瓶颈,并行执行更多功能可能会使所有功能降低更多。 (我一直试图将存档目录rsync到另一台机器进行聚合,从而在没有I / O瓶颈的情况下获得并行速度,但认为rsync也会非常慢。)
文件本身都是3 Kb,所以不是很大。
-----最后的想法-------
就像我说的那样,至少对我来说,从每个文件开头都没有存储任何数据。因此,记忆不应成为问题。但是,我注意到现在只使用了1.2 GB的RAM,之前使用了超过12 GB的RAM。 12个中的很大一部分可以存储1400万个文件名和路径。我刚刚开始处理,所以在接下来的几个小时里,python会收集一个文件列表,而且这个列表还没有在内存中。
所以我想知道是否存在垃圾收集问题或者我遗漏的其他问题。为什么它在循环过程中会变慢?
答案 0 :(得分:0)
<强> step1_move_files_to_archive_dirs
强>:
以下是步骤1可能比预期花费更长时间的一些原因......
步骤1中对任何异常的响应是continue
到下一个文件。如果您有任何损坏的数据文件,它们将永远保留在文件系统中,增加此函数下次(以及下一个,下一个......)的工作量。
您正在读取每个文件并将其从JSON转换为dict
,只是为了提取一个日期。所以所有至少被读取并转换一次。如果您控制这些文件的创建,可能值得将此值存储在文件名中或单独的索引/日志中,因此您不必在以后再次搜索该值。
如果输入目录和输出/存档目录位于不同的文件系统上,os.rename(f, newFile)
不能重命名该文件,但必须复制每个字节从源文件系统到目标文件系统。因此,要么几乎每个文件都被即时重命名,要么每个输入文件被慢慢复制。
PS:奇怪的是,这个函数会仔细检查输入文件是否仍然存在,或os.makedirs
是否有效,但是允许来自os.rename
的任何异常都会导致你在循环中崩溃。< / p>
<强> step2_combine_visits_into_1_file
强>:
所有文件I / O都隐藏在该PHP库中,但它看起来像这个PHP局外人,就像你试图在RAM中存储每个子目录中所有文件的内容一样。然后,您将所有这些内容累积到一些较小数量的存档文件中,同时保留(大部分?)已存在的数据。这不仅可能开始很慢,随着时间的推移它会变慢。
功能代码主要由注释替换:
file_dirs = # arch_dir/* --- Maybe lots, maybe only a few.
for fd in file_dirs:
arc_files = # arch_dir/subdir*.raw or maybe arch_dir/subdir/*.raw.
arc_fname = # subdir.arc
arc_file_data = # Contents of JSON file subdir.arc, as a dict.
for f in arc_files: # The *.raw files.
uniqID = # String based on f's filename.
if uniqID not in arc_file_data:
# Add to arc_file_data the uniqID key, and the
# _ entire contents_ of the .raw file as its value.
php_basic_files.file_put_contents # (...)
# Convert the arc_file_data dict into one _massive_ string,
# and replace the contents of the subdir.arc file.
除非您有一些定期修剪*.arc
文件的维护工作,否则您最终将拥有*.arc
文件中所有1400万个文件(以及任何旧文件)的全部内容。每个.arc
文件都被读入dict
,转换为巨型字符串,成长(可能),然后写回文件系统。这是一个很大的I / O,即使平均.arc
文件不是很大(只有在批次的情况下才会发生)。
为什么要这么做呢?在步骤2开始时,您已经为每个.raw
输入文件获得了一个唯一的ID,并且它已经是文件名中的 ---所以为什么不使用文件系统本身来存储/arch_dir/subdir/unique_id.json
?
如果您确实需要在一些大型档案中存储所有这些数据,那么这不需要那么多工作。 .arc
文件只是.raw
文件的未更改内容,它们之间有一些JSON字典。一个简单的shell脚本可以将它们拼凑在一起,而无需解释JSON本身。
(如果值不仅仅是JSON而是引用 JSON,则必须将.arc
文件的任何读取更改为而不是取消引用这些值但现在我纯粹在猜测,因为我只能看到一些正在发生的事情。)
PS:我错过了什么,或者是arc_files
*.raw
个文件名列表。不应该是raw_files
?
其他评论:
正如其他人所指出的那样,如果你的文件全局函数返回一个包含1400万个文件名的巨型列表,那么作为一个可以yield
一次只能list
个文件名的生成器,它的内存效率将大大提高。
最后,你提到从del my_list[0]
弹出文件名(虽然我在你的代码中没有看到)...插入或删除第一个巨大的时间代价大型列表的元素--- my_list.pop(0)
或my_list.insert(0, something)
或"SELECT * FROM [music_data] INNER JOIN [music_junc] ON [music_data].[music_d_id]=[music_junc].[music_d_id]) WHERE ([profile_id] = 2)"
---因为项目1到n-1都必须将一个索引复制到0.这就变成了O( n )操作到O( n ** 2)...再次,如果你的代码在任何地方。