Performance Haskell和可变结构';演出
我正在研究中给出的答案,并注意到与Python相比,使用一个小的输入确实会导致更快的Haskell运行 但随着数据集规模的扩大,Python占据了领先地位。使用基于hashmap的版本提高了性能,但仍然落后 更糟糕的是,我尝试将Python的字典翻译成哈希表,结果发现性能受到了严重影响。我真的很想了解发生了什么,因为我需要为未来的应用程序提供可变结构 下面是稍加修改的Python代码:Performance Haskell和可变结构';演出,performance,haskell,hashtable,mutable,Performance,Haskell,Hashtable,Mutable,我正在研究中给出的答案,并注意到与Python相比,使用一个小的输入确实会导致更快的Haskell运行 但随着数据集规模的扩大,Python占据了领先地位。使用基于hashmap的版本提高了性能,但仍然落后 更糟糕的是,我尝试将Python的字典翻译成哈希表,结果发现性能受到了严重影响。我真的很想了解发生了什么,因为我需要为未来的应用程序提供可变结构 下面是稍加修改的Python代码: #! /usr/bin/env python2.7 import random import re impor
#! /usr/bin/env python2.7
import random
import re
import cPickle
class Markov:
def __init__(self, filenames):
self.filenames = filenames
self.cache = self.train(self.readfiles())
picklefd = open("dump", "w")
cPickle.dump(self.cache, picklefd)
print "Built a db of length "+str(len(self.cache))
picklefd.close()
def train(self, text):
splitted = text.split(' ')
print "Total of %d splitted words" % (len(splitted))
cache = {}
for i in xrange(len(splitted)-2):
pair = (splitted[i], splitted[i+1])
followup = splitted[i+2]
if pair in cache:
if followup not in cache[pair]:
cache[pair][followup] = 1
else:
cache[pair][followup] += 1
else:
cache[pair] = {followup: 1}
return cache
def readfiles(self):
data = ""
for filename in self.filenames:
fd = open(filename)
data += fd.read()
fd.close()
return data
Markov(["76.txt"])
Haskell,原始响应(train4),其hashmap变体(trainHM2)和哈希表音译(trainHT):
Python(2.7)
Haskell(GHC 7.4.1)-“第4列”
Haskell-“列车HM2”
Haskell—“trainHT”-使用基本变体(我想这与Python对字典的作用很接近?)
两个表都使用线性或布谷鸟
0:06.06
0:05.69
最外面的桌子是布谷鸟,里面是线性的
0:04.17
分析表明有相当多的GC,因此,使用+RTS-A256M
0:02.11
对于输入的数据,我选择了其中一个答案,并将全文复制了12次。它应该达到7兆比特左右
测试是在一个VirtualBox容器中的Ubuntu12.04上运行的,使用一个i5-520M内核。做了不止一次,所有的结果都非常接近
最后一个结果对于这个微基准来说很好,但是考虑到以下情况,代码中还有什么需要改进的地方吗
- Buckoo&Linear可能更适合此数据集,但“通用”Python解决方案在这方面没有太多优化就可以了 <> Valgrind报告了C++和Python版本大约有代码< 60MBS < /COD>。而Haskell RTS报告的任何地方都是从代码< > 125Mbs/COD>(布谷和线性)到<代码> 409Mbs<代码>(基本,更大堆)的同一任务的内存。在生产环境中如此频繁地调整垃圾收集器不是有害的吗?是否可以重构代码以减少内存使用
40MBs
),这会导致GC花费更长的时间,从而导致执行时间变慢(我想是因为丢弃了从列表中懒洋洋读取的值吧?)
是的,哈希表的操作需要花费大量的时间。我将试着模仿修改,看看是否有进一步的改进。这并不是一个真正的答案,但太多了,无法在评论中添加,所以我将把它放在这里,直到更好的东西出现。我看不出你的hashtable代码有任何明显的错误(这是我真正研究的唯一部分),除了一些重构/打高尔夫球 首先,内存使用对我来说并不奇怪。使用
-A256M
,您要求RTS的最小分配区域为256M,这样就可以降低内存使用率。如果在GC之后升级或复制数据,内存使用量将增加。还请注意,与其他语言相比,Haskell数据结构往往有点内存不足,请参见示例。考虑到这两个因素,我对大分配区域的总内存使用率并不感到惊讶
像HashMap或bytestring trie这样的结构可能会占用更少的内存,使用hashtable以外的数据结构也会带来负面影响
说到分配区域,这段代码有点像微基准,因为几乎所有分配的数据(主要是bytestring数据和内部哈希表值)都是长寿命的(它们持续到程序结束)。这将使您的测试程序处于一种非常大的分配区域特别有利的情况下,而如果此数据库构建只是更大程序的一部分,则更大区域的成本可能会占主导地位
至于生产环境的最佳GC设置,在实际完整程序的上下文之外很难判断。我可以说,如果性能真的很重要,那么花一些时间调整GC标志是值得的。如果启用了线程运行时,情况更是如此
除了内存问题,我强烈怀疑hashtables包在这里对您不利。根据配置文件,成本最高的4个功能是
lookup/go
,lookup
,insert
,和delete'.go
。我认为,如果它具有与Data.Map.alter
等价的功能,您的一些操作就可以合并在一起以获得性能增益。如果Python字典没有针对cache[key]+=1这样的情况进行优化,我会非常惊讶。“代码中还有什么需要改进的地方”是一个大问题。你能说得更具体些吗?你说有很多GC,但是不要说太多关于你从分析中学到的东西,或者出现了什么问题。你远远没有给出完整的答案,但是你通过打印长度来强制整个单词列表,并将其保留在内存中以供dict构造。通过不打印长度,我节省了大约100米的基本、更大的堆大小。如果需要,可以在构建字典的同时生成一个长度值。
$ /usr/bin/time -f "%E" ./pytest.py
Total of 1255153 splitted words
Built a db of length 64442
0:02.62
$ ghc -fllvm -O2 -rtsopts -fforce-recomp -funbox-strict-fields hasktest.hs -o hasktest
[1 of 1] Compiling Main ( hasktest.hs, hasktest.o )
Linking hasktest ...
$ /usr/bin/time -f "%E" ./hasktest
1255153
"Built a DB of 64442 words"
0:06.35
$ /usr/bin/time -f "%E" ./hasktest
1255153
"Built a DB of 64442 words"
0:04.23
$ /usr/bin/time -f "%E" ./hasktest
1255153
"Built a DB"
0:10.42
0:06.06
0:05.69
0:04.17
0:02.11