Здесь код:
{-# LANGUAGE FlexibleContexts #-}
import Data.Int
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Generic as V
{-# NOINLINE f #-} -- Note the 'NO'
--f :: (Num r, V.Vector v r) => v r -> v r -> v r
--f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
--f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
f = V.zipWith (+) -- or U.zipWith, it doesn't make a difference
main = do
let iters = 100
dim = 221184
y = U.replicate dim 0 :: U.Vector Int64
let ans = iterate ((f y)) y !! iters
putStr $ (show $ U.sum ans)
Я скомпилировал с ghc 7.6.2
и -O2
, и для запуска потребовалось 1,7 секунды.
Я пробовал несколько разных версий f
:
-
f x = U.zipWith (+) x
-
f x = (U.zipWith (+) x) . id
-
f x y = U.zipWith (+) x y
Версия 1 такая же, как у оригинала, в то время как версии 2 и 3 работают менее 0,09 секунды (и INLINING
f
ничего не меняет).
Я также заметил, что если я сделаю f
полиморфным (с любой из трех подписей выше), даже с "быстрым" определением (т.е. 2 или 3), он замедляется обратно... ровно на 1,7 секунды. Это заставляет меня задаться вопросом, возможно ли исходная проблема из-за (отсутствия) вывода типа, хотя я явно даю типы типа Vector и типа элемента.
Мне также интересно добавлять целые числа по модулю q
:
newtype Zq q i = Zq {unZq :: i}
Как и при добавлении Int64
s, если я напишу функцию с каждым указанным типом,
h :: U.Vector (Zq Q17 Int64) -> U.Vector (Zq Q17 Int64) -> U.Vector (Zq Q17 Int64)
Я получаю на порядок лучшую производительность, чем если бы я оставил какой-либо полиморфизм
h :: (Modulus q) => U.Vector (Zq q Int64) -> U.Vector (Zq q Int64) -> U.Vector (Zq q Int64)
Но я должен хотя бы удалить конкретный тип phantom! Он должен быть скомпилирован, так как я имею дело с newtype
.
Вот мои вопросы:
- Где происходит замедление?
- Что происходит в версиях 2 и 3 из
f
, которые влияют на производительность каким-либо образом? Мне кажется, что ошибка (то, что соответствует) стиль кодирования может повлиять на производительность, как это. Существуют ли другие примеры за пределами Vector, где частично применение функции или других стилистических решений влияет на производительность? - Почему полиморфизм замедляет меня на порядок, независимо от того, где полиморфизм (т.е. в векторном типе, в типе
Num
, как, так и phantom)? Я знаю, что полиморфизм делает код медленнее, но это смешно. Есть ли взломать его?
ИЗМЕНИТЬ 1
Я подал issue на странице библиотеки Vector. Я нашел GHC вопрос, связанный с этой проблемой.
EDIT2
Я переписал вопрос, получив некоторое понимание от ответа @kqr. Ниже приведено оригинальное описание.
-------------- ОРИГИНАЛЬНЫЙ ВОПРОС --------------------
Здесь код:
{-# LANGUAGE FlexibleContexts #-}
import Control.DeepSeq
import Data.Int
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Generic as V
{-# NOINLINE f #-} -- Note the 'NO'
--f :: (Num r, V.Vector v r) => v r -> v r -> v r
--f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
--f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
f = V.zipWith (+)
main = do
let iters = 100
dim = 221184
y = U.replicate dim 0 :: U.Vector Int64
let ans = iterate ((f y)) y !! iters
putStr $ (show $ U.sum ans)
Я скомпилировал с ghc 7.6.2
и -O2
, и для запуска потребовалось 1,7 секунды.
Я пробовал несколько разных версий f
:
-
f x = U.zipWith (+) x
-
f x = (U.zipWith (+) x) . U.force
-
f x = (U.zipWith (+) x) . Control.DeepSeq.force)
-
f x = (U.zipWith (+) x) . (\z -> z `seq` z)
-
f x = (U.zipWith (+) x) . id
-
f x y = U.zipWith (+) x y
Версия 1 такая же, как и у оригинала, версия 2 работает за 0.111 секунд, а версии 3-6 запускаются менее 0,09 секунды (и INLINING
f
ничего не меняет).
Таким образом, замедление порядка величины, по-видимому, объясняется ленинностью, так как force
помог, но я не уверен, откуда лень. Unboxed типы не могут быть ленивыми, не так ли?
Я попробовал написать строгую версию iterate
, считая, что сам вектор должен быть ленивым:
{-# INLINE iterate' #-}
iterate' :: (NFData a) => (a -> a) -> a -> [a]
iterate' f x = x `seq` x : iterate' f (f x)
но с точечной версией f
это не помогло.
Я также заметил что-то еще, что может быть просто совпадением и красной селедкой:
Если я сделаю f
полиморфным (с любой из трех подписей выше), даже с "быстрым" определением, он замедляется обратно... ровно на 1,7 секунды. Это заставляет меня задаться вопросом, может ли исходная проблема возникнуть из-за (отсутствия) вывода типа, хотя все должно быть понятно.
Вот мои вопросы:
- Где происходит замедление?
- Почему создание с помощью
force
справки, но с использованием строгойiterate
нет? - Почему
U.force
хуже, чемDeepSeq.force
? Я понятия не имею, что делатьU.force
, но это очень похоже наDeepSeq.force
и, похоже, имеет аналогичный эффект. - Почему полиморфизм замедляет меня на порядок, независимо от того, где полиморфизм (т.е. в векторном типе, в типе
Num
или в обоих)? - Почему версии 5 и 6, ни одна из которых не должна иметь каких-либо последствий для строгости, так же быстро, как и строгая функция?
Как указывал @kqr, проблема не выглядит строгостью. Итак, что-то о том, как я пишу эту функцию, приводит к тому, что общий zipWith
используется, а не версия Unboxed. Это просто случайность между GHC и библиотекой Vector, или есть что-то более общее, что можно здесь сказать?