С точки зрения разработчика библиотеки, способ отладки состоит в том, чтобы создать оболочку для подозрительной операции, а затем посмотреть на основной код, чтобы убедиться, что слияние сработало.
-- Main.hs ---------------------------------------------------
import Solver
import Data.Array.Repa.IO.BMP
main
= do Right img <- readImageFromBMP "whatever.bmp"
print $ cumsumBMP img
-- Solver.hs --------------------------------------------------
{-# LANGUAGE TypeOperators, FlexibleContexts, TypeFamilies #-}
module Solver (cumsumBMP) where
import Data.Array.Repa as Repa
import Data.Word
{- all your defs -}
{-# NOINLINE cumsumBMP #-}
cumsumBMP :: Array DIM3 Word8 -> Array DIM3 Word8
cumsumBMP img = cumsum $ transpose img
Я поставил«решающий» код в отдельном модуле, поэтому нам нужно только пробежаться по коду ядра для определений, которые нас интересуют.
Компилировать как:
touch Solver.hs ; ghc -O2 --make Main.hs \
-ddump-simpl -dsuppress-module-prefixes -dsuppress-coercions > dump
Перейти к определениюcumsumBMP
и найдите ключевое слово letrec
.Поиск letrec
- это быстрый способ найти внутренние петли.
Не слишком далеко, я вижу это: (немного переформатирован)
case gen_a1tr
of _ {
GenManifest vec_a1tv ->
case sh2_a1tc `cast` ... of _ { :. sh3_a1iu sh4_a1iv ->
case ix'_a1t9 `cast` ... of _ { :. sh1'_a1iz sh2'_a1iA ->
case sh3_a1iu `cast` ... of _ { :. sh5_X1n0 sh6_X1n2 ->
case sh1'_a1iz `cast` ... of _ { :. sh1'1_X1n9 sh2'1_X1nb ->
case sh5_X1n0 of _ { :. sh7_X1n8 sh8_X1na ->
...
case sh2'1_X1nb of _ { I# y3_X1nO ->
case sh4_a1iv of _ { I# y4_X1nP ->
case sh2'_a1iA of _ { I# y5_X1nX ->
...
let { x3_a1x6 :: Int# [LclId]
x3_a1x6 =
+#
(*#
(+#
(*#
y1_a1iM
y2_X1nG)
y3_X1nO)
y4_X1nP)
y5_X1nX } in
case >=#
x3_a1x6
0
of ...
Бедствие!Привязка x3_a1x6
явно выполняет некоторую полезную работу (умножение, сложение и тому подобное), но она заключена в длинный ряд операций распаковки, которые также выполняются для каждой итерации цикла.Хуже всего то, что он распаковывает длину и ширину (форму) массива на каждой итерации, и эта информация всегда будет одинаковой.GHC действительно должен вывести эти выражения из цикла, но это пока не так.Это экземпляр Проблема # 4081 в GHC trac , которая, надеюсь, будет исправлена в ближайшее время.
Обходной путь - применить deepSeqArray
к входящему массиву.Это накладывает спрос на его значение на верхнем уровне (вне цикла), что позволяет GHC знать, что можно продолжать перемещение совпадений.Для такой функции, как cumsumBMP
, мы также ожидаем, что входящий массив уже будет манифестом, поэтому мы можем добавить явное совпадение регистра для этого:
{-# NOINLINE cumsumBMP #-}
cumsumBMP :: Array DIM3 Word8 -> Array DIM3 Word8
cumsumBMP img@(Array _ [Region RangeAll (GenManifest _)])
= img `deepSeqArray` cumsum $ transpose img
Снова компилируя, внутренний цикл теперь выглядит намного лучше:
letrec {
$s$wfoldlM'_loop_s2mW [...]
:: Int# -> Word# -> Word# [...]
$s$wfoldlM'_loop_s2mW =
\ (sc_s2mA :: Int#) (sc1_s2mB :: Word#) ->
case <=# sc_s2mA a_s2ji of _ {
False -> sc1_s2mB;
True ->
$s$wfoldlM'_loop_s2mW
(+# sc_s2mA 1)
(narrow8Word#
(plusWord#
sc1_s2mB
(indexWord8Array#
rb3_a2gZ
(+#
rb1_a2gX
(+#
(*#
(+#
(*#
wild19_X1zO
ipv1_X1m5)
sc_s2mA)
ipv2_X1m0)
wild20_X1Ct)))))
}; } in
Это узкий хвостовой рекурсивный цикл, который использует только примитивные операции.При условии, что вы скомпилируете с -fllvm -optlo-O3
, нет причины, по которой он не будет работать так же быстро, как эквивалентная программа на Си.
При запуске программы возможен небольшой сбой:
desire:tmp benl$ ./Main
Main: Solver.hs:(50,1)-(51,45): Non-exhaustive patterns in function cumsumBMP
Это простонапоминает нам о том, что перед вызовом cumsumBMP
.
-- Main.hs ---------------------------------------------------
...
import Data.Array.Repa as Repa
main
= do Right img <- readImageFromBMP "whatever.bmp"
print $ cumsumBMP $ Repa.force img
нам нужно форсировать массив. В итоге:
- Вам необходимо добавить в свой цикл
deepSeqArray
и шаблон соответствияфункции верхнего уровня для обхода текущей погрешности в GHC.Это продемонстрировано в окончательной версии функции cumsumBMP
выше.Если вы хотите, чтобы GHC HQ исправил это в ближайшее время, добавьте себя как cc к Issue # 4081 на GHC trac .Программы исправления будут намного красивее, когда это будет исправлено. - Вам не нужно добавлять цикл к каждой функции.В этом примере мне не нужно было трогать
indexSlice
и друзей.Общее правило заключается в добавлении группы к функциям, которые используют force
, fold
или sumAll
.Эти функции создают действительные циклы, которые работают с данными массива, то есть они преобразуют задержанный массив в значение манифеста. - Производительность фрагмента кода Repa в той же степени определяется контекстом, в котором ониспользуется в качестве фактического кода.Если вы передадите своим задержанным массивам функции верхнего уровня, они будут работать очень медленно.Более подробно об этом говорится в Учебное пособие по Repa .
- BMP-файлы, считываемые из библиотеки repa-io, предварительно не форсируются, поэтому перед их использованием необходимо принудительно их принудительно применить.Возможно, это неправильное значение по умолчанию, поэтому я изменю его в следующей версии.