我编写了一个将64位Double转换为ByteString的函数(体系结构/类型安全并不是真正的问题-现在让我们假设Double是64位Word)。虽然下面的函数运行良好,但我想知道是否有更快的方法将Double转换为ByteString。在下面的代码中,将Word64的一个包解压缩到Word8列表中,然后进行反向(使其变为小端格式),然后将其打包成ByteString。代码如下:
{-# LANGUAGE MagicHash #-}
import GHC.Prim
import GHC.Types
import GHC.Word
import Data.Bits (shiftR)
import Data.ByteString (pack, unpack)
import Data.ByteString.Internal (ByteString)
import Text.Printf (printf)
encodeDouble :: Double -> ByteString
encodeDouble (D# x) = pack $ reverse $ unpack64 $ W64# (unsafeCoerce# x)
unpack64 :: Word64 -> [Word8]
unpack64 x = map (fromIntegral.(shiftR x)) [56,48..0]
-- function to convert list of bytestring into hex digits - for debugging
bprint :: ByteString -> String
bprint x = ("0x" ++ ) $ foldl (++) "" $ fmap (printf "%02x") $ unpack x
main = putStrLn $ bprint $ encodeDouble 7234.4
Mac x86上的示例GHCi输出:
*Main> bprint $ encodeDouble 7234.4
"0x666666666642bc40"
尽管代码似乎运行良好,但我计划在通过IPC发送之前将其用于将许多Double值编码为ByteString。因此,如果有的话,我将感谢您提出使其速度更快的建议。
在我看来,必须将double拆包到Word8中,然后再打包到ByteString中。因此,可能是整体算法,不能在很大程度上改进。但是,如果有的话,使用更高效的拆包/打包功能可能会有所作为。
编辑1:
我刚刚在Mac(GHC 7.0.3)上发现了另一个复杂问题-由于此错误,以上代码无法在GHC中编译-到目前为止,我仍在GHCi中进行测试:
$ ghc -O --make t.hs
[1 of 1] Compiling Main ( t.hs, t.o )
/var/folders/_q/33htc59519b3xq7y6xv100z40000gp/T/ghc6976_0/ghc6976_0.s:285:0:
suffix or operands invalid for `movsd'
/var/folders/_q/33htc59519b3xq7y6xv100z40000gp/T/ghc6976_0/ghc6976_0.s:304:0:
suffix or operands invalid for `movsd'
因此,似乎我不得不依靠FFI( Cereal /数据二进制-ieee754软件包),直到此错误得到修复或找到解决方法为止。看起来和GHC Ticket 4092有关。如果是新错误或其他错误,请纠正我。现在,我不能编译它:(
编辑2:
更新代码以使用unsafeCoerce可解决编译问题。下面的代码具有Criterion基准测试:
{-# LANGUAGE MagicHash #-}
import GHC.Prim
import GHC.Types
import GHC.Word
import Data.Bits (shiftR)
import Data.ByteString (pack, unpack)
import Data.ByteString.Internal (ByteString)
import Text.Printf (printf)
import Unsafe.Coerce
import Criterion.Main
--encodeDouble :: Double -> ByteString
encodeDouble x = pack $ reverse $ unpack64 $ unsafeCoerce x
unpack64 :: Word64 -> [Word8]
unpack64 x = map (fromIntegral.(shiftR x)) [56,48..0]
main = defaultMain [
bgroup "encodeDouble" [
bench "78901.234" $ whnf encodeDouble 78901.234
, bench "789.01" $ whnf encodeDouble 789.01
]
]
标准输出(被截断):
estimating cost of a clock call...
mean is 46.09080 ns (36 iterations)
benchmarking encodeDouble/78901.234
mean: 218.8732 ns, lb 218.4946 ns, ub 219.3389 ns, ci 0.950
std dev: 2.134809 ns, lb 1.757455 ns, ub 2.568828 ns, ci 0.950
benchmarking encodeDouble/789.01
mean: 219.5382 ns, lb 219.0744 ns, ub 220.1296 ns, ci 0.950
std dev: 2.675674 ns, lb 2.197591 ns, ub 3.451464 ns, ci 0.950
经过进一步分析,大多数瓶颈似乎在unpack64中。强制大约需要6ns。 unpack64大约需要195ns。将word64解压缩为word8的列表在这里非常昂贵。
最佳答案
文档说,请注意在这里使用unsafeCoerce#
是危险的
关于速度,避免中间列表并通过unsafeCreate
中的Data.ByteString.Internal
直接写入内存可能更快。
关于haskell - 有效地将64位Double转换为ByteString,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8350814/