gpt4 book ai didi

haskell - 高效地将 64 位 Double 转换为 ByteString

转载 作者:行者123 更新时间:2023-12-04 15:03:05 25 4
gpt4 key购买 nike

我编写了一个将 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 中。所以,可能是整体算法,不能改进太多。但是,如果有的话,使用更有效的解包/打包功能可能会有所作为。

EDIT1:
我刚刚在 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 相关.如果这是一个新错误或不同的错误,请纠正我。目前,我无法编译它:(

EDIT2:
更新代码以使用 unsafeCoerce 修复了编译问题。下面的代码与标准基准:
{-# 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 列表非常昂贵。

最佳答案

我最近在 cereal 中添加了对 IEEE-754 浮点数的支持,您可以找到 binary 的类似函数在 data-binary-ieee754 .这是一个使用 cereal 的示例往返版本piByteString然后回来:

Prelude Data.Serialize> runGet getFloat64be $ runPut $ putFloat64be pi
Right 3.141592653589793

它使用 ST 数组的技巧来快速进行转换;见 this earlier question更多细节。

更新:D'oh,我应该知道如何使用我贡献给图书馆的调用......

更新 x2:关于编译失败,我不认为这是一个错误。

我没有仔细查看此特定代码的生成程序集,但是 movsd 的操作数指令被弄乱了。来自 Intel x86 manual 的§11.4.1.1 :

The MOVSD (move scalar double-precision floating-point) transfers a 64-bit double-precision floating-point operand from memory to the low quadword of an XMM register or vice versa, or between XMM registers.



在未优化的代码中,您有像 movsd LnTH(%rip),%xmm0 这样的精细指令。 , 但在 -O代码,你会看到像 movsd Ln2cJ(%rip),%rax 这样的东西, 其中 %rax是通用寄存器,而不是 XMM 寄存器。

优化器可能会根据所涉及的数据类型对它需要在寄存器之间移动的数据表示进行假设。 unsafeCoerce和 friend 们使这些假设无效,因此当指令选择器认为它正在为 D# 选择正确的操作时,它实际上是在发出试图填充 D# 的代码。在哪里 W64#会很适合。

由于处理这个问题需要优化器放弃在正常情况下让它发出更好代码的许多假设,我倾向于说这不是一个错误,而是一个很好的故事,为什么 unsafe函数带有警告购买者警告。

关于haskell - 高效地将 64 位 Double 转换为 ByteString,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8350814/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com