हास्केल मेमोरी दक्षता - बेहतर दृष्टिकोण कौन सा है?


11

हम एक संशोधित दो आयामी व्याकरण वाक्यविन्यास के आधार पर मैट्रिक्स संपीड़न लाइब्रेरी को लागू कर रहे हैं। अब हमारे पास हमारे डेटा प्रकारों के लिए दो दृष्टिकोण हैं-जो स्मृति उपयोग के मामले में बेहतर होगा? (हम कुछ सेक करना चाहते हैं;))।

व्याकरणों में नॉनटर्मिनल होते हैं जिनमें बिल्कुल 4 प्रोडक्शंस या दाएं तरफ एक टर्मिनल होता है। हमें समानता जांच और व्याकरण न्यूनतम करने के लिए प्रोडक्शंस के नामों की आवश्यकता होगी।

सबसे पहला:

-- | Type synonym for non-terminal symbols
type NonTerminal = String

-- | Data type for the right hand side of a production
data RightHandSide = DownStep NonTerminal NonTerminal NonTerminal NonTerminal | Terminal Int

-- | Data type for a set of productions
type ProductionMap = Map NonTerminal RightHandSide

data MatrixGrammar = MatrixGrammar {
    -- the start symbol
    startSymbol :: NonTerminal,
    -- productions
    productions :: ProductionMap    
    } 

यहां हमारा राइटहैंडसाइड डेटा अगले प्रस्तुतियों को निर्धारित करने के लिए केवल स्ट्रिंग नामों को बचाता है, और जो हम यहां नहीं जानते हैं कि हास्केल इन तारों को कैसे बचाता है। उदाहरण के लिए [[0, 0], [0, 0]] मैट्रिक्स में 2 निर्माण हैं:

a = Terminal 0
aString = "A"
b = DownStep aString aString aString aString
bString = "B"
productions = Map.FromList [(aString, a), (bString, b)]

तो यहाँ सवाल यह है कि स्ट्रिंग "ए" वास्तव में कितनी बार बचा है? एक बार aString में, 4 बार b में और एक बार प्रोडक्शंस में या सिर्फ एक बार aString में और बाकी लोग बस "सस्ता" संदर्भ रखते हैं?

द्वितीय:

data Production = NonTerminal String Production Production Production Production
                | Terminal String Int 

type ProductionMap = Map String Production

यहां "टर्मिनल" शब्द थोड़ा भ्रामक है क्योंकि इसका वास्तव में उत्पादन है जिसमें दाहिने हाथ के रूप में टर्मिनल है। वही मैट्रिक्स:

a = Terminal "A" 0
b = NonTerminal "B" a a a a
productions = Map.fromList [("A", a), ("B", b)]

और इसी तरह का प्रश्न: उत्पादन कितनी बार आंतरिक रूप से हास्केल द्वारा बचाया जाता है? संभवतः हम प्रोडक्शंस के नाम को छोड़ देंगे यदि हमें उनकी आवश्यकता नहीं है, लेकिन हम अभी इस बारे में निश्चित नहीं हैं।

तो हम कहते हैं कि हमारे पास लगभग 1000 प्रस्तुतियों के साथ एक व्याकरण है। कौन सा दृष्टिकोण कम मेमोरी का उपभोग करेगा?

अंत में हास्केल में पूर्णांक के बारे में एक प्रश्न: वर्तमान में हम स्ट्रिंग्स के रूप में नाम पर योजना बना रहे हैं। लेकिन हम आसानी से पूर्णांक नामों पर स्विच कर सकते हैं क्योंकि 1000 प्रस्तुतियों के साथ हमारे पास और अधिक 4 वर्णों के नाम होंगे (जो कि मैं 32 बिट है?)। हास्केल इसे कैसे संभालती है। क्या एक Int हमेशा 32 Bit और Integer मेमोरी को आवंटित करता है जो वास्तव में इसकी आवश्यकता है?

मैं भी इस के माध्यम से पढ़ने: हास्केल के मूल्य / संदर्भ अर्थ विज्ञान की तैयार परीक्षण - लेकिन मैं समझ नहीं वास्तव में क्या हमारे लिए अर्थ यह है कि - मैं एक जरूरी जावा बच्चे की अधिक कर रहा हूँ तो अच्छा कार्यात्मक प्रोग्रामर: पी

जवाबों:


7

आप अपने मैट्रिक्स व्याकरण का विस्तार ADT में कर सकते हैं, जिसमें थोड़ी सी भी चालाकी के साथ सही साझा किया जा सकता है:

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}

import Data.Map
import Data.Foldable
import Data.Functor
import Data.Traversable

-- | Type synonym for non-terminal symbols
type NonTerminal = String

-- | Data type for the right hand side of a production
data RHS a = DownStep NonTerminal NonTerminal NonTerminal NonTerminal | Terminal a
  deriving (Eq,Ord,Show,Read,Functor, Foldable, Traversable)

data G a = G NonTerminal (Map NonTerminal (RHS a))
  deriving (Eq,Ord,Show,Read,Functor)

data M a = Q (M a) (M a) (M a) (M a) | T a
  deriving (Functor, Foldable, Traversable)

tabulate :: G a -> M a
tabulate (G s pm) = loeb (expand <$> pm) ! s where
  expand (DownStep a11 a12 a21 a22) m = Q (m!a11) (m!a12) (m!a21) (m!a22)
  expand (Terminal a)               _ = T a

loeb :: Functor f => f (f b -> b) -> f b
loeb x = xs where xs = fmap ($xs) x

यहाँ मैंने आपके व्याकरणों को किसी भी प्रकार के डेटा के लिए अनुमति देने के लिए सामान्यीकृत किया है, न केवल इंट, और tabulateव्याकरण को ले जाएगा और इसे स्वयं का उपयोग करके इसे तह करके विस्तारित करेगा loeb

loebडैन पिपोनी के एक लेख में वर्णित है

ADT के रूप में परिणामी विस्तार शारीरिक रूप से मूल व्याकरण की तुलना में अधिक मेमोरी नहीं लेता है - वास्तव में यह काफी कम होता है, क्योंकि इसमें मैप स्पाइन के लिए अतिरिक्त लॉग-फैक्टर की आवश्यकता नहीं होती है, और इसे स्टोर करने की आवश्यकता नहीं होती है तार बिल्कुल।

अनुभवहीन विस्तार के विपरीत, loebमुझे 'नॉट टाई' का उपयोग करने और एक ही गैर-टर्मिनल की सभी घटनाओं के लिए थ्रॉक्स साझा करने देता है।

यदि आप इस सब के सिद्धांत में और अधिक डुबकी लगाना चाहते हैं, तो हम देख सकते हैं कि RHSइसे एक आधार फ़नकार में बदला जा सकता है:

data RHS t nt = Q nt nt nt nt | L t

और फिर मेरा M प्रकार केवल उसी का निश्चित बिंदु है Functor

M a ~ Mu (RHS a)

जबकि G aएक चुने हुए तार और तार से एक नक्शा शामिल होगा (RHS String a)

हम तो विस्तार कर सकते हैं Gमें Mविस्तार तार lazily का एक नक्शा में प्रवेश ऊपर देखने से।

यह data-reifyपैकेज में किए गए दोहरे के प्रकार की तरह है , जो इस तरह के बेस फन्नेकर को ले सकता है, और कुछ ऐसा कर सकता है Mऔर Gइससे आपके समान नैतिक लाभ प्राप्त कर सकता है। वे गैर-टर्मिनल नामों के लिए एक अलग प्रकार का उपयोग करते हैं, जो मूल रूप से सिर्फ एक है Int

data Graph e = Graph [(Unique, e Unique)] Unique

और एक संयोजन प्रदान करते हैं

reifyGraph :: MuRef s => s -> IO (Graph (DeRef s))

जिसका उपयोग उपरोक्त डेटा प्रकारों पर एक उपयुक्त उदाहरण के साथ एक ग्राफ (मैट्रिक्स मैट्रिक्स) को एक मनमाना मैट्रिक्स से बाहर निकालने के लिए किया जा सकता है। यह समान लेकिन अलग-अलग संग्रहीत क्वाड्रंट का समर्पण नहीं करेगा, लेकिन यह मूल ग्राफ़ में मौजूद सभी साझाकरण को पुनर्प्राप्त कर देगा।


8

हास्केल में, स्ट्रिंग प्रकार [चार] के लिए एक उपनाम है, जो एक वेक्टर या सरणी नहीं, बल्कि चार की एक नियमित हास्केल सूची है। चार एक प्रकार है जो एक एकल यूनिकोड चरित्र रखता है। स्ट्रिंग के शाब्दिक हैं, जब तक आप एक भाषा एक्सटेंशन, स्ट्रिंग प्रकार के मूल्यों का उपयोग नहीं करते हैं।

मुझे लगता है कि आप ऊपर से अनुमान लगा सकते हैं कि स्ट्रिंग बहुत कॉम्पैक्ट या अन्यथा कुशल प्रतिनिधित्व नहीं है। स्ट्रिंग के लिए सामान्य वैकल्पिक अभ्यावेदन में Data.Text और Data.ByteString द्वारा आपूर्ति किए गए प्रकार शामिल होते हैं।

अतिरिक्त सुविधा के लिए, आप -XOverloadedStrings का उपयोग कर सकते हैं ताकि आप स्ट्रिंग स्ट्रिंग को वैकल्पिक स्ट्रिंग प्रकार के निरूपण के रूप में उपयोग कर सकें, जैसे कि Data.ByteString.Char8 द्वारा प्रदान किया गया है। पहचानकर्ताओं के रूप में स्ट्रिंग का आसानी से उपयोग करने के लिए शायद सबसे अधिक अंतरिक्ष-कुशल तरीका है।

जहां तक ​​Int जाता है, यह एक निश्चित-चौड़ाई का प्रकार है, लेकिन इसके बारे में कोई गारंटी नहीं है कि यह कितना व्यापक है, इसके अलावा यह मूल्यों को धारण करने के लिए पर्याप्त चौड़ा होना चाहिए [-2 ^ 29 .. 2 ^ 29-1]। यह बताता है कि यह कम से कम 32 बिट्स है, लेकिन 64 बिट्स होने से इंकार नहीं करता है। Data.Int के पास कुछ और विशिष्ट प्रकार हैं, Int8-Int64, जिन्हें आप विशिष्ट चौड़ाई की आवश्यकता होने पर उपयोग कर सकते हैं।

जानकारी जोड़ने के लिए संपादित करें

मुझे विश्वास नहीं होता कि हास्केल के शब्दार्थ किसी भी तरह से डेटा शेयरिंग के बारे में कुछ भी बताते हैं। आपको स्मृति में एक ही 'कैनोनिकल' ऑब्जेक्ट को संदर्भित करने के लिए दो स्ट्रिंग शाब्दिक या किसी भी निर्मित डेटा के दो की उम्मीद नहीं करनी चाहिए। यदि आप एक नए नाम के साथ एक निर्मित मूल्य (चलो, एक पैटर्न मैच, आदि) बांधने वाले थे, तो दोनों नाम सबसे अधिक समान डेटा को संदर्भित करेंगे, लेकिन क्या वे वास्तव में अपरिवर्तनीय प्रकृति के कारण दिखाई देते हैं या नहीं हास्केल डेटा।

स्टोरेज दक्षता के लिए, आप स्ट्रिंग्स को इंटर्न कर सकते हैं, जो अनिवार्य रूप से किसी प्रकार के लुकअप टेबल में प्रत्येक का एक कैनोनिकल प्रतिनिधित्व संग्रहीत करता है, आमतौर पर एक हैश तालिका। जब आप किसी वस्तु को इंटर्न करते हैं, तो आपको इसके लिए एक डिस्क्रिप्टर मिलता है, और आप उन डिस्क्रिप्टर की तुलना दूसरों के साथ कर सकते हैं, यह देखने के लिए कि क्या वे बहुत सस्ते में आपके स्ट्रिंग्स की तुलना में अधिक सस्ते हैं, और वे अक्सर बहुत छोटे होते हैं।

इंटर्न करने वाली लाइब्रेरी के लिए, आप https://github.com/ekmett/intern/ का उपयोग कर सकते हैं

रन-टाइम में किस पूर्णांक आकार का उपयोग करने का निर्णय लेने के लिए, कोड को लिखना काफी आसान है जो ठोस संख्यात्मक प्रकारों के बजाय इंटीग्रल या न्यूम प्रकार वर्गों पर निर्भर करता है। टाइप इंट्रेंस आपको अपने आप सबसे सामान्य प्रकार देगा। फिर आप विशिष्ट संख्यात्मक प्रकारों के लिए स्पष्ट रूप से संकुचित प्रकारों के साथ कुछ अलग कार्य कर सकते हैं जिन्हें आप प्रारंभिक सेटअप करने के लिए रनटाइम में से एक चुन सकते हैं, और उसके बाद अन्य पॉलीमॉर्फिक कार्यों के सभी उनमें से किसी पर भी काम करेंगे। उदाहरण के लिए:

polyConstructor :: Integral a => a -> MyType a
int16Constructor :: Int16 -> MyType Int16
int32Constructor :: Int32 -> MyType Int32

int16Constructor = polyConstructor
int32Constructor = polyConstructor

संपादित करें : इंटर्निंग के बारे में अधिक जानकारी

यदि आप केवल तारों को इंटर्न करना चाहते हैं, तो आप एक नया प्रकार बना सकते हैं जो एक स्ट्रिंग (अधिमानतः एक पाठ या बाइटस्ट्रिंग) और एक छोटा पूर्णांक एक साथ लपेटता है।

data InternedString = { id :: Int32, str :: Text }
instance Eq InternedString where
    {x, _ } == {y, _ }  =  x == y

intern :: MonadIO m => Text -> m InternedString

'इंटर्न' जो करता है वह स्ट्रिंग को एक कमजोर-संदर्भ हैशमैप में देखता है जहाँ टेक्स कीज़ होती हैं और इंटर्नस्ट्रेयर्स मान होते हैं। यदि कोई मिलान पाया जाता है, तो 'इंटर्न' मान लौटाता है। यदि नहीं, तो यह मूल पाठ और एक अद्वितीय पूर्णांक आईडी के साथ एक नया इंटर्नस्ट्रीमिंग मूल्य बनाता है (यही कारण है कि मैंने मोनाडिओ बाधा को शामिल किया; यह अद्वितीय आईडी प्राप्त करने के बजाय एक स्टेट मोनड या कुछ असुरक्षित ऑपरेशन का उपयोग कर सकता है; बहुत संभावनाएं हैं) और इसे वापस करने से पहले इसे मानचित्र में संग्रहीत करता है।

अब आपको पूर्णांक आईडी के आधार पर एक तेज़ तुलना मिलती है और केवल प्रत्येक अद्वितीय स्ट्रिंग की एक प्रति होती है।

एडवर्ड केमेट की आंतरिक लाइब्रेरी समान सिद्धांत को लागू करती है, कम या ज्यादा, सामान्य तरीके से, ताकि पूरे संरचित डेटा शब्द को हैशड किया जाए, विशिष्ट रूप से संग्रहीत किया जाए, और एक तेज़ तुलना ऑपरेशन दिया जाए। यह थोड़ा कठिन है और विशेष रूप से प्रलेखित नहीं है, लेकिन यदि आप पूछें तो वह मदद करने के लिए तैयार हो सकता है; या आप बस अपने स्वयं के स्ट्रिंग इंटर्निंग कार्यान्वयन की कोशिश कर सकते हैं यह देखने के लिए कि क्या यह पर्याप्त मदद करता है।


आपके उत्तर के लिए अब तक धन्यवाद। क्या यह निर्धारित करना संभव है कि हमें किस इंट साइज का उपयोग रनटाइम पर करना चाहिए? मुझे आशा है कि कोई अन्य व्यक्ति प्रतियां के साथ समस्या पर कुछ इनपुट दे सकता है :)
डेनिस इच

अतिरिक्त जानकारी के लिए धन्यवाद। मैं वहां देखूंगा। बस इसे ठीक करने के लिए यह डिस्क्रिप्टर यू बोल रहे हैं एक संदर्भ की तरह कुछ है जो हैशेड हो जाता है और तुलना की जा सकती है? क्या आपने अपने स्व के साथ यह काम किया? क्या आप कह सकते हैं कि कैसे "अधिक जटिल" यह इसके साथ हो जाता है क्योंकि पहली नज़र में ऐसा लगता है कि मुझे व्याकरण को परिभाषित करने के साथ बहुत सावधान रहना होगा;)
डेनिस इच

1
उस पुस्तकालय का लेखक एक बहुत ही उन्नत हास्केल उपयोगकर्ता है जिसे गुणवत्ता के काम के लिए जाना जाता है, लेकिन मैंने उस विशेष पुस्तकालय का उपयोग नहीं किया है। यह एक बहुत ही सामान्य उद्देश्य "हैश कॉन्स" कार्यान्वयन है, जो किसी भी निर्मित डेटा प्रकार में प्रतिनिधित्व साझा करने के लिए स्टोर करेगा और अनुमति देगा , न कि केवल तार। अपनी तरह की समस्या के लिए उसकी उदाहरण निर्देशिका को देखें, और आप देख सकते हैं कि समानता के कार्य कैसे कार्यान्वित किए जाते हैं।
लेवी पियर्सन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.