我正在为我创建的数据类型编写一个“附加”函数(基本上处理“流”)。但是,这个数据类型有12个不同的构造函数,处理不同类型的“流”,例如,无限,空,固定长度,可变长度,已经附加等。
输入类型和输出类型之间的逻辑有点复杂但不是那么令人难以置信。
我考虑了两种方法:
我知道第二种方法更难以维护,但忽视这一点,GHC会发现第二种方法更容易优化吗?如果它可以使用简单的跳转表(或者可能是两个跳转表)进行第二种方法,我怀疑它会更快。但是,如果它正在进行线性检查,那将会慢得多。
GHC是否将模式匹配(甚至非常大的匹配)优化为恒定时间跳转表?
答案 0 :(得分:6)
是的,GHC优化了这种模式匹配。前七个(我认为)构造函数通过指针标记得到优化。我相信其余的将由跳桌来处理。但144个案例听起来很难维护,你必须注意代码大小。你真的需要所有这些案件吗?
答案 1 :(得分:2)
编写一个小的Haskell脚本编写一个巨大的case-block和一个小的基准测试并不难。例如:
module Main (main) where
mapping = zip ['!'..'z'] (reverse ['!'..'z'])
test_code =
[
"module Main where",
"",
"tester :: String -> String",
"tester cs = do",
" c <- cs",
" case transform c of",
" Just c' -> [c']",
" Nothing -> [c ]",
"",
"input = concat [ [' '..'z'] | x <- [1..10000] ]",
"",
"main = print $ length $ tester $ input",
""
]
code1 =
test_code ++
[
"transform :: Char -> Maybe Char",
"transform c = lookup c " ++ show mapping
]
code2 =
test_code ++
[
"transform :: Char -> Maybe Char",
"transform c =",
" case c of"
] ++
map (\(k, v) -> " " ++ show k ++ " -> Just " ++ show v) mapping ++
[
" _ -> Nothing"
]
main = do
writeFile "Test1.hs" (unlines code1)
writeFile "Test2.hs" (unlines code2)
如果您运行此代码,它会生成两个小的Haskell源文件:Test1.hs
和Test2.hs
。前者使用Prelude.lookup
将字符映射到字符。后者使用了一个巨大的案例块。这两个文件都包含将映射应用于大型数据列表并打印出结果大小的代码。 (这样可以避免I / O,否则I / O将成为主导因素。)在我的系统上,Test1
需要几秒钟才能运行,而Test2
几乎是即时的。
过度感兴趣的读者可能会尝试将其扩展为使用Data.Map.lookup
并比较速度。
这证明模式匹配比键/值映射列表的O(n)遍历要快得多......这不是你提出的问题。但随意制定自己的基准测试。您可以尝试自动生成嵌套案例与平面案例并对结果进行计时。我猜测是你不会发现很多不同之处,但可以随意尝试。