कैसे पता करें कि यह समाधान इतना धीमा क्यों है। क्या ऐसी कोई आज्ञा है जो मुझे बताती है कि अधिकांश संगणना-समय कहाँ व्यतीत होता है इसलिए मुझे पता है कि मेरे हैस्केल-प्रोग्राम का कौन सा भाग धीमा है?
यकीनन! GHC कई उत्कृष्ट उपकरण प्रदान करता है, जिनमें शामिल हैं:
समय और स्थान की रूपरेखा का उपयोग करने पर एक ट्यूटोरियल रियल वर्ल्ड हास्केल का हिस्सा है ।
जीसी सांख्यिकी
सबसे पहले, सुनिश्चित करें कि आप gh -O2 के साथ संकलन कर रहे हैं। और आप यह सुनिश्चित कर सकते हैं कि यह एक आधुनिक जीएचसी है (उदाहरण के लिए जीएचसी 6.12.x)
पहली चीज जो हम कर सकते हैं वह है कि कचरा संग्रहण समस्या नहीं है। + RTS -s के साथ अपना प्रोग्राम चलाएं
$ 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
जो पहले से ही हमें बहुत सारी जानकारी देता है: आपके पास केवल 2M हीप है, और GC में 0.8% समय लगता है। इसलिए चिंता करने की जरूरत नहीं है कि आवंटन समस्या है।
समय प्रोफ़ाइल
अपने कार्यक्रम के लिए एक समय प्रोफ़ाइल प्राप्त करना सीधे आगे है: -ऑफ़ट -ऑटो-संकलन के साथ
$ ghc -O2 --make A.hs -prof -auto-all
[1 of 1] Compiling Main ( A.hs, A.o )
Linking A ...
और, एन = 200 के लिए:
$ time ./A +RTS -p
749700
./A +RTS -p 13.23s user 0.06s system 98% cpu 13.547 total
जो एक फ़ाइल बनाता है, A.prof, युक्त:
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
यह इंगित करते हुए कि आपका सारा समय सुन्नियों में बीता है, और यह आपके सभी आवंटन का स्रोत भी है।
ढेर प्रोफाइल
आप उन आवंटन का ब्रेक डाउन भी प्राप्त कर सकते हैं, + RTS -p -hy के साथ चलकर, जो कि Ahp बनाता है, जिसे आप इसे पोस्टस्क्रिप्ट फ़ाइल में परिवर्तित करके देख सकते हैं (hp2ps -c A.hp), जनरेट कर रहा है:
जो हमें बताता है कि आपकी मेमोरी के उपयोग में कुछ भी गलत नहीं है: यह निरंतर स्थान में आवंटित कर रहा है।
तो आपकी समस्या numDivs की एल्गोरिथम जटिलता है:
toInteger $ length [ x | x<-[2.. ((n `quot` 2)+1)], n `rem` x == 0] + 2
उसे ठीक करें, जो आपके चलने के समय का 100% है, और बाकी सब कुछ आसान है।
अनुकूलन
यह अभिव्यक्ति स्ट्रीम फ़्यूज़न ऑप्टिमाइज़ेशन के लिए एक अच्छा उम्मीदवार है , इसलिए मैं इसे Data.Vector का उपयोग करने के लिए फिर से लिखूँगा , जैसे:
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))
जिसे बिना किसी अनावश्यक हीप आवंटन के एकल लूप में फ्यूज करना चाहिए। यही है, यह सूची संस्करण की तुलना में बेहतर जटिलता (स्थिर कारकों द्वारा) होगा। आप ऑप्टिमाइज़ेशन के बाद इंटरमीडिएट कोड का निरीक्षण करने के लिए ghc-core टूल (उन्नत उपयोगकर्ताओं के लिए) का उपयोग कर सकते हैं।
यह परीक्षण, ghc -O2 - Zake
$ time ./Z
749700
./Z 3.73s user 0.01s system 99% cpu 3.753 total
तो यह एल्गोरिथ्म को बदलने के बिना, एन = 150 के लिए 3.5x तक चलने का समय कम कर दिया।
निष्कर्ष
आपकी समस्या स्तब्ध है। यह आपके चलने के समय का 100% है, और इसमें भयानक जटिलता है। संख्याओं के बारे में सोचें, और कैसे, उदाहरण के लिए, प्रत्येक N के लिए आप [2 .. n div
2 + 1] N बार उत्पन्न कर रहे हैं । मानों को न बदलने के कारण, उसे याद रखने का प्रयास करें।
मापने के लिए कि आपका कौन सा कार्य तेज़ है, मानदंड का उपयोग करने पर विचार करें , जो रनिंग टाइम में उप-माइक्रोसेकंड सुधार के बारे में सांख्यिकीय रूप से मजबूत जानकारी प्रदान करेगा।
परिशिष्ट
चूंकि सुगमता आपके चलने के समय का 100% है, इसलिए कार्यक्रम के अन्य हिस्सों को छूने से बहुत फर्क नहीं पड़ेगा, हालांकि, शैक्षणिक उद्देश्यों के लिए, हम स्ट्रीम फ्यूजन का उपयोग करने वालों को भी फिर से लिख सकते हैं।
हम ट्रायलिस्ट को भी फिर से लिख सकते हैं, और ट्रायल पर भरोसा करके इसे लूप में बदल सकते हैं, जिसे आप ट्रायल लिस्ट 2 में हाथ से लिखते हैं, जो कि "प्रीफिक्स स्कैन" फंक्शन (उर्फ स्कैनल) है:
triaList = U.scanl (+) 0 (U.enumFrom 1 top)
where
top = 10^6
इसी तरह सोल के लिए:
sol :: Int -> Int
sol n = U.head $ U.filter (\x -> numDivs x > n) triaList
एक ही समग्र समय चल रहा है, लेकिन एक बिट क्लीनर कोड के साथ।
time
डॉन ने टाइम प्रोफाइल में जिस उपयोगिता का उल्लेख किया है वह सिर्फ लिनक्सtime
प्रोग्राम है। यह विंडोज में उपलब्ध नहीं है। तो विंडोज पर (वास्तव में कहीं भी) प्रोफाइलिंग के लिए, इस प्रश्न को देखें ।