나는 Optimizing Haskell code에 주어진 응답을 연구 중이었고 작은 입력을 사용하면 실제로 파이썬에 비해 더 빠른 하스켈이 실행된다는 것을 알게되었습니다.하스켈 및 변경 가능한 구조의 성능
그러나 데이터 세트의 크기가 커지면서 파이썬이 선두를 차지했습니다. 해시 맵 기반 버전을 사용하면 성능이 향상되었지만 여전히 뒤떨어져 있습니다.
더 나쁜 것은, 파이썬의 사전을 해시 테이블로 음역으로 변환 해 보았는데, 성능이 크게 떨어지는 것을 관찰했습니다. 나는 미래의 어플리케이션을 위해 변경 가능한 구조가 필요할 것이므로 실제로 진행되는 것을 이해하고 싶습니다.
#! /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"])
하스켈 원래 응답 (train4)는 HashMap 변체 (trainHM2)과 해시 테이블 음역 (trainHT)와 :
여기에 약간 수정 파이썬 코드이다
{-# LANGUAGE BangPatterns,DeriveGeneriC#-}
import GHC.Generics (Generic)
import Data.List (foldl')
import Data.Hashable
import qualified Data.Map as M
import qualified Data.HashMap.Strict as HM
import qualified Data.ByteString.Char8 as B
import qualified Data.HashTable.IO as HT
--Using this instead of tuples yielded a 5~10% speedup
data StringTuple = STP !B.ByteString !B.ByteString deriving(Ord,Eq,Generic)
instance Hashable StringTuple
type Database3 = M.Map StringTuple (M.Map B.ByteString Int)
type DatabaseHM = HM.HashMap StringTuple (HM.HashMap B.ByteString Int)
type DatabaseHT = HT.BasicHashTable StringTuple DatabaseInnerHT
type DatabaseInnerHT = (HT.BasicHashTable B.ByteString Int)
train4 :: [B.ByteString] -> Database3
train4 words = foldl' update M.empty (zip3 words (drop 1 words) (drop 2 words))
where update m (x,y,z) = M.insertWith' (inc z) (STP x y) (M.singleton z 1) m
inc k _ = M.insertWith' (+) k 1
trainHM2 :: [B.ByteString] -> DatabaseHM
trainHM2 words = trainHM2G words HM.empty
where
trainHM2G (x:y:[]) !hm = hm
trainHM2G (x:y:z:rem) !hm = trainHM2G (y:z:rem) (HM.insertWith (inc z) (STP x y) (HM.singleton z 1) hm)
where inc k _ = HM.insertWith (+) k 1
trainHT :: [B.ByteString] -> IO (DatabaseHT)
trainHT words = do
hm <- HT.new
trainHT' words hm
where
trainHT' (x:y:[]) !hm = return hm
trainHT' (x:y:z:rem) !hm = do
let pair = STP x y
inCache <- HT.lookup hm pair
case inCache of
Nothing -> do
htN <- HT.new :: IO (DatabaseInnerHT)
HT.insert htN z $! 1
HT.insert hm pair $! htN
Just ht -> do
cvM <- HT.lookup ht z
case cvM of
Nothing -> HT.insert ht z 1
Just cv -> HT.insert ht z $! (cv+1)
trainHT' (y:z:rem) hm
main = do contents <- B.readFile "76.txt"
let bcont = B.split ' ' $ contents
print $ length bcont
let db = train4 $ bcont
print $ "Built a DB of " ++ show (M.size db) ++ " words"
--let db = trainHM2 $ bcont
--print $ "Built a DB of " ++ show (HM.size db) ++ " words"
--db <- trainHT $ (bcont)
--print $ "Built a DB"
임시 변통적 인 C++ 11 음역 (컴파일이 필요합니다.) (++
$ g++ -O3 -fpermissive -std=c++0x cpptest.cpp -o cpptest
$ /usr/bin/time -f "%E" ./cpptest
1255153
Hashtable count 64442
0:01.02
파이썬 (2.7)
$ /usr/bin/time -f "%E" ./pytest.py
Total of 1255153 splitted words
Built a db of length 64442
0:02.62
하스켈 (GHC 7.4 GCC 4.6.3을
C :
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <tuple>
/*
Hash stuff here
Taken from https://stackoverflow.com/a/7111460/314327
*/
size_t hash_combiner(size_t left, size_t right) //replacable
{ return left^right;}
template<int index, class...types>
struct hash_impl {
size_t operator()(size_t a, const std::tuple<types...>& t) const {
typedef typename std::tuple_element<index, std::tuple<types...>>::type nexttype;
hash_impl<index-1, types...> next;
size_t b = std::hash<nexttype>()(std::get<index>(t));
return next(hash_combiner(a, b), t);
}
};
template<class...types>
struct hash_impl<0, types...> {
size_t operator()(size_t a, const std::tuple<types...>& t) const {
typedef typename std::tuple_element<0, std::tuple<types...>>::type nexttype;
size_t b = std::hash<nexttype>()(std::get<0>(t));
return hash_combiner(a, b);
}
};
namespace std {
template<class...types>
struct hash<std::tuple<types...>> {
size_t operator()(const std::tuple<types...>& t) {
const size_t begin = std::tuple_size<std::tuple<types...>>::value-1;
return hash_impl<begin, types...>()(1, t); //1 should be some largervalue
}
};
}
/*
Hash stuff end
*/
using namespace std;
/*
Split, from https://stackoverflow.com/a/236803/314327
*/
vector<string> &split(const string &s, char delim, vector<string> &elems) {
stringstream ss(s);
string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
vector<string> split(const string &s, char delim) {
vector<string> elems;
split(s, delim, elems);
return elems;
}
/*
Split end
*/
typedef tuple<string,string> STP;
unordered_map< STP,unordered_map< string,int > > train(vector<string> &words)
{
unordered_map< STP,unordered_map< string,int > > cache;
for(int i=0;i<words.size()-2;i++)
{
STP tup = make_tuple(words[i],words[i+1]);
auto it = cache.find(tup);
if(it!=cache.end())
{
auto it2 = it->second.find(words[i+2]);
if(it2!=it->second.end())
{
it2->second += 1;
}
else
it->second[words[i+2]] = 1;
}
else
{
unordered_map< string,int > cacheInner;
cacheInner[words[i+2]] = 1;
cache[tup] = cacheInner;
}
}
return cache;
}
int main()
{
ifstream ifs("76.txt");
stringstream buf;
buf << ifs.rdbuf();
string contents(buf.str());
auto words = split(contents,' ');
cout << words.size();
auto wordCache = train(words);
cout << "\nHashtable count " << wordCache.size();
cout << "\n";
return 0;
}
을 그리고 그 결과는 다음과 같습니다)을 수정합니다 .1) - "train4"
$ 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
하스켈 - "trainHM2"
$ /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
- "trainHT"(파이썬 사전을 위해, 내가 생각하는 일에 가까운?) 내부
에 바깥 쪽 테이블과 선형에 대한 뻐꾸기를 사용하여 두 테이블0:06.06
0:05.69
에 대한
0:04.17
답변 중 하나에 표시하고, 중복으로 프로파일이 + RTS -A256M 입력 데이터에 대한
0:02.11
으로, GC 꽤 많은 있다는 걸 보여, 그래서했다
, 나는 76.txt를 선택 전체 텍스트 12 번. 약 7MB 정도가되어야합니다.
테스트는 i5-520M 코어 하나를 사용하여 VirtualBox 컨테이너에서 Ubuntu 12.04에서 실행되었습니다. 한번에 한 번 이상 실행하면 모든 결과가 거의 비슷합니다.
마지막 결과는이 마이크로 벤치 꽤 괜찮지 만, 는 고려, 코드 개선하기 위해 무엇 있습니다 :
- 뻐꾸기 & 선형이 데이터 세트에 대한 더 적합 할 수는 있지만,
- Valgrind는 C++ & Python 버전은 약
60MBs
을 사용하는 반면, 하스켈 RTS는125MBs
(Cuckoo)의 모든 부분을보고한다고 Valgrind는보고합니다.Linear)에서409MBs
(기본, 더 큰 힙)까지 동일한 작업을 수행 할 수 있습니다. 프로덕션 환경에서 가비지 컬렉터를 조정하지 않는 것이 좋지 않습니까? 메모리 사용량이 적어 지도록 코드를 리팩토링 할 수 있습니까?
업데이트 :
내가 찾고 있어요 "쓰레기를 줄이는 것이"추측 무엇. 하스켈은 C++과 같은 방식으로 작동하지 않는다는 것을 알고 있지만, C++ 예제는 공간 누수없이 메모리의 절반을 소비하므로 명령형 코드에서 생성되는 쓰레기를 줄일 수 있는지 알고 싶습니다. 메모리 사용량과 실행 시간면에서 개선 될 것으로 기대됩니다 (GC가 적어짐에 따라).
업데이트 2 : 테이블의 건설 기간 동안의 길이를 계산
은 (실제로 주위 40MBs
아래로!) 확실히 메모리 풋 프린트를 감소시켰다, 느린 실행의 결과로, GC 시간이 더 걸릴 원인이되는 (목록에서 느슨하게 읽은 값을 버리는 것으로 인해 나는 추정 했습니까?).
예, 해시 테이블의 작업에는 상당한 시간이 걸립니다. 나는 그것이 더 이상 향상되는지보기 위해 변경을 모방하려고 노력할 것이다.
"코드가 개선되어야 할 부분이 있습니까?"라는 것이 큰 질문입니다. 더 자세하게 얘기해 주 시겠어요? GC가 많다고 말하지만 프로파일 링으로 얻은 것, 또는 어떤 질문이 떠오르는지에 관해 많이 말하지 마십시오. – jberryman
그러나 완전한 대답은 아니지만 길이를 인쇄하여 전체 단어 목록을 강제로 작성하고 dict 생성을 위해 메모리에 유지합니다. 길이를 인쇄하지 않음으로써 기본, 더 큰 힙 크기에서 약 100 만 개를 절약했습니다. 필요한 경우 사전 구성과 병행하여 길이 값을 만들 수 있습니다. –