bu çözümün neden bu kadar yavaş olduğunu nasıl öğrenebilirim? Bana hesaplama zamanının çoğunun nerede harcandığını söyleyen herhangi bir komut var mı, böylece haskell programımın hangi bölümünün yavaş olduğunu öğrenebilir miyim?
Tam! GHC, aşağıdakiler dahil birçok mükemmel araç sağlar:
Zaman ve mekan profili oluşturma hakkında bir eğitim, Real World Haskell'in bir parçasıdır .
GC İstatistikleri
İlk olarak, ghc -O2 ile derlediğinizden emin olun. Ve bunun modern bir GHC olduğundan emin olabilirsiniz (ör. GHC 6.12.x)
Yapabileceğimiz ilk şey, çöp toplamanın sorun olmadığını kontrol etmektir. Programınızı + RTS -s ile çalıştırın
$ time ./A +RTS -s
./A +RTS -s
749700
9,961,432,992 bytes allocated in the heap
2,463,072 bytes copied during GC
29,200 bytes maximum residency (1 sample(s))
187,336 bytes maximum slop
**2 MB** total memory in use (0 MB lost due to fragmentation)
Generation 0: 19002 collections, 0 parallel, 0.11s, 0.15s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 13.15s ( 13.32s elapsed)
GC time 0.11s ( 0.15s elapsed)
RP time 0.00s ( 0.00s elapsed)
PROF time 0.00s ( 0.00s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 13.26s ( 13.47s elapsed)
%GC time **0.8%** (1.1% elapsed)
Alloc rate 757,764,753 bytes per MUT second
Productivity 99.2% of total user, 97.6% of total elapsed
./A +RTS -s 13.26s user 0.05s system 98% cpu 13.479 total
Bu da bize zaten çok fazla bilgi veriyor: yalnızca 2M'lik bir yığınınız var ve GC zamanın% 0,8'ini kaplıyor. Bu nedenle, sorunun tahsisat olduğu konusunda endişelenmenize gerek yok.
Zaman Profilleri
Programınız için bir zaman profili elde etmek basittir: -prof -auto-all ile derleyin
$ ghc -O2 --make A.hs -prof -auto-all
[1 of 1] Compiling Main ( A.hs, A.o )
Linking A ...
Ve N = 200 için:
$ time ./A +RTS -p
749700
./A +RTS -p 13.23s user 0.06s system 98% cpu 13.547 total
bu, aşağıdakileri içeren bir A.prof dosyası oluşturur:
Sun Jul 18 10:08 2010 Time and Allocation Profiling Report (Final)
A +RTS -p -RTS
total time = 13.18 secs (659 ticks @ 20 ms)
total alloc = 4,904,116,696 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
numDivs Main 100.0 100.0
Belirten bütün zaman numDivs harcandığını ve aynı zamanda tüm ayırmaları kaynağıdır.
Yığın Profilleri
Ayrıca, aşağıdakileri oluşturarak bir postscript dosyasına (hp2ps -c A.hp) dönüştürerek görüntüleyebileceğiniz A.hp'yi oluşturan + RTS -p -hy ile çalıştırarak bu tahsislerin dökümünü alabilirsiniz:
bu bize bellek kullanımınızda yanlış bir şey olmadığını söyler: sabit alanda ayırma.
Yani probleminiz numDivs'in algoritmik karmaşıklığı:
toInteger $ length [ x | x<-[2.. ((n `quot` 2)+1)], n `rem` x == 0] + 2
Çalıştırma sürenizin% 100'ü olan bunu düzeltin ve diğer her şey kolaydır.
Optimizasyonlar
Bu ifade, akış füzyon optimizasyonu için iyi bir adaydır , bu yüzden Data.Vector'ı kullanmak için yeniden yazacağım , şöyle:
numDivs n = fromIntegral $
2 + (U.length $
U.filter (\x -> fromIntegral n `rem` x == 0) $
(U.enumFromN 2 ((fromIntegral n `div` 2) + 1) :: U.Vector Int))
Gereksiz yığın ayırmaları olmadan tek bir döngüde birleşmelidir. Yani, liste sürümünden (sabit faktörlere göre) daha iyi karmaşıklığa sahip olacaktır. Optimizasyondan sonra ara kodu incelemek için ghc-core aracını (ileri düzey kullanıcılar için) kullanabilirsiniz.
Bunu test ediyorum, ghc -O2 - Z.hs yapın
$ time ./Z
749700
./Z 3.73s user 0.01s system 99% cpu 3.753 total
Böylece, algoritmanın kendisini değiştirmeden, N = 150 için çalışma süresini 3,5x azalttı.
Sonuç
Senin sorunun numDivs. Çalıştırma sürenizin% 100'üdür ve korkunç bir karmaşıklığa sahiptir. NumDiv'leri düşünün ve örneğin her N için nasıl [2 .. n div
2 + 1] N kez ürettiğinizi düşünün . Değerler değişmediği için bunu hatırlamayı deneyin.
Hangi işlevlerinizin daha hızlı olduğunu ölçmek için , çalışma süresindeki mikrosaniyenin altındaki iyileştirmeler hakkında istatistiksel olarak sağlam bilgiler sağlayacak olan ölçüt kullanmayı düşünün .
Addenda
NumDivs, çalışma sürenizin% 100'ü olduğundan, programın diğer bölümlerine dokunmak pek bir fark yaratmayacaktır, ancak pedagojik amaçlar için, bunları stream fusion kullanarak yeniden yazabiliriz.
Ayrıca, deneme Listesini yeniden yazabilir ve bir "önek tarama" işlevi olan (diğer adıyla scanl) deneme Listesi2'de elle yazdığınız döngüye dönüştürmek için füzyona güvenebiliriz:
triaList = U.scanl (+) 0 (U.enumFrom 1 top)
where
top = 10^6
Sol için benzer şekilde:
sol :: Int -> Int
sol n = U.head $ U.filter (\x -> numDivs x > n) triaList
Aynı genel çalışma süresine sahip, ancak biraz daha temiz bir kod.
time
Don'un Zaman Profillerinde bahsettiği yardımcıtime
program sadece Linux programıdır. Windows'ta mevcut değildir. Dolayısıyla, Windows'ta (aslında herhangi bir yerde) zaman profili oluşturmak için bu soruya bakın .