6

64 ビットの Double を ByteString に変換する関数を作成しました (アーキテクチャ/型の安全性は実際には問題ではありません。ここでは、Double が 64 ビットの Word であると仮定します)。以下の関数はうまく機能しますが、Double を ByteString に変換するより高速な方法があるかどうか疑問に思っています。以下のコードでは、Word64 を Word8 リストに 1 回アンパックした後、(リトル エンディアン形式にするために) リバースし、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 にパックする必要があるように思えます。したがって、全体的なアルゴリズムはそのままである可​​能性があり、あまり改善することはできません。ただし、より効率的な unpack/pack 関数を使用すると、おそらく違いが生じるでしょう。

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 チケット 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 のリストとしてアンパックするのは非常にコストがかかります。

4

3 に答える 3

2

ここでの使用unsafeCoerce#は危険であることに注意してください、とドキュメントは言います

ボックス化されていない型を同じサイズの別のボックス化されていない型にキャストする (ただし、浮動小数点型と整数型の間の強制は除く)

速度的には、中間リストを避けてunsafeCreatefrom経由で直接メモリに書き込んだ方が早いかもしれませんData.ByteString.Internal

于 2011-12-02T02:08:13.280 に答える
1

acfoltzer (シリアル ソース コード) と Daniel Fischer (unsafeCreate) の提案に従って、以下のコードを書きました。これは私のユース ケースに適していて、高速でもあります。

{-#LANGUAGE MagicHash #-}
import Data.ByteString (pack, unpack)
import Data.ByteString.Internal (unsafeCreate,ByteString)
import Data.Bits (shiftR)
import GHC.Int (Int64)
import GHC.Prim
import GHC.Types
import GHC.Word
import Unsafe.Coerce
import Criterion.Main
import Foreign

-- | Write a Word64 in little endian format
putWord64le :: Word64 -> Ptr Word8 -> IO()
putWord64le w p = do
  poke p               (fromIntegral (w)           :: Word8)
  poke (p `plusPtr` 1) (fromIntegral (shiftR w  8) :: Word8)
  poke (p `plusPtr` 2) (fromIntegral (shiftR w 16) :: Word8)
  poke (p `plusPtr` 3) (fromIntegral (shiftR w 24) :: Word8)
  poke (p `plusPtr` 4) (fromIntegral (shiftR w 32) :: Word8)
  poke (p `plusPtr` 5) (fromIntegral (shiftR w 40) :: Word8)
  poke (p `plusPtr` 6) (fromIntegral (shiftR w 48) :: Word8)
  poke (p `plusPtr` 7) (fromIntegral (shiftR w 56) :: Word8)

{-# INLINE putWord64le #-}

encodeDouble :: Double -> ByteString
encodeDouble x = unsafeCreate 8 (putWord64le $ unsafeCoerce x)

main :: IO ()
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.80361 ns (35 iterations)
found 5 outliers among 35 samples (14.3%)
  3 (8.6%) high mild
  2 (5.7%) high severe

benchmarking encodeDouble/78901.234
mean: 18.80689 ns, lb 18.73805 ns, ub 18.97247 ns, ci 0.950
std dev: 516.7499 ps, lb 244.8588 ps, ub 1.043685 ns, ci 0.950

benchmarking encodeDouble/789.01
mean: 18.96963 ns, lb 18.90986 ns, ub 19.06127 ns, ci 0.950
std dev: 374.2191 ps, lb 275.3313 ps, ub 614.4281 ps, ci 0.950

~220ns から ~19ns まで、素晴らしいです! 私はコンパイルで何も派手なことをしませんでした。GHC7 (Mac, x86_64) では -O フラグだけで十分です。

今、ダブルスのリストでそれを速くする方法を見つけようとしています!

于 2011-12-03T19:28:54.740 に答える