Эффективное преобразование 64-битного Double в ByteString

Я написал функцию для преобразования 64-битного Double в ByteString (безопасность архитектуры / типов на самом деле не проблема - давайте пока предположим, что Double - это 64-битное слово) . Хотя приведенная ниже функция работает хорошо, мне интересно, есть ли более быстрый способ преобразовать 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

Пример вывода GHCi на Mac x86:

*Main> bprint $ encodeDouble 7234.4
"0x666666666642bc40"

Хотя код, кажется, работает хорошо, я планирую использовать его для кодирования множества значений Double в ByteString перед отправкой по IPC. Итак, я буду признателен за указатели, как сделать это быстрее, если таковые имеются.

Мне кажется, что 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 / data-binary-ieee754), пока эта ошибка не будет исправлена, или пока я не найду обходной путь. Похоже на GHC Ticket 4092 . Пожалуйста, поправьте меня, если это новая ошибка или другая ошибка. На данный момент я не могу его скомпилировать: (

EDIT2: Обновление кода для использования 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. Принуждение занимает ~ 6 нс. Unpack64 занимает ~ 195 нс. Распаковка word64 в виде списка word8 здесь довольно затратна.

6
задан Sal 2 December 2011 в 05:04
поделиться