haskell อ่านเวกเตอร์ของคู่จาก bytestring ช้ามาก จะทำให้เร็วขึ้นได้อย่างไร?

ฉันกำลังพยายามอ่านเวกเตอร์ขนาดใหญ่ประเภทข้อมูลที่กำหนดเองจากไฟล์ไบนารี ฉันพยายามใช้ตัวอย่างที่ให้ไว้ที่นี่.

ปัญหาของโค้ดตัวอย่างคือ มันใช้รายการ และฉันต้องการใช้เวกเตอร์ ดังนั้นฉันจึงปรับโค้ดนั้นตามด้านล่าง แต่ใช้เวลานานมาก (มากกว่าหนึ่งนาทีหลังจากนั้น) เพื่ออ่านไฟล์ขนาด 1 MB

module Main where

import Data.Word
import qualified Data.ByteString.Lazy as BIN
import Data.Binary.Get
import qualified Data.Vector.Unboxed as Vec

main = do
  b <- BIN.readFile "dat.bin" -- about 1 MB size file
  let v = runGet getPairs (BIN.tail b) -- skip the first byte
  putStrLn $ show $ Vec.length v

getPair :: Get (Word8, Word8)
getPair = do
  price <- getWord8
  qty <- getWord8
  return (price, qty)

getPairs :: Get (Vec.Vector (Word8, Word8))
getPairs = do
 empty <- isEmpty
 if empty
   then return Vec.empty
   else do pair  <- getPair
           pairs <- getPairs
           return (Vec.cons pair pairs) -- is it slow because V.cons is O(n)?

เมื่อฉันพยายามเรียกใช้ด้วย ghc --make -O2 pairs.hs ฉันได้รับข้อผิดพลาด Stack space overflow: current size ...

จะอ่านคู่ของค่าจาก bytestring เป็น vector ได้อย่างมีประสิทธิภาพได้อย่างไร

ฉันต้องการรับโค้ดการทำงานที่สมบูรณ์ ไม่ใช่แค่ตัวชี้ไปยังไซต์ Haskell หรือ RWH หรือเพียงรายการฟังก์ชัน/ชื่อโมดูล


person mntk123    schedule 19.09.2015    source แหล่งที่มา
comment
คุณไม่ควร เรียกร้อง วิธีแก้ปัญหาการทำงานที่สมบูรณ์เป็นคำตอบ SO เป็นไซต์การเรียนรู้และทำความเข้าใจปัญหา ไม่ใช่บริการเขียนโค้ดที่มาจากฝูงชน   -  person Erik Kaplun    schedule 19.09.2015
comment
@ErikAllik ฉันต้องการวิธีแก้ปัญหาการทำงานที่สมบูรณ์โดยมีจิตวิญญาณเดียวกันกับตัวอย่างการทำงานที่เล็กที่สุด ฉันยังยกตัวอย่างการทำงานขั้นต่ำด้วย อีกเหตุผลหนึ่งคือความหายากของตัวอย่างการทำงานบนไซต์ Haskell ปัญหาที่ฉันถามวิธีแก้ปัญหาโดยทั่วไปเป็นเพียงตัวอย่างเล็กๆ น้อยๆ ซึ่งแทบจะไม่เพียงพอที่จะกล่าวหาฉันเกี่ยวกับโค้ดการจัดหาจากฝูงชน SO นั้นยอดเยี่ยมมาก และเอกสารของ Haskell ก็ไม่ดี นั่นคือสาเหตุที่ฉันต้องความต้องการนั้น ฉันขอโทษถ้ามันทำให้เจ็บปวด / ขุ่นเคืองหรือทำให้ใครบางคนเดือดร้อนเกินสมควร   -  person mntk123    schedule 19.09.2015
comment
ใช่ มันช้าเพราะ V.cons คือ O(n) คุณคาดหวังอะไร? ล้านคูณล้านนั้นเยอะมาก! vector มีเอกสารประกอบที่ดีอย่างสมบูรณ์ โปรดทราบว่าเอกสารสำหรับโมดูลแฮ็กโดยทั่วไปจะมีสารบัญอยู่ที่มุมขวาบน ตรวจสอบส่วนเกี่ยวกับการสร้างเวกเตอร์   -  person dfeuer    schedule 19.09.2015
comment
@dfeuer ฉันคาดว่ามันจะช้า แต่ฉันไม่รู้วิธีอื่นใดในการสร้างเวกเตอร์โดยการเพิ่มองค์ประกอบทีละน้อยในขณะที่ฉันอ่านค่าจากสตริงไบต์ ทำไมฉันถึงถามคำถาม: ฉันไม่เข้าใจวิธีการอื่นใดที่ให้ไว้กับแฮ็กเกอร์ที่ทำให้ฉันสามารถสร้างเวกเตอร์แบบค่อยเป็นค่อยไปได้ตามต้องการ   -  person mntk123    schedule 19.09.2015
comment
โดยพื้นฐานแล้วคุณ ไม่สามารถ สร้างเวกเตอร์โดยการเพิ่มเข้าไปทีละน้อย! พวกมันเหมือนกับอาร์เรย์ C หรือ C++ หรือเวกเตอร์ FORTRAN มากกว่าสิ่งที่ยืดหยุ่นที่สภาพแวดล้อมอื่นเรียกว่าเวกเตอร์ คุณสามารถรับสิ่งที่ยืดหยุ่นได้ใน Haskell หากคุณต้องการ แบบเดิมคือ Data.Sequence แต่ในช่วงนี้ Ed Kmett กำลังพัฒนาอันที่เร็วกว่าอยู่   -  person dfeuer    schedule 19.09.2015


คำตอบ (2)


ต่อไปนี้คือตัวอย่างบางส่วนของการสร้างเวกเตอร์จากไฟล์ พวกเขาไม่ได้มีประสิทธิภาพมากที่สุด แต่ทั้งคู่ทำงานใน ghci เพียงไม่กี่วินาที

module Main where

import qualified Data.ByteString.Lazy as BIN
import qualified Data.ByteString as BS
import qualified Data.Vector.Unboxed as Vec
import System.IO
import System.Posix

getFileSize :: String -> IO Int
getFileSize path = do
    stat <- getFileStatus path
    return (fromEnum $ fileSize stat)

readVector1 path = do
  size <- getFileSize path
  withBinaryFile path ReadMode $ \h -> do
    -- can also use: size <- hFileSize h
    let go _ = do bs <- BS.hGet h 2 
                  return (BS.index bs 0, BS.index bs 1)
    Vec.generateM (div size 2) go

pairs (a:b:rest) = (a,b) : pairs rest
pairs _          = []

readVector2 path = do
  contents <- BIN.readFile path
  -- unfoldr :: Unbox a => (b -> Maybe (a, b)) -> b -> Vector a
  let v = Vec.unfoldr go (pairs $ BIN.unpack contents)
        where go [] = Nothing
              go (p:ps) = Just (p, ps)
  return v

main = do
  v <- readVector1 "rand" -- large file
  print $ Vec.length v
  v <- readVector2 "rand"
  print $ Vec.length v

ทางเลือกที่สาม:

readVector3 path = do
  contents <- BS.readFile path
  let size = BS.length contents
      v = Vec.generate (div (fromIntegral size) 2) go
            where go i = let a = BS.index contents (2*i)
                             b = BS.index contents (2*i+1)
                         in (a,b)
  return v

อันนี้กลายเป็นเร็วที่สุดในทั้งสาม

person ErikR    schedule 19.09.2015
comment
โปรดทราบว่าการใช้ size <- hFileSize h อาจเป็นวิธีที่พกพาสะดวกกว่าในการรับขนาดไฟล์ และไม่จำเป็นต้องใช้ System.Posix - person ErikR; 19.09.2015
comment
ฟังก์ชัน readVector2 ใช้รายการและ readVector1 มีงานมากเกินไปใน IO - person mntk123; 19.09.2015
comment
ทั้งสองทำงานในเวลาน้อยกว่าหนึ่งวินาทีเมื่อคอมไพล์บนเครื่องของฉัน ของคุณใช้เวลานานเท่าไหร่? - person ErikR; 19.09.2015
comment
@ user5402: คุณเปลี่ยนแปลงไปเล็กน้อยเกี่ยวกับโค้ด - คุณช่วยอธิบายได้ไหมว่าการเปลี่ยนแปลงที่สำคัญที่สุดคืออะไรในการปรับปรุงประสิทธิภาพ (ฉันอยากรู้) - person Sam van Herwaarden; 19.09.2015
comment
ฉันคิดว่าประเด็นหลักคือ Vec.cons อาจไม่มีประสิทธิภาพ ตอนแรกฉันคิดว่าคุณควรบอก Vector ถึงความยาวที่คุณต้องการ - ดังนั้นเวอร์ชันแรก อย่างไรก็ตาม unfold ก็ใช้งานได้ค่อนข้างดีเช่นกัน ข้อแตกต่างที่สำคัญระหว่าง unfoldr และ cons ก็คือ unfoldr ต่อท้ายเวกเตอร์ซึ่งมีการดำเนินการที่เป็นธรรมชาติมากกว่า และด้วยเหตุนี้ ฉันคิดว่ามีประสิทธิภาพมากกว่า cons อย่างไรก็ตาม ฉันเชื่อว่าจะต้องเพิ่มเวกเตอร์ซึ่งไม่ใช่วิธีธรรมชาติในการเติบโตเวกเตอร์ - person ErikR; 19.09.2015
comment
ไม่ unfoldr ใช้เคล็ดลับการเพิ่มอาร์เรย์แบบดั้งเดิมเป็นสองเท่าเพื่อให้ได้เวลาตัดจำหน่าย O(1) ต่อ snoc แม้ว่าอาจต้องเพิ่มขนาดของอาร์เรย์เป็นสองเท่าหลายครั้ง โดยคัดลอกทั้งหมดทุกครั้ง - person dfeuer; 19.09.2015
comment
ใช่ - แต่ประเด็นของฉันคือดูเหมือนว่าจะ มาก ดีกว่าการใช้ cons - person ErikR; 19.09.2015
comment
ใช่แล้ว มันเป็นอย่างนั้นอย่างแน่นอน! แต่สามารถใช้เทคนิคเดียวกันนี้ในการเติมเวกเตอร์จากหลังไปหน้าแทนได้ cons ต้องคัดลอกเวกเตอร์ทั้งหมดทุกครั้ง unfoldr ช่วยตัวเองให้มีห้องเลื้อยเพื่อให้สามารถทำสำเนาเป็นครั้งคราวได้โดยไม่ทำให้เส้นกำกับแสดง การเติมหน้าและการต่อท้ายไม่เกี่ยวอะไรกับมัน - person dfeuer; 19.09.2015
comment
@ user5402 ฉันชอบและโหวตคำตอบของคุณ แต่สิ่งที่กวนใจฉันคือส่วน IO ใน readVector1 หากคุณให้รหัสสำหรับการอ่านเวกเตอร์ที่ใช้ ByteString (คุณสามารถค้นหาขนาดของมันได้) จากนั้นส่งคืนเวกเตอร์ที่เติมด้วยค่าที่ฉันจะยอมรับ ฉันกำลังดิ้นรนเพื่อให้ได้โค้ดประเภทนั้นโดยใช้โค้ดของคุณ แต่ไม่สามารถลบ IO ออกจากโค้ดได้ - person mntk123; 19.09.2015
comment
ตกลง - ดูพื้นที่นี้ - person ErikR; 19.09.2015
comment
ลองดูที่ readVector3 ปรับสูตรออฟเซ็ต 2*i, 2*i+1 และการกำหนดความยาว div size 2 เพื่อพิจารณาไบต์ส่วนหัวที่คุณต้องการข้ามไป - person ErikR; 19.09.2015
comment
@ user5402 ฉันยอมรับคำตอบนี้ ฉันไม่สามารถโหวตได้มากกว่าหนึ่งครั้ง คุณได้สอนฉันหลายสิ่งหลายอย่างผ่านคำตอบ - person mntk123; 19.09.2015

นี่เป็นอีกวิธีหนึ่งในการโหลดเวกเตอร์ ซึ่งใช้ pipes และ pipes-bytestring ในการสตรีมไฟล์ และ vector ฟังก์ชันจาก foldl เพื่อสร้างเวกเตอร์:

{-# LANGUAGE PackageImports #-}
import Data.Functor (void)
import "pipes" Pipes
import qualified "pipes" Pipes.Prelude as P
import qualified "pipes-bytestring" Pipes.ByteString as B
import qualified "pipes-binary" Pipes.Binary as B
import qualified "vector" Data.Vector.Unboxed as V
import qualified "foldl" Control.Foldl as L
import "lens-family-core" Lens.Family (view)
import System.IO

main :: IO ()
main = do
    v <- withBinaryFile "somefile" ReadMode (\h ->
        -- for simplicity, errors are ignored with "void"
        L.impurely P.foldM L.vector (void (view B.decoded (B.drop 1 (B.fromHandle h)))))
    print (V.length (v::V.Vector (B.Word8,B.Word8)))

cons ไม่มีประสิทธิภาพ แนวทางที่ดำเนินการโดย foldl's vector คือการเพิ่มความจุของเวกเตอร์เป็นสองเท่าอย่างต่อเนื่องโดยใช้ unsafeGrow เพื่อรองรับค่าที่เข้ามา และในตอนท้าย "ตัด" ความจุส่วนเกินด้วย unsafeTake

person danidiaz    schedule 19.09.2015
comment
มันเป็นวิธีแก้ปัญหาที่ยอดเยี่ยม - ฉันชอบมันมาก แต่เมื่อฉันเปรียบเทียบจะใช้เวลาประมาณ 2 วินาทีในการใช้ไฟล์ขนาด 1 MB ในขณะที่วิธีการอ่านครั้งละสองไบต์จะใช้เวลาประมาณ 0.1 วินาที นั่นคือสิ่งที่คุณกำลังเห็น? - person ErikR; 19.09.2015