簡體   English   中英

為什么replicateM (length xs) m 比sequenceA (fmap (const m) xs) 更有效?

[英]Why is replicateM (length xs) m way more efficient than sequenceA (fmap (const m) xs)?

我提交的兩個編程問題的不同之處僅在於一個表達式(其中anchors是一個非空列表,而(getIntegrals n)是一個狀態單子):

提交 1 . replicateM (length anchors - 1) (getIntegrals n)

提交 2 sequenceA $ const (getIntegrals n) <$> tail anchors

我猜這兩個表達式的等價性在編譯時應該很容易看出。 然而,相比之下sequenceA更慢,更重要的是,它占用 > 10 倍的內存:

代碼 時間 記憶
復制一 732 毫秒 22200 KB
序列一 1435 毫秒 262100 KB

(第二個條目出現“測試 4 時超出內存限制”錯誤,因此情況可能更糟)。

為什么會這樣?

很難預測哪些優化是自動的,哪些不是!

編輯:按照建議,粘貼下面的提交 1代碼。 在這個交互式問題中,“服務器”有一個大小為n的隱藏樹。 我們的代碼的工作是找出那棵樹,用最少數量的表單查詢? k ? k 粗略地說,服務器對? k ? k是樹的 鄰接 距離矩陣中節點k對應的行。 我們對k選擇是:最初是1 ,然后是從getAnchors獲得的一堆節點。

{-# LANGUAGE Safe #-}
{-# OPTIONS_GHC -O2 #-}

import Data.Maybe
import qualified Data.ByteString.Lazy.Char8 as B8
import qualified Data.ByteString.Builder as Bu
import Data.Functor.Identity
import Control.Monad.Trans.State
import Control.Monad
import Control.Applicative
import Data.ByteString.Builder.Extra (flush) 
import System.IO

type St = StateT [B8.ByteString] Identity

solve :: St Bu.Builder
solve = do
  n <- getIntegral
  ds <- getIntegrals n  -- get the first row of adjacency matrix
  let
    anchors = getAnchors ds
    readFirst = if head anchors==1 then return ds else getIntegrals n
    readRest = replicateM (length anchors - 1) (getIntegrals n) -- get some other rows too
  adjss <- liftA2 (:) readFirst readRest  
  let
    adj1ss = [map snd $ filter ((1==).fst) (zip adjs [1..]) | adjs <- adjss]
    s0 = Bu.string7
    snl = Bu.string7 "\n" <> flush
    i0 = Bu.intDec
    printEdge src dst = i0 src <> s0 " " <> i0 dst <> snl
    printAdj (src,dsts) = mconcat [printEdge src dst | dst<-dsts]
    printAdjs = mconcat $ printAdj <$> zip anchors adj1ss
    ask k = s0 "? " <> i0 k <> snl
    askRest = mconcat $ ask <$> (dropWhile (==1) anchors)
  return $ ask 1 <> askRest <> s0 "!" <> snl <> printAdjs

getAnchors :: [Int]->[Int]
getAnchors xs = reverse $ go (zip xs [1..]) [] [] where
  go [] odds evens = if length odds < length evens then odds else evens
  go ((k,i):rest) odds evens
    | even k = go rest odds (i: evens)
    | odd k = go rest (i: odds) evens
 
getByteString :: St B8.ByteString
getByteString = state getNext where
  getNext [] =  (B8.take 0 (B8.pack "."),[])
  getNext (w:ws) =  (w,ws)
 
getIntegral :: Num t => St t
getIntegral  = convertToNum <$> getByteString where
  convertToNum x =  fromIntegral $ fromMaybe 0 $ liftA fst $ B8.readInteger x
 
getIntegrals :: Num t => Int -> St [t]
getIntegrals n = replicateM n getIntegral

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  bytestrings <- B8.words <$> B8.getContents
  B8.putStr $ Bu.toLazyByteString $ evalState solve bytestrings

這里的問題與內聯有關。 我不完全理解它,但這是我的理解。

內聯

首先,我們發現將replicateM定義復制並粘貼到Submission 1 會產生與Submission 2 ( submit ) 相同的糟糕性能。 然而,如果我們用INLINABLE pragma 替換replicateMNOINLINE pragma,事情NOINLINE起作用(提交)。

INLINABLE對編譯replicateM是從不同的INLINE編譯,后者導致比前者更聯處理。 具體來說,如果我們在同一個文件中定義了replicateM ,那么Haskells heuristic for inlining 決定內聯,但是使用replicateM from base 在這種情況下,即使存在INLINABLE pragma,它也決定不進行內聯。

另一方面, sequenceAtraverse都有導致內聯的INLINE編譯指示。 從上面的實驗中得到一個提示,我們可以定義一個不可內聯的sequenceA A,這確實使解決方案 2 起作用(提交)。

{-# NOINLINE sequenceA' #-}
sequenceA' :: [St x] -> St [x]
sequenceA' = sequenceA

出了什么問題?

以下是我的一些非常嚴重的猜測。

但是內聯是如何引起問題的呢? 好吧,讓我們看看以下兩個核心轉儲之間的區別

內聯:
帶內聯

沒有內聯:
沒有內聯

在這里,我們兩次查看對應的內容,第一個實例是內聯部分,第二個實例是對replicateM 的實際調用。

readRest = replicateM (length anchors - 1) (getIntegrals n)

現在有趣的一點是,在內聯代碼中,黃色突出顯示的行在replicateM每個循環中運行,而在非內聯部分,它們在傳遞給replicateM的lambda 抽象之外計算一次。

但他們在做什么? 核心中有多個變量叫ds ,但是這個是指這個:

在此處輸入圖片說明

這又對應於

solve = do
  n <- getIntegral

所以我認為正在發生的事情是,它不是運行getIntegral一次並保存結果,而是保存它的起始狀態,並在每次循環時以該狀態重新運行。 確實將這一行更改為以下內容(需要 BangPatterns 語言擴展)修復所有版本(提交)。

solve = do
  !n <- getIntegral

我仍然不確定,但這是我最好的猜測。

這里有兩個核心轉儲供參考: Inline , Noinline

這太瘋狂了

嗯,是的,但我覺得這里的潛在問題是你使用了惰性 IO 和惰性狀態。 使用嚴格狀態轉換器 Haskell 可能會想出不保留舊狀態(我不知道,只是猜測),但是我們不能在這里使用嚴格狀態,因為您依賴於惰性 IO,即獲取所有輸入一開始使用getContents並懶惰地強制它,同時確保在強制太多之前提供輸出。 相反,逐行顯式讀取輸入會更安全。 StateT [ByteString]替換為IO或更花哨的東西,例如ConduitPipe

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM