所以我尝试从日志文件中导入大约 8000 万的页面浏览量。我试图将它们作为 session 放入数据库中,即页面 View 组之间间隔 20 分钟。
所以最终,在我的用户数据库中,我希望每个用户都有一个像这样的字典对象列表:
{
'id': 'user1'
'sessions':[
{
"start" : ISODate("2011-04-03T23:21:59.639Z"),
"end" : ISODate("2011-04-03T23:50:05.518Z"),
"page_loads" : 136
},
{
"start" : ISODate("another date"),
"end" : ISODate("later date"),
"page_loads" : 20
},
]
}
应该相当简单。所以我写了这个脚本:
howManyLinesTotal = 9999999 #i've done: wc -l in bash before to find the file size
blank_dict = {'page_loads':0, 'start':0, 'end':0}
latest_sessions = defaultdict(lambda: blank_dict)
for line in f: #opens a gigantic gzip file called "f"
line = line.split('\t') #each entry is tab-delimited with: user \t datetime \t page_on_my_site
user = line[1] #grab the data from this line in the file
timestamp = datetime.utcfromtimestamp(float(line[2]))
latest_sessions[user]['page_loads'] += 1 #add one to this user's current session
if latest_sessions[user]['start'] == 0: #put in the start time if there isn't one
latest_sessions[user]['start'] = timestamp
if latest_sessions[user]['end'] == 0: #put in the end time if there isn't one
latest_sessions[user]['end'] = timestamp
else: #otherwise calculate if the new end time is 20 mins later
diff = (timestamp - latest_sessions[user]['end']).seconds
if diff > 1200: #if so, save the session to the database
db.update({'id':user}, {'$push':{'sessions':latest_sessions[user]}})
latest_sessions[user] = blank_dict
else:
latest_sessions[user]['end'] = timestamp #otherwise just replace this endtime
count += 1
if count % 100000 == 0: #output some nice stats every 100,000 lines
print str(count) + '/' + str(howManyLinesTotal)
#now put the remaining last sessions in
for user in latest_sessions:
db.update({'id':user}, {'$push':{'sessions':latest_sessions[user]}})
对于一个 8000 万页 View 文件,我每行大约需要 0.002 秒 = 44 小时。
配备 2TB 7200rpm 希捷硬盘、32GB RAM 和 3.4Ghz 双核 i3 处理器。
这次听起来合理还是我犯了一些可怕的错误?
编辑:我们正在查看大约 90,000 多个用户,即 defaultdict 中的键
EDIT2:这是一个小得多的 106mb 文件的 cProfile 输出。我出于测试目的注释掉了实际的 mongoDB 保存:http://pastebin.com/4XGtvYWD
EDIT3:这是 cProfile 的条形图分析:http://i.imgur.com/K6pu6xx.png
请您参考如下方法:
我不能告诉你瓶颈在哪里,但我可以告诉你如何找到它。 Python 有 built in profiling tools ,它会告诉您代码的每个部分花费了多少时间。将此工具用于脚本,就像运行一样简单:
python -m cProfile my_db_import_script.py
my_db_import_script.py 是您实际脚本的名称。该命令的作用是运行附加了分析器的脚本。脚本执行完成后,会打印出每个函数被调用了多少次,总共花费了多少时间,累积了多少时间,以及其他一些统计信息。
要将其与您的脚本一起使用,您需要处理可以在合理时间内完成的数据子集。从那里您将能够分析您发现的任何瓶颈。
优化代码的关键是永远不要假设您知道问题出在哪里。先测量并经常测量。
编辑:
在浏览了您的个人资料结果后,这些是我印象深刻的几行:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 gzip.py:149(_init_read)
1 0.000 0.000 0.000 0.000 gzip.py:153(_read_gzip_header)
2709407 5.543 0.000 8.898 0.000 gzip.py:200(read)
2 0.000 0.000 0.000 0.000 gzip.py:23(read32)
2242878 3.267 0.000 3.727 0.000 gzip.py:232(_unread)
107984 0.266 0.000 3.310 0.000 gzip.py:237(_read)
1 0.000 0.000 0.000 0.000 gzip.py:26(open)
107979 0.322 0.000 1.258 0.000 gzip.py:287(_add_read_data)
1 0.000 0.000 0.000 0.000 gzip.py:293(_read_eof)
1 0.000 0.000 0.000 0.000 gzip.py:308(close)
1 0.000 0.000 0.000 0.000 gzip.py:35(GzipFile)
2242878 8.029 0.000 23.517 0.000 gzip.py:385(readline)
1 0.000 0.000 0.000 0.000 gzip.py:4(<module>)
1 0.000 0.000 0.000 0.000 gzip.py:434(__iter__)
2242878 1.561 0.000 25.078 0.000 gzip.py:437(next)
1 0.000 0.000 0.000 0.000 gzip.py:44(__init__)
2242878 2.889 0.000 2.889 0.000 {built-in method utcfromtimestamp}
107979 1.627 0.000 1.627 0.000 {built-in method decompress}
2709408 1.451 0.000 1.451 0.000 {method 'find' of 'str' objects}
2242880 1.849 0.000 1.849 0.000 {method 'split' of 'str' objects}
您会注意到我已经突出显示了所有 gzip 代码。我对 gzip 模块不是很熟悉,所以我一直在浏览 source code .看起来这个模块正在做的是向 gzip 数据公开一个像接口(interface)这样的普通文件。有几种方法可以加快速度。
如果可行,您可以事先解压缩您的文件。这将消除 gzip 的一些开销。
您可以开始优化文件的读取方式。 Here is a link举个例子,根据您一次读入的文件量不同,情况会有所不同。这个stackoverflow里面也有一些不错的建议问题。
我还强调了一个事实,即转换时间戳会占用大量时间,字符串操作函数也是如此。
归根结底,进行这种规模的任何优化的最佳方法是运行基准测试、进行更改,然后重新运行。希望这对您有所启发!
