gat*_*ado 7 linux performance haskell
我想要一个能编写序列的程序,
1
...
10000000
Run Code Online (Sandbox Code Playgroud)
到一个文件.什么是最简单的代码可以编写,并获得不错的性能?我的直觉是存在一些缺乏缓冲的问题.我的C代码以100 MB/s的速度运行,而通过引用,Linux命令行实用程序dd
以9 GB/s 3 GB/s的速度运行(对不起,请注意 - 请注意 - 我对大图订单更感兴趣 - 虽然很高).
人们会认为这将是一个现在解决的问题...即任何现代编译器都会立即编写这样的程序,表现得相当好......
#include <stdio.h>
int main(int argc, char **argv) {
int len = 10000000;
for (int a = 1; a <= len; a++) {
printf ("%d\n", a);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我正在编译clang -O3
.调用putchar('\n')
8次的性能框架可获得相当的性能.
一个naiive Haskell实现以13 MiB/sec的速度运行,并使用ghc -O2 -optc-O3 -optc-ffast-math -fllvm -fforce-recomp -funbox-strict-fields
.(我没有重新编译我的库-fllvm
,也许我需要这样做.)代码:
import Control.Monad
main = forM [1..10000000 :: Int] $ \j -> putStrLn (show j)
Run Code Online (Sandbox Code Playgroud)
我最好用Haskell刺伤的速度更慢,达到17 MiB/sec.问题是我找不到一个很好的方法来转换Vector
成ByteString
s(也许有一个使用iteratees的解决方案?).
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed (Vector, Unbox, (!))
writeVector :: (Unbox a, Show a) => Vector a -> IO ()
writeVector v = V.mapM_ (System.IO.putStrLn . show) v
main = writeVector (V.generate 10000000 id)
Run Code Online (Sandbox Code Playgroud)
看起来编写ByteString
很快,正如这段代码所示,编写相同数量的字符,
import Data.ByteString.Char8 as B
main = B.putStrLn (B.replicate 76000000 '\n')
Run Code Online (Sandbox Code Playgroud)
它的速度为1.3 GB/s,速度不是很快dd
,但显然要好得多.
所有程序都使用默认优化级别编译(对于gcc为-O3,对于GHC为-O2)并运行
time ./prog > outfile
Run Code Online (Sandbox Code Playgroud)
作为基准,C程序需要1.07秒才能生成~76MB(78888897字节)的文件,吞吐量大约为70MB/s.
forM [1 .. 10000000] $ \j -> putStrLn (show j)
)耗时8.64秒,大约8.8MB/s.forM_
而不是forM
花了5.64s,大约13.5MB/s.ByteString
从dflemstr的回答版本了9.13s,约8.3MB /秒.Text
从dflemstr的回答版本了5.64s,约13.5MB /秒.Vector
问题的版本耗时5.54秒,约为13.7MB/s.main = mapM_ (C.putStrLn . C.pack . show) $ [1 :: Int .. 10000000]
,其中C
是Data.ByteString.Char8
,花了4.25s,约17.9MB /秒.putStr . unlines . map show $ [1 :: Int .. 10000000]
花了3.06s,大约24.8MB/s.手动循环,
main = putStr $ go 1
where
go :: Int -> String
go i
| i > 10000000 = ""
| otherwise = shows i . showChar '\n' $ go (i+1)
Run Code Online (Sandbox Code Playgroud)
耗时2.32秒,约32.75MB/s.
main = putStrLn $ replicate 78888896 'a'
花了1.15秒,大约66MB/s.main = C.putStrLn $ C.replicate 78888896 'a'
其中C
是Data.ByteString.Char8
,把0.143s,约530MB/s的,大致为懒惰同一附图ByteString
秒.首先,不要使用forM
或mapM
除非你真的想收集结果.表现,这很糟糕.
然后,ByteString
输出可以非常快(10.),但是如果ByteString
输出的结构很慢(3.),则最终的代码比天真String
输出慢.
3有什么可怕的?好吧,所有涉及String
的都很短.所以你得到一份清单
Chunk "1234567" Empty
Run Code Online (Sandbox Code Playgroud)
并且在任何两个这样的之间Chunk "\n" Empty
放置a,然后将结果列表连接起来,这意味着Empty
当... (Chunk "1234567" (Chunk "\n" (Chunk "1234568" (...))))
构建a时所有这些都被抛弃.这是很多浪费的构造 - 解构 - 重建正在进行中.可以通过严格的s和使用(以及换行)来实现Text
与固定的"天真" String
版本相当的速度.通过消除昂贵的单例,可以获得比6更好的性能.如果你将换行符粘贴到s上,而不是使用,则连接必须处理一半稍长的s,这会得到回报.pack
ByteString
fromChunks
Data.List.intersperse
String
\k -> shows k "\n"
show
ByteString
我对文本或向量的内部不够熟悉,提供的不仅仅是关于观察到的性能的原因的半教育猜测,所以我将它们排除在外.可以说,与固定的天真String
版本相比,性能增益最多是微不足道的.
现在,6表明ByteString
输出比String
输出快,足以在这种情况下额外的pack
输入工作得到补偿.但是,不要因为相信总是这样而被愚弄.如果String
包装很长,则包装可能比String
输出需要更多时间.
但千万调用putStrLn
,无论是String
或ByteString
版本,采取了很多的时间.抓取stdout
Handle
一次并String
在非IO代码中构造输出更快.unlines
已经做得很好,但我们仍然受到列表建设的影响map show [1 .. 10^7]
.不幸的是,编译器没有设法消除它(但它消除了[1 .. 10^7]
,这已经相当不错).所以让我们自己做,导致8.这不是太可怕,但仍然需要C程序的两倍多.
可以通过低级别直接填充ByteString
s而不通过String
via 来制作更快的Haskell程序show
,但我不知道C速度是否可达.无论如何,那个低级别的代码并不是很漂亮,所以我会饶恕你所拥有的东西,但是如果速度很重要,有时必须得到一个人的手.
归档时间: |
|
查看次数: |
496 次 |
最近记录: |