2013-02-26 3 views
10

Мое приложение умножает векторы после (дорогостоящего) преобразования с использованием БПФ. В результате, когда я пишуЗапоминающее умножение

f :: (Num a) => a -> [a] -> [a] 
f c xs = map (c*) xs 

Я только хочу, чтобы вычислить БПФ c один раз, а не для каждого элемента xs. Там действительно нет необходимости хранить БПФ от c для всей программы, только в локальной области.

я попытался определить мой Num экземпляр как:

data Foo = Scalar c 
     | Vec Bool v -- the bool indicates which domain v is in 

instance Num Foo where 
    (*) (Scalar c) = \x -> case x of 
         Scalar d -> Scalar (c*d) 
         Vec b v-> Vec b $ map (c*) v 
    (*) v1 = let Vec True v = fft v1 
      in \x -> case x of 
        Scalar d -> Vec True $ map (c*) v 
        v2 -> Vec True $ zipWith (*) v (fft v2) 

Затем, в приложении, я вызываю функцию, аналогичную f (который работает на произвольных Num с), где c=Vec False v, и я ожидал, что это будет быть столь же быстро, как если бы я взломать f на:

g :: Foo -> [Foo] -> [Foo] 
g c xs = let c' = fft c 
     in map (c'*) xs 

функция g делает запоминание из fft c происходят, и мю ch быстрее, чем вызов f (независимо от того, как я определяю (*)). Я не понимаю, что происходит с f. Является ли это моим определением (*) в примере Num? Имеет ли он какое-то отношение к f, работающему над всеми Nums, и GHC, поэтому не может понять, как частично вычислить (*)?

Примечание: Я проверил вывод ядра для моего экземпляра Num, и (*) действительно представлен как вложенные лямбды с преобразованием FFT на лямбда верхнего уровня. Таким образом, похоже, что это, по крайней мере, способно быть замеченным. Я также попытался как разумно, так и безрассудно использовать шаблоны ударов, чтобы попытаться заставить оценку не иметь никакого эффекта.

Как примечание стороны, даже если я могу понять, как сделать (*) memoize его первый аргумент, есть еще одна проблема с тем, как это определено: Программист хочет использования тип данных Foo должен знать о это возможность memoization. Если она написали

map (*c) xs 

никакой меморандума не возникнет. (Это должно быть написано как (map (c*) xs)) Теперь, когда я думаю об этом, я не совсем уверен, как GHC перепишет версию (*c), так как у меня есть (*). (c*) делает c первым аргументом *, тогда как (*c) делает c вторым аргументом *. Поэтому проблема заключается в том, что неясно, как следует писать умножение для обеспечения memoization. Является ли это просто неотъемлемым недостатком нотации infix (и неявное предположение, что аргументы * являются симметричными)?

Вторая, менее актуальная проблема заключается в том, что корпус re отобразим (v *) в список скаляров. В этом случае (надеюсь), fft of v будет вычисляться и храниться, хотя это необязательно, так как другой множитель является скаляром. Есть ли способ обойти это?

Благодаря

+0

компиляции с '-O2' и сравнительного анализа с скомпилированного кода? – jberryman

+0

Да, но я проверял ядро, и, похоже, он ничего не компилирует. – crockeea

+0

BTW, 'let c '= fft c на карте (c *) xs' does * not * вычисляет' fft', потому что Haskell ленив. 'c'' никогда не используется, поэтому он никогда не будет вычислен. – luqui

ответ

2

Я считаю, что stable-memo пакет может решить вашу проблему. Это memoizes значения не используя равенство, но ссылочная идентичность:

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

И он автоматически падает memoized значения, когда их ключи сборки мусора:

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

Так что, если вы определяете что-то вроде

fft = memo fft' 
    where fft' = ... -- your old definition 

вы получите довольно много, что вам нужно: Calling map (c *) xs будет memoize вычисление БПФ внутри первого вызова (*) и он получает повторно при последующих вызовах до (c *). И если c - сбор мусора, то есть fft' c.

Смотрите также this answer к How to add fields that only cache something to ADT?

+0

Я обязательно сделаю это, но знаете ли вы, почему «равенство» не работает для memoization? – crockeea

+0

@ Эрик Нет ничего плохого в равенстве. Просто эта библиотека пошла по другому пути и работает на низком уровне, сравнивая ссылки. Хотя это, вероятно, требует полагаться на низкоуровневый API GHC и снижает переносимость пакета, он имеет некоторые преимущества. В частности, его тесная интеграция с сборкой мусора (что важно для вашей задачи). И у вас нет ограничений на memoizable types - вы можете memoize типы, для которых у вас нет экземпляра 'Eq' или другого подобного типа. –

+0

Согласен, все эти вещи очень приятные. Мой вопрос: почему я должен верить, что это сработает, когда моя попытка «замещения мнений» потерпела неудачу? Или вы предполагаете, что это на самом деле делало * не * терпит неудачу, но что он просто использовал линейное время для использования? Я бы поверила, что мои плохие результаты, если это было так. – crockeea

1

я вижу две проблемы, которые могут помешать запоминанием:

Во-первых, f имеет перегруженный тип и работает для всех Num экземпляров. Таким образом, f не может использовать memoization, если он не является специализированным (что обычно требует прагмы SPECIALIZE) или inlined (что может происходить автоматически, но более надежно с помощью прагмы INLINE).

Во-вторых, определение (*) для Foo выполняет сопоставление с образцом на первый аргумент, но f умножает с неизвестным c. Таким образом, в пределах f, даже если он специализирован, никакая memoization не может произойти. Еще раз, это очень зависит от f, являющегося встроенным, и конкретным аргументом для c, который будет поставляться, чтобы на самом деле появилась вставка.

Так что я думаю, что это поможет узнать, как именно вы звоните f. Обратите внимание, что если f определяется с использованием двух аргументов, ему нужно дать два аргумента, иначе он не может быть встроен. Это также помогло бы увидеть фактическое определение Foo, как вы сообщаете c и v, которые не входят в сферу действия.

+0

Я не вижу, как что-то может зависеть от того, что f является встроенным или нет. Я не пытаюсь сам memoize f, просто функция, которую я использую для отображения по списку. Фактический def: f :: (Num a) => [a] -> [[a]] -> [a] f xs = foldl1 '(zipWith (+)). zipWith (\ a b -> map (a *) b) xs Я вижу, что я пропустил параметры вне области видимости на Foo, но реальные данные имеют 5 параметров и столько же конструкторов. Я думаю, что Foo передает соответствующую информацию. Чтобы быть более точным в этом, вы можете себе представить, что у меня есть – crockeea

+0

'Foo = Vec Bool [Int] | Scalar Int'. – crockeea

+0

А как насчет вашего 'f', вы ожидаете, что его запомнят? '(A *)'? Но, как я сказал, это не может уменьшить это, поскольку (1) тип слишком общий, и (2) мы ничего не знаем о 'a', тогда как определение' (*) 'сначала выполняет сопоставление шаблонов. Таким образом, все зависит от 'f', являющегося специализированным или встроенным. – kosmikus