Haskell, кэширующий результаты функции

У меня есть функция, которая берет параметр и приводит к результату. К сожалению, это берет, вполне очень хотят, чтобы функция привела к результату. Функция вызывается довольно часто с тем же входом, вот почему было бы удобно, если я мог бы кэшировать результаты. Что-то как

let cachedFunction = createCache slowFunction
in (cachedFunction 3.1) + (cachedFunction 4.2) + (cachedFunction 3.1)

Я изучал Данные. Массив и хотя массив ленив, я должен инициализировать его со списком пар (использующий listArray) - который непрактичен. Если 'ключ', например, 'Двойной' тип, я не могу инициализировать его вообще, и даже если я могу теоретически присвоить Целое число каждому возможному входу, у меня есть несколько десятков тысяч возможные исходные данные, и я только на самом деле использую небольшое количество. Я должен был бы инициализировать массив (или, предпочтительно хэш-таблица, поскольку только горстка результатов будет использоваться), использование функции вместо списка.

Обновление: Я читаю memoization статьи и насколько я понимаю это, MemoTrie мог проложить себе путь, я хочу. Возможно. Кто-то мог попытаться произвести 'cachedFunction'? Prefereably для медленной функции, которая берет 2 Двойных аргумента? Или, альтернативно, это берет один Международный аргумент в домене ~ [0.. 1 миллиард], который не съел бы всю память?

20
задан C. A. McCann 7 February 2010 в 20:06
поделиться

7 ответов

Ну, есть Data.HashTable . Однако хеш-таблицы не очень хорошо работают с неизменяемыми данными и ссылочной прозрачностью, поэтому я не думаю, что они найдут много пользы.

Для небольшого количества значений, вероятно, будет достаточно быстро разместить их в дереве поиска (например, Data.Map ).Если вы можете смириться с некоторыми искажениями ваших Double s, более надежным решением будет использование структуры, подобной дереву, такой как Data.IntMap ; у них время поиска пропорционально, в первую очередь, длине ключа и примерно одинаково по размеру коллекции. Если Int слишком ограничивает, вы можете покопаться в Hackage, чтобы найти библиотеки trie, более гибкие по типу используемого ключа.

Что касается того, как кэшировать результаты, я думаю, то, что вы хотите, обычно называется «мемоизацией» . Если вы хотите вычислять и запоминать результаты по запросу, суть метода состоит в том, чтобы определить индексированную структуру данных, содержащую все возможные результаты таким образом, чтобы, когда вы запрашиваете конкретный результат, вынуждает только вычисления, необходимые для получения желаемого ответа. Общие примеры обычно включают индексацию в список, но тот же принцип должен применяться к любой нестрогой структуре данных. Как показывает опыт, значения, не относящиеся к функциям (включая бесконечные рекурсивные структуры данных), часто кэшируются средой выполнения, но не результаты функций, поэтому хитрость заключается в том, чтобы заключить все ваши вычисления в определение верхнего уровня, которое не зависят от любых аргументов.

Изменить: Пример MemoTrie Эй!

Это быстрое и грязное доказательство концепции; могут существовать лучшие подходы.

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Data.MemoTrie
import Data.Binary
import Data.ByteString.Lazy hiding (map)

mangle :: Double -> [Int]
mangle = map fromIntegral . unpack . encode

unmangle :: [Int] -> Double
unmangle = decode . pack . map fromIntegral

instance HasTrie Double where
    data Double :->: a = DoubleTrie ([Int] :->: a)
    trie f = DoubleTrie $ trie $ f . unmangle
    untrie (DoubleTrie t) = untrie t . mangle

slow x 
    | x < 1 = 1
    | otherwise = slow (x / 2) + slow (x / 3)

memoSlow :: Double -> Integer
memoSlow = memo slow

Обратите внимание на расширения GHC, используемые пакетом MemoTrie; надеюсь, это не проблема. Загрузите его в GHCi и попробуйте вызвать slow vs. memoSlow с чем-то вроде (10 ^ 6) или (10 ^ 7), чтобы увидеть его в действии.

Обобщение этого на функции, принимающие несколько аргументов или еще чего-то, должно быть довольно простым. Для получения дополнительных сведений об использовании MemoTrie вы можете найти это сообщение в блоге его автора полезным.

17
ответ дан 30 November 2019 в 00:39
поделиться
4
ответ дан 30 November 2019 в 00:39
поделиться

Вы можете написать медленную функцию как функцию более высокого порядка, возвращающую саму функцию. Таким образом, вы можете выполнять всю предварительную обработку внутри медленной функции и той части, которая отличается в каждом вычислении в возвращаемой (надеюсь, быстрой) функции. Пример может выглядеть так: (код SML, но идея должна быть ясной)

fun computeComplicatedThing (x:float) (y:float) = (* ... some very complicated computation *)
fun computeComplicatedThingFast = computeComplicatedThing 3.14 (* provide x, do computation that needs only x *)
val result1 = computeComplicatedThingFast 2.71 (* provide y, do computation that needs x and y *)
val result2 = computeComplicatedThingFast 2.81
val result3 = computeComplicatedThingFast 2.91
2
ответ дан 30 November 2019 в 00:39
поделиться

Я добавлю собственное решение, которое тоже кажется довольно медленным. Первый параметр - это функция, которая возвращает Int32 - уникальный идентификатор параметра. Если вы хотите однозначно идентифицировать его разными способами (например, по 'id'), вам нужно изменить второй параметр в H.new на другую хеш-функцию. Я постараюсь узнать, как использовать Data.Составьте карту и проверьте, получу ли я более быстрые результаты.

import qualified Data.HashTable as H
import Data.Int
import System.IO.Unsafe

cache :: (a -> Int32) -> (a -> b) -> (a -> b)
cache ident f = unsafePerformIO $ createfunc
    where 
        createfunc = do
            storage <- H.new (==) id
            return (doit storage)

        doit storage = unsafePerformIO . comp
            where 
                comp x = do
                    look <- H.lookup storage (ident x)

                    case look of
                        Just res -> return res
                        Nothing -> do
                            result <- return (f x)
                            H.insert storage (ident x) result
                            return result
2
ответ дан 30 November 2019 в 00:39
поделиться

У меня есть несколько десятков тысяч возможных входов, а на самом деле я использую только горстку. Мне бы потребовалось инициализировать массив ... с помощью функции вместо списка.

Я бы использовал listArray (start, end) (map func [start..end])

  • func на самом деле не вызывалась выше. Haskell ленив и создает thunks, которые будут вычисляться, когда значение будет действительно необходимо.
  • При использовании обычного массива всегда нужно инициализировать его значения. Так что работа, необходимая для создания этих элементов, все равно необходима.
  • Несколько десятков тысяч - это далеко не много. Если бы у Вас были триллионы, то я бы предложил использовать хэш-таблицу yada yada
1
ответ дан 30 November 2019 в 00:39
поделиться

Я не знаю конкретно haskell, но как насчет сохранения существующих ответов в некотором хешированном виде? структура данных (можно было бы назвать словарем или хэш-картой)? Вы можете заключить свою медленную функцию в другую функцию, которая сначала проверяет карту и вызывает медленную функцию только в том случае, если она не нашла ответа.

Вы можете сделать это необычным, ограничив размер карты определенным размером и, когда он достигнет этого размера, выкинув наименее использовавшуюся запись. Для этого вам дополнительно потребуется сохранить карту сопоставлений ключей и отметок времени.

0
ответ дан 30 November 2019 в 00:39
поделиться

В системе времени выполнения GHC есть ряд инструментов, специально предназначенных для поддержки мемоизации.

К сожалению, мемоизация не является универсальной задачей, поэтому существует несколько различных подходов, которые нам необходимо поддерживать, чтобы справиться с различными потребностями пользователей.

Вы можете найти оригинальную запись 1999 года полезной, поскольку она включает в себя несколько реализаций в качестве примеров:

Расширение диспетчера хранилища: слабые указатели и стабильные имена в Haskell Саймона Пейтона Джонса, Саймона Марлоу и Конала Эллиотта

]
3
ответ дан 30 November 2019 в 00:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: