गति और अनुकूलन का अध्ययन करते समय, बेतहाशा गलत परिणाम प्राप्त करना बहुत आसान है । विशेष रूप से, आप वास्तव में यह नहीं कह सकते हैं कि संकलक संस्करण और आपके बेंचमार्किंग सेटअप के अनुकूलन मोड का उल्लेख किए बिना एक संस्करण दूसरे की तुलना में तेज़ है। फिर भी, आधुनिक प्रोसेसर तंत्रिका नेटवर्क आधारित शाखा भविष्यवाणियों की सुविधा के लिए इतने परिष्कृत हैं, सभी प्रकार के कैश का उल्लेख नहीं करते हैं, इसलिए, यहां तक कि सावधान सेट-अप के साथ, बेंचमार्किंग परिणाम धुंधले होंगे।
ऐसा कहे जाने के बाद...
बेंचमार्किंग हमारा दोस्त है।
criterionएक पैकेज है जो उन्नत बेंचमार्किंग उपकरण प्रदान करता है। मैंने जल्दी से इस तरह एक बेंचमार्क का मसौदा तैयार किया:
module Main where
import Criterion
import Criterion.Main
-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"
-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse
-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init
butLast2 :: [a] -> a
butLast2 (x : _ : [ ] ) = x
butLast2 (_ : xs@(_ : _ ) ) = butLast2 xs
butLast2 _ = error "List too short"
setupEnv = do
let xs = [1 .. 10^7] :: [Int]
return xs
benches xs =
[ bench "slow?" $ nf myButLast xs
, bench "decent?" $ nf myButLast' xs
, bench "fast?" $ nf myButLast'' xs
, bench "match2" $ nf butLast2 xs
]
main = defaultMain
[ env setupEnv $ \ xs -> bgroup "main" $ let bs = benches xs in bs ++ reverse bs ]
जैसा कि आप देख रहे हैं, मैंने उस वेरिएंट को जोड़ा है जो स्पष्ट रूप से एक साथ दो तत्वों पर मेल खाता है, लेकिन अन्यथा यह एक ही कोड वर्बेटिम है। मैं बेंचमार्क को रिवर्स में भी चलाता हूं, ताकि कैशिंग के कारण पूर्वाग्रह के बारे में पता चल सके। तो, हम चलें और देखें!
% ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.6.5
% ghc -O2 -package criterion A.hs && ./A
benchmarking main/slow?
time 54.83 ms (54.75 ms .. 54.90 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 54.86 ms (54.82 ms .. 54.93 ms)
std dev 94.77 μs (54.95 μs .. 146.6 μs)
benchmarking main/decent?
time 794.3 ms (32.56 ms .. 1.293 s)
0.907 R² (0.689 R² .. 1.000 R²)
mean 617.2 ms (422.7 ms .. 744.8 ms)
std dev 201.3 ms (105.5 ms .. 283.3 ms)
variance introduced by outliers: 73% (severely inflated)
benchmarking main/fast?
time 84.60 ms (84.37 ms .. 84.95 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 84.46 ms (84.25 ms .. 84.77 ms)
std dev 435.1 μs (239.0 μs .. 681.4 μs)
benchmarking main/match2
time 54.87 ms (54.81 ms .. 54.95 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 54.85 ms (54.81 ms .. 54.92 ms)
std dev 104.9 μs (57.03 μs .. 178.7 μs)
benchmarking main/match2
time 50.60 ms (47.17 ms .. 53.01 ms)
0.993 R² (0.981 R² .. 0.999 R²)
mean 60.74 ms (56.57 ms .. 67.03 ms)
std dev 9.362 ms (6.074 ms .. 10.95 ms)
variance introduced by outliers: 56% (severely inflated)
benchmarking main/fast?
time 69.38 ms (56.64 ms .. 78.73 ms)
0.948 R² (0.835 R² .. 0.994 R²)
mean 108.2 ms (92.40 ms .. 129.5 ms)
std dev 30.75 ms (19.08 ms .. 37.64 ms)
variance introduced by outliers: 76% (severely inflated)
benchmarking main/decent?
time 770.8 ms (345.9 ms .. 1.004 s)
0.967 R² (0.894 R² .. 1.000 R²)
mean 593.4 ms (422.8 ms .. 691.4 ms)
std dev 167.0 ms (50.32 ms .. 226.1 ms)
variance introduced by outliers: 72% (severely inflated)
benchmarking main/slow?
time 54.87 ms (54.77 ms .. 55.00 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 54.95 ms (54.88 ms .. 55.10 ms)
std dev 185.3 μs (54.54 μs .. 251.8 μs)
ऐसा लगता है कि हमारा "धीमा" संस्करण बिल्कुल भी धीमा नहीं है! और पैटर्न मिलान की पेचीदगियां कुछ भी नहीं जोड़ती हैं। (एक छोटी सी गति जो हम देखते हैं कि match2मैं लगातार दो रन के बीच कैशिंग के प्रभावों को बताता हूं।)
अधिक "वैज्ञानिक" डेटा प्राप्त करने का एक तरीका है : -ddump-simplकंपाइलर हमारे कोड को देखने के तरीके पर ध्यान दे सकता है ।
मध्यवर्ती संरचनाओं का निरीक्षण हमारा मित्र है।
"कोर" जीएचसी की एक आंतरिक भाषा है। हर हास्केल स्रोत फ़ाइल को चलाने के लिए अंतिम समय के लिए अंतिम कार्यात्मक ग्राफ में तब्दील होने से पहले कोर को सरलीकृत किया जाता है। यदि हम इस मध्यवर्ती चरण को देखते हैं, तो यह हमें बताएगा कि myButLastऔर butLast2समतुल्य हैं। यह देख रहा है, के बाद से, नामकरण चरण में, हमारे सभी अच्छे पहचानकर्ता बेतरतीब ढंग से मैंग्ड हैं।
% for i in `seq 1 4`; do echo; cat A$i.hs; ghc -O2 -ddump-simpl A$i.hs > A$i.simpl; done
module A1 where
-- slow
myButLast :: [a] -> a
myButLast [x, y] = x
myButLast (x : xs) = myButLast xs
myButLast _ = error "List too short"
module A2 where
-- decent
myButLast' :: [a] -> a
myButLast' = (!! 1) . reverse
module A3 where
-- fast
myButLast'' :: [a] -> a
myButLast'' = last . init
module A4 where
butLast2 :: [a] -> a
butLast2 (x : _ : [ ] ) = x
butLast2 (_ : xs@(_ : _ ) ) = butLast2 xs
butLast2 _ = error "List too short"
% ./EditDistance.hs *.simpl
(("A1.simpl","A2.simpl"),3866)
(("A1.simpl","A3.simpl"),3794)
(("A2.simpl","A3.simpl"),663)
(("A1.simpl","A4.simpl"),607)
(("A2.simpl","A4.simpl"),4188)
(("A3.simpl","A4.simpl"),4113)
ऐसा लगता है कि A1और A4सबसे समान हैं। पूरी तरह से निरीक्षण से पता चलेगा कि वास्तव में कोड संरचनाएं समान हैं A1और A4समान हैं। यही कारण है कि A2और A3एक जैसे हैं, क्योंकि दोनों ही दो कार्यों की एक संरचना के रूप में परिभाषित कर रहे हैं भी उचित है।
यदि आप coreबड़े पैमाने पर आउटपुट की जांच करने जा रहे हैं , तो यह इस तरह के और झंडे की आपूर्ति करने के लिए भी समझ में आता है । वे इसे पढ़ने में बहुत आसान बनाते हैं।-dsuppress-module-prefixes-dsuppress-uniques
हमारे दुश्मनों की एक छोटी सूची भी।
तो, बेंचमार्किंग और अनुकूलन में क्या गलत हो सकता है?
ghciइंटरएक्टिव प्ले और त्वरित पुनरावृत्ति के लिए डिज़ाइन किया जा रहा है, अंतिम निष्पादन योग्य के बजाय बाइट कोड के एक निश्चित स्वाद के लिए हास्केल स्रोत को संकलित करता है, और त्वरित पुनः लोड के पक्ष में महंगी अनुकूलन को बढ़ाता है।
- प्रोफाइलिंग एक जटिल प्रोग्राम के व्यक्तिगत बिट्स और टुकड़ों के प्रदर्शन को देखने के लिए एक अच्छा उपकरण की तरह लगता है, लेकिन यह कंपाइलर अनुकूलन को बुरी तरह से बर्बाद कर सकता है, परिणाम आधार के परिमाण के आदेश होंगे।
- आपका सुरक्षाकर्मी प्रत्येक छोटे कोड को एक अलग निष्पादन योग्य के रूप में अपने स्वयं के बेंचमार्क धावक के साथ प्रोफाइल करना है।
- कचरा संग्रह करने योग्य है। आज ही के दिन एक नया प्रमुख फीचर जारी किया गया था। कचरा संग्रह के लिए देरी ऐसे तरीकों से प्रदर्शन को प्रभावित करेगी जो भविष्यवाणी करने के लिए सीधे नहीं हैं।
- जैसा कि मैंने उल्लेख किया है, विभिन्न संकलक संस्करण अलग-अलग प्रदर्शन के साथ अलग-अलग कोड का निर्माण करेंगे, इसलिए आपको यह जानना होगा कि आपके कोड का उपयोगकर्ता किस संस्करण का उपयोग करने की संभावना रखेगा, और इससे पहले कि आप कोई भी वादा करें, उसके साथ बेंचमार्क करें।
यह दुखद लग सकता है। लेकिन यह वास्तव में वह चीज नहीं है जिसे हास्केल प्रोग्रामर की चिंता करनी चाहिए, ज्यादातर समय। वास्तविक कहानी: मेरा एक दोस्त है जिसने अभी हाल ही में हास्केल सीखना शुरू किया है। उन्होंने संख्यात्मक एकीकरण के लिए एक कार्यक्रम लिखा था, और यह कछुआ धीमा था। इसलिए हम एक साथ बैठ गए और आरेख और सामान के साथ एल्गोरिथ्म का एक श्रेणीबद्ध विवरण लिखा । जब उन्होंने अमूर्त विवरण के साथ संरेखित करने के लिए कोड को फिर से लिखा, तो यह जादुई रूप से हो गया, जैसे, चीता तेज़, और स्मृति पर पतला भी। हमने कुछ ही समय में no की गणना की। कहानी का नैतिक? सही सार संरचना, और आपका कोड खुद को अनुकूलित करेगा।
initकई बार "अनपैकिंग" सूची से बचने के लिए अनुकूलित किया गया है।