Performance 为什么基于haskell枚举器的IO如此频繁地调用sigprocmask?
修订摘要 好的,看起来系统调用肯定与GC有关,而根本的问题是GC发生得太频繁了。这似乎与splitWhen和pack的使用有关,这是我通过分析得出的最好结论 将每个块从惰性文本转换为严格文本,并将它们全部连接起来,因为它建立了块的缓冲区。那肯定会分配很多 包,因为它正在从一种类型转换到另一种类型,必须分配,这在我的内部循环中,所以这也是有意义的 原版 在基于haskell枚举器的IO中,我偶然发现了一些令人惊讶的系统调用活动。希望有人能解释一下 我一直在玩弄haskell版本的快速perl脚本,我曾经编写了几个月,现在断断续续。脚本从每行读取一些json,然后打印出一个特定字段(如果存在) 这是perl版本,以及我如何运行它Performance 为什么基于haskell枚举器的IO如此频繁地调用sigprocmask?,performance,haskell,ghc,Performance,Haskell,Ghc,修订摘要 好的,看起来系统调用肯定与GC有关,而根本的问题是GC发生得太频繁了。这似乎与splitWhen和pack的使用有关,这是我通过分析得出的最好结论 将每个块从惰性文本转换为严格文本,并将它们全部连接起来,因为它建立了块的缓冲区。那肯定会分配很多 包,因为它正在从一种类型转换到另一种类型,必须分配,这在我的内部循环中,所以这也是有意义的 原版 在基于haskell枚举器的IO中,我偶然发现了一些令人惊讶的系统调用活动。希望有人能解释一下 我一直在玩弄haskell版本的快速perl脚本,
cat ~/sample_input | perl -lpe '($_) = grep(/type/, split(/,/))' > /dev/null
这里是haskell版本(它的调用方式与perl版本类似)
如果在这些sigsetmask之间读取的数据量很大,我首先猜测运行时正在gc运行之前执行sigsetmask,因此,gc不会因堆处于不一致状态而中断。多于一条注释,少于一个答案:如果您通过GHC源代码grep,您将看到
posix/TTY.c
(TERMIOS code)和sm/gc.c
(通过{,un}blockUserSignals
)最有可能的候选对象。您可以使用调试符号编译GHC,或者只抛出一些伪(唯一)系统调用,以确保您可以区分这两个系统调用配置文件来找到答案。另一个廉价的测试是移除任何终端交互,如果掩蔽行为消失,那么这将是支持GC的温和证据(none的答案)
编辑:我应该承认一些库代码也可以调用
sigprocmask
,我忽略了这一可能性较小的源代码,但实际上这可能是问题所在 通过评论将此提升到最高级别:
FWIW,我正在经历运行时(我们也在IRC中讨论这一点),sigprocmask只有两种用途:GC和tty驱动程序。后者不太可能,我建议进行分析以验证它是否执行了大量GC,并尝试找出原因
结果(来自IRC)它为0.5MB的数据分配了90MB的空间,垃圾收集器确实被触发了很多次。现在,我们来看看枚举器为什么要做这么多额外的分配。您是否关心分配或对sigprocmask调用的开销 如果是前者,并且您想要使用
枚举器
包,那么这个小小的更改将帮助4k测试集减少50%:8MB的分配减少到4MB,gen0 GC从15减少到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
之前(来自+RTS-sstderr-RTS
的统计数据):
堆中分配的8212680字节
GC期间复制的696184字节
148656字节的最大驻留空间(1个示例)
30664字节最大斜率
2 MB总内存在使用中(0 MB因碎片而丢失)
Tot时间(已用)平均暂停最大暂停
Gen 0 15 Cels,0个PAR 0.00 s 0.00 s 0.000 01S 0.000 05S
Gen 1 1 Cels,0 PAR 0.00 s 0.00 s 0.00 10S 0.00 10S
之后:
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
堆中分配的3838048字节
GC期间复制的689592字节
148368字节的最大驻留空间(1个示例)
27040字节最大斜率
2 MB总内存在使用中(0 MB因碎片而丢失)
Tot时间(已用)平均暂停最大暂停
Gen 0 6 Cels,0个PAR 0.00 s 0.00 s 0.000 01S 0.000 03S
Gen 1 1 Cels,0面值0.00 s 0.00 s 0.000 06S 0.000 06S
这是一个相当合理的改进,但肯定还有一些不尽如人意的地方。我并没有过多地使用枚举器,而是尝试在导管中重写它——0.4.1只是为了好玩。它应该相当于
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
导入数据。导管为C
将限定的Data.conductor.Binary作为Cb导入
导入符合条件的数据.conductor.List作为Cl
将限定的Data.conductor.Text作为Ct导入
导入符合条件的数据。文本为T
将限定的Data.Text.IO导入为TI
进口管制.Monad.Trans(MonadIO,liftIO)
导入系统。环境
导入System.IO(标准输入法)
grabField::Monad m=>String->导管T.Text m T.Text
grabbield=Cl.filter。T.isInfixOf。T.pack
printField::MonadIO m=>T.Text->m()
printField=liftIO$do
TI.putStrLn字段
putStr“\n\n”
fastSplit::Char->Bool
fastSplit','=真
fastSplit'\n'=真
fastSplit=错误
main::IO()
main=do
字段:\它不是一次读取很多数据。我想是4k还是8k?嗯,我想每个256k的分配都有一个“小”gc。也许中间结构实际上分配了这么多。另一个想法是sigsetmask可能会保护线程开关(GHC轻量级线程)的寄存器保存序列。这在Perl中是不会发生的,它使用Posix线程(如果它使用线程的话),afaik。不过我只是在猜测。我对GHC runtime.FWIW知之甚少,我正在经历运行时(我们也在IRC中讨论这个问题),而sigprocmask
只有两种用途:GC和tty驱动程序。后者不太可能,我建议评测以验证它是否在执行大量GC,并尝试找出原因。Perl实际上不执行线程(有ithreads
,但它非常可怕;相当于每个线程都有自己的独立Perl解释器),并且没有基于事件循环的输入管理器(因此,除非您使用的是POE或提供自己事件管理器的GUI库,否则不要选择。@geekosaur:I'm c
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
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
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
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