Как эта замещенная таблица DP слишком медленная для SPOJ?

SPOILERS: Я работаю над http://www.spoj.pl/problems/KNAPSACK/, поэтому не заглядывайте, если вы не хотите, чтобы возможное решение испортилось для вас.

Шаблон:

import Data.Sequence (index, fromList)
import Data.MemoCombinators (memo2, integral)

main = interact knapsackStr

knapsackStr :: String -> String
knapsackStr str = show $ knapsack items capacity numItems
  where [capacity, numItems] = map read . words $ head ls
        ls = lines str
        items = map (makeItem . words) $ take numItems $ tail ls

Некоторые типы и помощники для установки сцены:

type Item = (Weight, Value)
type Weight = Int
type Value = Int

weight :: Item -> Weight
weight = fst

value :: Item -> Value
value = snd

makeItem :: [String] -> Item
makeItem [w, v] = (read w, read v)

И основная функция:

knapsack :: [Item] -> Weight -> Int -> Value
knapsack itemsList = go
  where go = memo2 integral integral knapsack'
        items = fromList $ (0,0):itemsList
        knapsack' 0 _ = 0
        knapsack' _ 0 = 0
        knapsack' w i | wi > w    = exclude
                      | otherwise = max exclude include
          where wi = weight item
                vi = value item
                item = items `index` i
                exclude = go w (i-1)
                include = go (w-wi) (i-1) + vi

И этот код работает; Я попытался подключить тестовый пример SPOJ, и он дает правильный результат. Но когда я отправляю это решение на SPOJ (вместо импорта MemoCombinators от Luke Palmer, я просто копирую и вставляю необходимые части в отправленный источник), он превышает срок. =/

Я не понимаю, почему; Я спросил ранее об эффективном способе выполнения рюкзака 0-1, и я уверен, что это примерно так же быстро, как и получается: memoized функция, которая будет только рекурсивно вычислять подзаголовки, которые ему абсолютно необходимы, чтобы получить правильный результат. Я каким-то образом испортил мемуары? Есть ли медленный момент в этом коде, который мне не хватает? SPOJ просто предвзято относится к Haskell?

Я даже положил {-# OPTIONS_GHC -O2 #-} в начало представления, но, увы, это не помогло. Я пробовал аналогичное решение, которое использует 2D-массив Sequence s, но также отклонялся как слишком медленный.

Ответы

Ответ 1

Есть одна серьезная проблема, которая действительно замедляет это. Это тоже полиморфно. Типоспецифические версии функций могут быть намного быстрее, чем полиморфные разновидности, и по какой-либо причине GHC не встраивает этот код в точку, где он может определять точные типы использования. Когда я изменяю определение integral на:

integral :: Memo Int
integral = wrap id id bits

Я получаю примерно 5-кратное ускорение; Я думаю, что это достаточно быстро, чтобы быть принятым на SPOJ.

Это все еще значительно медленнее, чем решение gorlum0. Я подозреваю, причина в том, что он использует массивы, и вы используете пользовательский тип trie. Использование trie займет гораздо больше памяти, а также сделает поиск медленнее из-за дополнительных указаний, промахов кэш-памяти и т.д. Возможно, вы сможете многое разделить, если вы ограничиваете и удаляете поля в IntMap, но я не уверен это возможно. Попытка скрыть поля в BitTrie создает для меня ошибки во время выполнения.

Pure haskell memoizing code может быть хорошим, но я не думаю, что это так же быстро, как делать небезопасные вещи (по крайней мере, под капотом). Вы можете применить метод метод Леннарта Аугссона, чтобы узнать, лучше ли это в воспоминаниях.

Ответ 2

Единственное, что замедляет работу Haskell - это IO, тип String в Haskell дает поддержку UTF8, которая нам не нужна для SPOJ. ByteStrings быстро растут, поэтому вы можете захотеть использовать их вместо них.