Я написал функцию для преобразования 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
Пример вывода GHCi на Mac x86:
*Main> bprint $ encodeDouble 7234.4
"0x666666666642bc40"
Хотя код, кажется, работает хорошо, я планирую использовать его для кодирования множества значений Doubleв ByteString перед отправкой через IPC.Так что я буду признателен за подсказки, как сделать это быстрее, если таковые имеются.
Мне кажется, что 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 / 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. Принуждение занимает ~ 6ns. Unpack64 занимает ~ 195ns. Распаковка word64 как списка word8 вполнедорого здесь.