2012-04-23 24 views
7

SỬA ĐỔI TÓM TẮTTại sao IO dựa trên điều tra viên gọi là sigprocmask thường xuyên như vậy?

Alright, nó trông giống như syscalls chắc chắn liên quan đến GC, và vấn đề cơ bản chỉ là GC đang xảy ra quá thường xuyên. Điều này dường như có liên quan đến việc sử dụng splitWhen và gói, như là tốt nhất tôi có thể nói bằng cách profiling.

splitWhen's implementation chuyển đổi từng đoạn văn từ văn bản lười sang văn bản nghiêm ngặt và ghép tất cả chúng ra, vì nó xây dựng bộ đệm khối. Đó là ràng buộc để phân bổ rất nhiều.

gói, vì nó chuyển đổi từ loại này sang loại khác, phải phân bổ và đó là vòng lặp bên trong của tôi, do đó cũng có ý nghĩa.

ORIGINAL CẤP

tôi đã vấp vào một số hoạt động syscall đáng ngạc nhiên trong Haskell Enumerator dựa IO. Hy vọng ai đó có thể làm sáng tỏ nó.

Tôi đã từng đùa giỡn với phiên bản haskell của một tập lệnh perl nhanh mà tôi đã từng viết trong một vài tháng, và tắt. Kịch bản lệnh đọc trong một số json từ mỗi dòng, và sau đó in ra một trường cụ thể, nếu nó tồn tại.

Đây là phiên bản perl và cách tôi đang chạy phiên bản đó.

cat ~/sample_input | perl -lpe '($_) = grep(/type/, split(/,/))' > /dev/null 

Đây là phiên bản haskell (được gọi tương tự như phiên bản perl).

{-# LANGUAGE OverloadedStrings #-} 
import qualified Data.Enumerator as E 
import qualified Data.Enumerator.Internal as EI 
import qualified Data.Enumerator.Text as ET 
import qualified Data.Enumerator.List as EL 
import qualified Data.Text as T 
import qualified Data.Text.IO as TI 
import Data.Functor 
import Control.Monad 
import qualified Data.Text.Lazy as TL 
import qualified Data.Text.Lazy.IO as TLI 
import System.Environment 
import System.IO (stdin, stdout) 
import GHC.IO.Handle (hSetBuffering, BufferMode(BlockBuffering)) 

fieldEnumerator field = enumStdin E.$= splitOn [',','\n'] E.$= grabField field 

enumStdin = ET.enumHandle stdin 

splitOn :: [Char] -> EI.Enumeratee T.Text T.Text IO b 
splitOn chars = (ET.splitWhen (`elem` chars)) 

grabField :: String -> EI.Enumeratee T.Text T.Text IO b 
grabField = EL.filter . T.isInfixOf . T.pack 

intercalateNewlines = EL.mapM_ (\field -> (TI.putStrLn field >> (putStr "\n\n"))) 

runE enum = E.run_ $ enum E.$$ intercalateNewlines 

main = do 
    (field:_) <- getArgs 
    runE $ fieldEnumerator field 

Điều ngạc nhiên là dấu vết của phiên bản haskell trông giống như thế này (JSON thực tế bị chặn vì dữ liệu từ cơ quan), trong khi phiên bản perl làm những gì tôi mong đợi; một loạt các lần đọc tiếp theo là viết, lặp đi lặp lại.

55333/0x8816f5: 366125  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 366136  3  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 367209  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 367218  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 368449  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 368458  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 369525  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 369534  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 370610  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 370620  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 371735  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 371744  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 371798  5  2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)  = 1 0 
55333/0x8816f5: 371802  3  1 read(0x0, SOME_JSON, 0x1FA0)  = 8096 0 
55333/0x8816f5: 372907  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 372918  3  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 374063  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 374072  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 375147  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 375156  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 376283  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 376292  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 376809  6  2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)  = 1 0 
55333/0x8816f5: 376814  5  3 read(0x0, SOME_JSON, 0x1FA0)  = 8096 0 
55333/0x8816f5: 377378  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 377387  3  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 378537  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 378546  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 379598  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 379604  3  0 sigreturn(0x7FFF5FBFF9A0, 0x1E, 0x1)  = 0 Err#-2 
55333/0x8816f5: 379613  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 380667  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 380678  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 381862  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 381871  3  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 382032  6  2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)  = 1 0 
55333/0x8816f5: 382036  4  2 read(0x0, SOME_JSON, 0x1FA0)  = 8096 0 
55333/0x8816f5: 383064  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 383073  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 384118  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 384127  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 385206  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 385215  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 386348  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 386358  3  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 387468  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 387477  11  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 387614  6  2 select(0x1, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)  = 1 0 
55333/0x8816f5: 387620  5  3 read(0x0, SOME_JSON, 0x1FA0)  = 8096 0 
55333/0x8816f5: 388597  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 388606  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 389707  3  0 sigprocmask(0x1, 0x10069BFA8, 0x10069BFAC)  = 0x0 0 
55333/0x8816f5: 389716  2  0 sigprocmask(0x3, 0x10069BFAC, 0x0)  = 0x0 0 
55333/0x8816f5: 390261  7  3 select(0x2, 0x7FFF5FBFBA70, 0x7FFF5FBFB9F0, 0x0, 0x7FFF5FBFBAF0)  = 1 0 
55333/0x8816f5: 390273  6  3 write(0x1, SOME_OUTPUT, 0x1FA0)  = 8096 0 

Trả lời

7

Bạn có lo lắng về việc phân bổ hoặc cuộc gọi (trên không?) Tới sigprocmask không?

Nếu đó là cũ và bạn muốn sử dụng gói enumerator thay đổi nhỏ này giúp ra một thử nghiệm 4k do khoảng 50%: 8MB phân bổ giảm xuống còn 4MB và gen0 GC của đi từ 15 đến 6.

splitOn :: EI.Enumeratee T.Text T.Text IO b 
splitOn = EL.concatMap (T.split fastSplit) 

fastSplit :: Char -> Bool 
fastSplit ',' = True 
fastSplit '\n' = True 
fastSplit _ = False 

Trước (số liệu thống kê từ +RTS -sstderr -RTS):

 
     8,212,680 bytes allocated in the heap 
     696,184 bytes copied during GC 
     148,656 bytes maximum residency (1 sample(s)) 
      30,664 bytes maximum slop 
       2 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0  15 colls,  0 par 0.00s 0.00s  0.0001s 0.0005s 
    Gen 1   1 colls,  0 par 0.00s 0.00s  0.0010s 0.0010s 

Sau:

 
     3,838,048 bytes allocated in the heap 
     689,592 bytes copied during GC 
     148,368 bytes maximum residency (1 sample(s)) 
      27,040 bytes maximum slop 
       2 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0   6 colls,  0 par 0.00s 0.00s  0.0001s 0.0003s 
    Gen 1   1 colls,  0 par 0.00s 0.00s  0.0006s 0.0006s 

Đó là một cải tiến khá hợp lý nhưng chắc chắn để lại một cái gì đó để được mong muốn. Thay vì đá điều tra xung quanh quá nhiều tôi đã đâm một lúc viết lại nó trong conduit-0.4.1 chỉ cho đá. Nó phải là tương đương ...

import Data.Conduit as C 
import qualified Data.Conduit.Binary as Cb 
import qualified Data.Conduit.List as Cl 
import qualified Data.Conduit.Text as Ct 
import qualified Data.Text as T 
import qualified Data.Text.IO as TI 
import Control.Monad.Trans (MonadIO, liftIO) 
import System.Environment 
import System.IO (stdin) 

grabField :: Monad m => String -> Conduit T.Text m T.Text 
grabField = Cl.filter . T.isInfixOf . T.pack 

printField :: MonadIO m => T.Text -> m() 
printField field = liftIO $ do 
    TI.putStrLn field 
    putStr "\n\n" 

fastSplit :: Char -> Bool 
fastSplit ',' = True 
fastSplit '\n' = True 
fastSplit _ = False 

main :: IO() 
main = do 
    field:_ <- getArgs 
    runResourceT $ Cb.sourceHandle stdin 
       $$ Ct.decode Ct.utf8 
       =$ Cl.concatMap (T.split fastSplit) 
       =$ grabField field 
       =$ Cl.mapM_ printField 

... nhưng đối với một số giao đất lý trí và giữ lại ít bộ nhớ:

 
     835,688 bytes allocated in the heap 
      8,576 bytes copied during GC 
      87,200 bytes maximum residency (1 sample(s)) 
      19,968 bytes maximum slop 
       1 MB total memory in use (0 MB lost due to fragmentation) 

            Tot time (elapsed) Avg pause Max pause 
    Gen 0   1 colls,  0 par 0.00s 0.00s  0.0000s 0.0000s 
    Gen 1   1 colls,  0 par 0.00s 0.00s  0.0008s 0.0008s 
+0

Tôi lo ngại về chi phí không phải là những gì tôi muốn. Vì vậy, bao gồm sigprocmask và GC. Tôi sẽ xem xét hai cải tiến này sau khi tôi đi làm về. Cảm ơn câu trả lời. =] – tehgeekmeister

4

Nếu số lượng dữ liệu đọc giữa những sigsetmasks là lớn, đoán đầu tiên ra khỏi đỉnh đầu của tôi là thời gian chạy là làm sigsetmask trước khi chạy gc, vì vậy gc mà không bị gián đoạn với đống trong trạng thái không nhất quán.

+0

Nó không đọc dữ liệu rất nhiều trong cùng một lúc. Tôi nghĩ 4k hay 8k? – tehgeekmeister

+0

.. và các lần đọc sẽ hiển thị trong các cuộc gọi hệ thống. – sarnold

+0

Hmm, có một "nhỏ" gc mỗi 256k phân bổ, tôi nghĩ. Có lẽ với các cấu trúc trung gian mà nhiều thực sự đang được phân bổ. Một ý tưởng khác là có thể sigsetmask bảo vệ chuỗi lưu trữ đăng ký cho một công tắc luồng (các chủ đề nhẹ GHC). Điều đó sẽ không xảy ra trong Perl, trong đó sử dụng chủ đề Posix nếu nó sử dụng chủ đề ở tất cả, afaik. Tôi chỉ đoán thôi. Tôi không thực sự biết nhiều về thời gian chạy GHC. – none

3

Nhiều hơn một nhận xét và ít hơn câu trả lời: nếu bạn grep thông qua nguồn GHC, bạn sẽ thấy posix/TTY.c (mã TERMIOS) và sm/GC.c (qua {,un}blockUserSignals) có nhiều ứng viên nhất. Bạn có thể biên dịch GHC với các biểu tượng gỡ lỗi hoặc chỉ cần ném vào một số cuộc gọi hệ thống giả (duy nhất) để đảm bảo bạn có thể phân biệt hai cấu hình cuộc gọi hệ thống để tìm hiểu. Một thử nghiệm giá rẻ khác là loại bỏ bất kỳ tương tác đầu cuối nào và nếu hành vi mặt nạ biến mất thì đó sẽ là bằng chứng nhẹ hỗ trợ GC (không có câu trả lời).

EDIT: Tôi nên thừa nhận rằng một số mã thư viện cũng có thể gọi số sigprocmask, tôi đã bỏ qua đó là một nguồn ít có khả năng hơn nhưng thực sự có thể là vấn đề!

7

Thúc đẩy này đến cấp cao nhất từ ​​nhận xét:

FWIW, tôi đang trải qua thời gian chạy (chúng tôi cũng đang thảo luận này trong IRC) và chỉ có hai công dụng của sigprocmask: GC và trình điều khiển tty. Thứ hai là không, tôi đã đề nghị profiling để xác minh rằng nó đang làm rất nhiều GC và cố gắng tìm hiểu lý do tại sao.

Và hóa ra (từ IRC) rằng nó đang thực hiện 90MB phân bổ cho 0,5MB dữ liệu và bộ thu gom rác thực sự được kích hoạt khá nhiều. Vì vậy, bây giờ nó xuống đến lý do tại sao điều tra viên đang làm rất nhiều phân bổ thêm.

Các vấn đề liên quan