我们正在开发一个程序,它接收和转发"消息",同时保留这些消息的临时历史记录,以便它可以告诉您消息历史记录(如果请求).消息以数字方式标识,通常大小约为1千字节,我们需要保留数十万条这些消息.
我们希望优化此程序的延迟:发送和接收消息之间的时间必须低于10毫秒.
该程序是用Haskell编写的,并用GHC编译.但是,我们发现垃圾收集暂停对于我们的延迟要求来说太长了:在我们的实际程序中超过100毫秒.
以下程序是我们的应用程序的简化版本.它使用a Data.Map.Strict来存储消息.消息ByteString由a标识Int.以递增的数字顺序插入1,000,000条消息,并且不断删除最旧的消息以使历史记录最多保留200,000条消息.
module Main (main) where
import qualified Control.Exception as Exception
import qualified Control.Monad as Monad
import qualified Data.ByteString as ByteString
import qualified Data.Map.Strict as Map
data Msg = Msg !Int !ByteString.ByteString
type Chan = Map.Map Int ByteString.ByteString
message :: Int -> Msg
message n = Msg n (ByteString.replicate 1024 (fromIntegral n))
pushMsg :: Chan -> Msg -> IO Chan
pushMsg chan (Msg msgId msgContent) =
Exception.evaluate $
let
inserted = Map.insert …Run Code Online (Sandbox Code Playgroud) 即使是简单的小型Haskell程序也会变成巨大的可执行文件.
我已经编写了一个小程序,它被编译(用GHC)到二进制文件,大小扩展到7 MB!
甚至可以将一个小的Haskell程序编译成巨大的二进制文件?
如果有的话,我可以做些什么来减少这个?
如何找到在Haskell中存储某些数据类型值所需的实际内存量(主要是使用GHC)?是否可以在运行时(例如在GHCi中)对其进行评估,还是可以从其组件中估算复合数据类型的内存要求?
在一般情况下,如果类型的存储需求a和b已知的,什么是代数数据类型,如内存开销:
data Uno = Uno a
data Due = Due a b
Run Code Online (Sandbox Code Playgroud)
例如,这些值占用的内存中有多少字节?
1 :: Int8
1 :: Integer
2^100 :: Integer
\x -> x + 1
(1 :: Int8, 2 :: Int8)
[1] :: [Int8]
Just (1 :: Int8)
Nothing
Run Code Online (Sandbox Code Playgroud)
据我所知,由于垃圾收集延迟,实际的内存分配更高.由于惰性评估,它可能会有很大的不同(并且thunk大小与值的大小无关).问题是,给定数据类型,在完全评估时它的值会占用多少内存?
我发现:set +sGHCi中有一个选项可以查看内存统计信息,但目前尚不清楚如何估算单个值的内存占用量.
Haskell有一个标识函数,它返回输入不变.定义很简单:
id :: a -> a
id x = x
Run Code Online (Sandbox Code Playgroud)
所以为了好玩,这应该输出8:
f = id id id id id id id id id id id id id id id id id id id id id id id id id id id
main = print $ f 8
Run Code Online (Sandbox Code Playgroud)
几秒钟后(根据任务管理器大约2 GB的内存),编译失败了ghc: out of memory.同样,翻译说ghci: out of memory.
由于它id是一个非常简单的函数,我不希望它在运行时或编译时成为内存负担.什么是用于的内存?
我无法弄清楚为什么m1显然被记忆,而m2不在下面:
m1 = ((filter odd [1..]) !!)
m2 n = ((filter odd [1..]) !! n)
Run Code Online (Sandbox Code Playgroud)
m1 10000000在第一次调用时大约需要1.5秒,而后续调用需要一小部分(大概是缓存列表),而m2 10000000总是花费相同的时间(每次调用重建列表).知道发生了什么事吗?关于GHC是否以及何时会记忆功能,是否有任何经验法则?谢谢.
Prelude> show _
<interactive>:7:6:
Found hole ‘_’ with type: a0
Where: ‘a0’ is an ambiguous type variable
Relevant bindings include it :: String (bound at <interactive>:7:1)
In the first argument of ‘show’, namely ‘_’
In the expression: show _
In an equation for ‘it’: it = show _
Run Code Online (Sandbox Code Playgroud)
如果GHC还告诉我输入的孔具有Show类型类约束,那将是很好的.
GHC版本7.8.1
这段Haskell代码运行得慢得多-O,但-O应该是非危险的.谁能告诉我发生了什么?如果重要,它是尝试解决这个问题,它使用二进制搜索和持久段树:
import Control.Monad
import Data.Array
data Node =
Leaf Int -- value
| Branch Int Node Node -- sum, left child, right child
type NodeArray = Array Int Node
-- create an empty node with range [l, r)
create :: Int -> Int -> Node
create l r
| l + 1 == r = Leaf 0
| otherwise = Branch 0 (create l m) (create m r)
where m …Run Code Online (Sandbox Code Playgroud) 鉴于:
data Foo =
FooString String
…
class Fooable a where --(is this a good way to name this?)
toFoo :: a -> Foo
Run Code Online (Sandbox Code Playgroud)
我想做String一个实例Fooable:
instance Fooable String where
toFoo = FooString
Run Code Online (Sandbox Code Playgroud)
然后GHC抱怨:
Illegal instance declaration for `Fooable String'
(All instance types must be of the form (T t1 ... tn)
where T is not a synonym.
Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Fooable String'
Run Code Online (Sandbox Code Playgroud)
如果相反我使用[Char]: …
我写了一小部分Haskell来弄清楚GHC如何证明对于自然数,你只能将偶数的一半:
{-# LANGUAGE DataKinds, GADTs, KindSignatures, TypeFamilies #-}
module Nat where
data Nat = Z | S Nat
data Parity = Even | Odd
type family Flip (x :: Parity) :: Parity where
Flip Even = Odd
Flip Odd = Even
data ParNat :: Parity -> * where
PZ :: ParNat Even
PS :: (x ~ Flip y, y ~ Flip x) => ParNat x -> ParNat (Flip x)
halve :: ParNat Even -> Nat
halve PZ = Z
halve …Run Code Online (Sandbox Code Playgroud) 使用该-Wall选项编译我的Haskell应用程序时,GHC会抱怨孤立的实例,例如:
Publisher.hs:45:9:
Warning: orphan instance: instance ToSElem Result
Run Code Online (Sandbox Code Playgroud)
类型类ToSElem不是我的,它由HStringTemplate定义.
现在我知道如何解决这个问题(将实例声明移动到声明Result的模块中),我知道为什么GHC更愿意避免孤立的实例,但我仍然相信我的方式更好.我不在乎编译器是否带来不便 - 而不是我.
我想ToSElem在Publisher模块中声明我的实例的原因是因为它是依赖于HStringTemplate的Publisher模块,而不是其他模块.我试图保持关注点的分离,并避免让每个模块依赖于HStringTemplate.
我认为,与Java的接口相比,Haskell类型类的优点之一是它们是开放的而不是封闭的,因此实例不必在与数据类型相同的位置声明.GHC的建议似乎是忽略了这一点.
所以,我正在寻找的是要么认可我的想法是正确的,要么我有理由忽视/压制这个警告,或者更有说服力的反对我做事的做法.
ghc ×10
haskell ×10
typeclass ×2
compiler-bug ×1
glfw ×1
latency ×1
linker ×1
memoization ×1
optimization ×1
performance ×1
proof ×1
type-systems ×1
types ×1