تحسين المحلل اللغوي البسيط الذي يتم استدعاؤه عدة مرات

StackOverflow https://stackoverflow.com//questions/24063219

  •  02-01-2020
  •  | 
  •  

سؤال

لقد كتبت محللًا لملف مخصص باستخدام attoparsec.أشار تقرير ملفات التعريف إلى أن حوالي 67% من تخصيص الذاكرة يتم في وظيفة مسماة tab, ، والذي يستهلك أيضًا معظم الوقت.ال tab الوظيفة بسيطة جدًا:

tab :: Parser Char
tab = char '\t'

تقرير الملف الشخصي بأكمله هو كما يلي:

       ASnapshotParser +RTS -p -h -RTS

    total time  =       37.88 secs   (37882 ticks @ 1000 us, 1 processor)
    total alloc = 54,255,105,384 bytes  (excludes profiling overheads)

COST CENTRE    MODULE                %time %alloc

tab            Main                   83.1   67.7
main           Main                    6.4    4.2
readTextDevice Data.Text.IO.Internal   5.5   24.0
snapshotParser Main                    4.7    4.0


                                                             individual     inherited
COST CENTRE        MODULE                  no.     entries  %time %alloc   %time %alloc

MAIN               MAIN                     75           0    0.0    0.0   100.0  100.0
 CAF               Main                    149           0    0.0    0.0   100.0  100.0
  tab              Main                    156           1    0.0    0.0     0.0    0.0
  snapshotParser   Main                    153           1    0.0    0.0     0.0    0.0
  main             Main                    150           1    6.4    4.2   100.0  100.0
   doStuff         Main                    152     1000398    0.3    0.0    88.1   71.8
    snapshotParser Main                    154           0    4.7    4.0    87.7   71.7
     tab           Main                    157           0   83.1   67.7    83.1   67.7
   readTextDevice  Data.Text.IO.Internal   151       40145    5.5   24.0     5.5   24.0
 CAF               Data.Text.Array         142           0    0.0    0.0     0.0    0.0
 CAF               Data.Text.Internal      140           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Handle.FD        122           0    0.0    0.0     0.0    0.0
 CAF               GHC.Conc.Signal         103           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Encoding         101           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.FD               100           0    0.0    0.0     0.0    0.0
 CAF               GHC.IO.Encoding.Iconv    89           0    0.0    0.0     0.0    0.0
  main             Main                    155           0    0.0    0.0     0.0    0.0

كيف يمكنني تحسين هذا؟

الكود بأكمله للمحلل هنا. يبلغ حجم الملف الذي أقوم بتحليله حوالي 77 ميجابايت.

هل كانت مفيدة؟

المحلول

tab هو كبش فداء.إذا قمت بتحديد boo :: Parser (); boo = return () وأدخل أ boo قبل كل ربط في snapshotParser التعريف، فإن تخصيصات التكلفة ستصبح شيئًا مثل:

 main             Main                    255           0   11.8   13.8   100.0  100.0
  doStuff         Main                    258     2097153    1.1    0.5    86.2   86.2
   snapshotParser Main                    260           0    0.4    0.1    85.1   85.7
    boo           Main                    262           0   71.0   73.2    84.8   85.5
     tab          Main                    265           0   13.8   12.3    13.8   12.3

لذلك يبدو أن منشئ ملفات التعريف يقوم بإلقاء اللوم على تخصيصات نتائج التحليل، ويرجع ذلك على الأرجح إلى التضمين الشامل لـ attoparsec الكود، كما اقترح John L في التعليقات.

أما بالنسبة لقضايا الأداء، فالنقطة الأساسية هي أنه أثناء قيامك بتحليل ملف نصي بحجم 77 ميجابايت لإنشاء قائمة تحتوي على مليون عنصر، فإنك تريد أن تكون معالجة الملف كسولة وليست صارمة.بمجرد حل ذلك، قم بفصل الإدخال/الإخراج والتحليل doStuff ومن المفيد أيضًا إنشاء قائمة اللقطات بدون مُراكم.إليك نسخة معدلة من برنامجك مع أخذ ذلك في الاعتبار.

{-# LANGUAGE BangPatterns #-}
module Main where

import Data.Maybe
import Data.Attoparsec.Text.Lazy
import Control.Applicative
import qualified Data.Text.Lazy.IO as TL
import Data.Text (Text)
import qualified Data.Text.Lazy as TL

buildStuff :: TL.Text -> [Snapshot]
buildStuff text = case maybeResult (parse endOfInput text) of
  Just _ -> []
  Nothing -> case parse snapshotParser text of
      Done !i !r -> r : buildStuff i
      Fail _ _ _ -> []

main :: IO ()
main = do
  text <- TL.readFile "./snap.dat"
  let ss = buildStuff text
  print $ listToMaybe ss
    >> Just (fromIntegral (length $ show ss) / fromIntegral (length ss))

newtype VehicleId = VehicleId Int deriving Show
newtype Time = Time Int deriving Show
newtype LinkID = LinkID Int deriving Show
newtype NodeID = NodeID Int deriving Show
newtype LaneID = LaneID Int deriving Show

tab :: Parser Char
tab = char '\t'

-- UNPACK pragmas. GHC 7.8 unboxes small strict fields automatically;
-- however, it seems we still need the pragmas while profiling. 
data Snapshot = Snapshot {
  vehicle :: {-# UNPACK #-} !VehicleId,
  time :: {-# UNPACK #-} !Time,
  link :: {-# UNPACK #-} !LinkID,
  node :: {-# UNPACK #-} !NodeID,
  lane :: {-# UNPACK #-} !LaneID,
  distance :: {-# UNPACK #-} !Double,
  velocity :: {-# UNPACK #-} !Double,
  vehtype :: {-# UNPACK #-} !Int,
  acceler :: {-# UNPACK #-} !Double,
  driver :: {-# UNPACK #-} !Int,
  passengers :: {-# UNPACK #-} !Int,
  easting :: {-# UNPACK #-} !Double,
  northing :: {-# UNPACK #-} !Double,
  elevation :: {-# UNPACK #-} !Double,
  azimuth :: {-# UNPACK #-} !Double,
  user :: {-# UNPACK #-} !Int
  } deriving (Show)

-- No need for bang patterns here.
snapshotParser :: Parser Snapshot
snapshotParser = do
  sveh <- decimal
  tab
  stime <- decimal
  tab
  slink <- decimal
  tab
  snode <- decimal
  tab
  slane <- decimal
  tab
  sdistance <- double
  tab
  svelocity <- double
  tab
  svehtype <- decimal
  tab
  sacceler <- double
  tab
  sdriver <- decimal
  tab
  spassengers <- decimal
  tab
  seasting <- double
  tab
  snorthing <- double
  tab
  selevation <- double
  tab
  sazimuth <- double
  tab
  suser <- decimal
  endOfLine <|> endOfInput
  return $ Snapshot
    (VehicleId sveh) (Time stime) (LinkID slink) (NodeID snode)
    (LaneID slane) sdistance svelocity svehtype sacceler sdriver
    spassengers seasting snorthing selevation sazimuth suser

يجب أن يتمتع هذا الإصدار بأداء مقبول حتى لو قمت بإدخال قائمة اللقطات بأكملها في الذاكرة، كما فعلت في ذلك الوقت main هنا.لقياس ما هو "مقبول"، ضع في اعتبارك أنه بالنظر إلى الحقول الستة عشر (الصغيرة وغير المعبأة) في كل منها Snapshot بالإضافة إلى تكاليف غير مباشرة التابع Snapshot ومنشئو القائمة، نحن نتحدث عن 152 بايت لكل خلية قائمة، والتي تتلخص في حوالي 152 ميجابايت لبيانات الاختبار الخاصة بك.على أية حال، هذا الإصدار كسول قدر الإمكان، كما ترون من خلال إزالة التقسيم main, ، أو استبداله last ss.

ملحوظة:تم إجراء اختباراتي باستخدام attoparsec-0.12.

نصائح أخرى

بعد تحديث attoparsec إلى الإصدار الأحدث (0.12.0.0) ، انخفض الوقت المستغرق للتنفيذ من 38 ثانية إلى 16 ثانية.هذا أكثر من 50٪ تسريع.كما انخفضت الذاكرة التي يستهلكها بشكل كبير.كما لاحظ @JohnL، مع تمكين التوصيف، تختلف النتائج بشكل كبير.عندما حاولت تعريفه بأحدث إصدار من مكتبة attoparsec، استغرق تنفيذ البرنامج بأكمله حوالي 64 ثانية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top