Nano Hash - криптовалюты, майнинг, программирование

Почему не работает мемоизация?

Прочитав введение в запоминание, я повторно реализовал пример Фибоначчи, используя более общую функцию запоминания (только для учебные цели):

memoizer :: (Int -> Integer) -> Int -> Integer
memoizer f = (map f [0 ..] !!)

memoized_fib :: Int -> Integer
memoized_fib = memoizer fib
    where fib 0 = 0
          fib 1 = 1
          fib n = memoized_fib (n-2) + memoized_fib (n-1)

Это работает, но когда я просто меняю последнюю строку на следующий код, мемоизация вдруг не работает так, как я ожидал (программа снова начинает тормозить):

          fib n = memoizer fib (n-2) + memoizer fib (n-1)

Где принципиальная разница с.р.т. для запоминания?



Ответы:


1

Речь идет о явном и неявном совместном использовании. Когда вы явно называете вещь, она, естественно, может использоваться совместно, то есть существовать как отдельная сущность в памяти и использоваться повторно. (Конечно, совместное использование не является частью языка как такового, мы можем лишь немного подтолкнуть компилятор к совместному использованию некоторых вещей).

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

Ваш первый вариант эквивалентен

memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)  where
        fib 0 = 0
        fib 1 = 1
        fib n = memoized_fib (n-2) + memoized_fib (n-1)

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

memoized_fib :: Int -> Integer
memoized_fib = (fibs !!)  where
        fibs = map fib [0 ..] 
        fib 0 = 0
        fib 1 = 1
        fib n = memoized_fib (n-2) + memoized_fib (n-1)

Последнюю строку можно сделать еще более наглядной, добавив явную ссылку на реальную сущность, которой мы делимся здесь, — список fibs, который мы только что назвали на шаге выше:

        fib n = fibs !! (n-2) + fibs !! (n-1)

Ваш второй вариант эквивалентен этому:

memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)  where
        fib 0 = 0
        fib 1 = 1
        fib n = (map fib [0 ..] !!) (n-2) + (map fib [0 ..] !!) (n-1)

Здесь у нас есть три, казалось бы, независимых выражения map, которые могут быть разделены или не разделены компилятором. Компиляция с помощью ghc -O2, по-видимому, снова вводит совместное использование, а вместе с ним и скорость.

18.08.2012
  • Во втором примере вы можете просто написать fib n = fibs !! (n-2) + fibs !! (n-1). Я не уверен, противоречит ли это вашей точке зрения, но я также не уверен, что версия, которую вы написали, разделяет. 19.08.2012
  • С эстетической точки зрения я бы придерживался версии второго решения Уилла Несса, потому что она более удобочитаема. 19.08.2012
  • @BenMillwood да, ты мог бы; но оба вели себя точно так же, когда я проверил. Т.е. выставлено совместное использование при компиляции -O2. Потому что здесь публикуется именованный список fibs (что я и хотел сказать). Если 1-я версия OP была общей, то и моя переписка тоже должна быть общей, потому что она явно называет выражение, которое разделяет компилятор, даже если оно не имеет имени. 19.08.2012
  • @Bastian На самом деле я предпочитаю более явную версию с fibs !! (n-2) + ... , потому что там мы видим явную ссылку на список, который на самом деле является общим объектом - списком fibs. Просто постарался не делать слишком длинный пост. Отредактирую это в. 19.08.2012
  • @ Уилл Несс Пожалуйста, потерпите меня еще 2 дня - из-за экзамена в пятницу у меня сейчас нет времени возиться с этой проблемой. Я вернусь к нему на выходных. Но спасибо за вашу преданность! :-) 22.08.2012
  • Так что я просто перечитал ваш пост и пойду с ним. Есть ли какая-нибудь ссылка на Haskell по этой теме, чтобы лучше с ней ознакомиться (например, какая-то книга о Haskell)? 28.08.2012
  • @ Бастиан понятия не имеет. :) В самом деле. (Я думаю, у Haskell нет документации). Вы можете попробовать еще несколько записей SO, связанных справа здесь. Главное - мемоизация или нет, совместное использование или нет, это не часть Языка, в лучшем случае это часть компилятора. Даже когда мы называем что-то, компилятор может решить пересчитать его вместо повторного использования. У нас нет контроля над этим вопросом. Некоторые говорят, что он отсутствует в Haskell (в этом отношении). 28.08.2012

  • 2

    momoized_fib = ... - это простое определение верхнего уровня. это может быть прочитано как постоянное ленивое значение (без каких-либо дополнительных аргументов, необходимых для привязки перед его расширением. Это своего рода «источник» ваших запомненных значений.

    Когда вы используете (memoizer fib) (n-2), создается новый источник значений, который не имеет отношения к memoized_fib и поэтому не используется повторно. На самом деле вы переносите большую нагрузку на GC здесь, так как вы создаете много (map fib [0 ..]) последовательностей во втором варианте.

    Рассмотрим также более простой пример:

    f = \n -> sq !! n where sq = [x*x | x <- [0 ..]]
    g n = sq !! n where sq = [x*x | x <- [0 ..]]
    

    Сначала будет сгенерирован один f и связанный с ним sq, потому что в заголовке объявления нет n. Второй создаст семейство списков для каждого другого значения f n и переместится по нему (не ограничиваясь фактическими значениями), чтобы получить значение.

    18.08.2012
  • Я думаю, что g n = ... — это синтаксический сахар для g = \n-> .... Не должно быть никакой разницы. Вы думаете о f = (sq !!) where sq = ..., но это не то, что вы написали. Кроме того, определение sq на самом деле не зависит от n, поэтому компилятор может повторно использовать список sq. Если, конечно, этому не препятствует полиморфизм при отсутствии сигнатуры мономорфного типа. 19.08.2012
  • @WillNess: ключ в том, находится ли предложение where внутри или снаружи лямбды, что синтаксический сахар действительно изменяет, поскольку where присоединяется только к объявлениям, а не к выражениям. 19.08.2012
  • @ony оказывается, я был неправ, извините :) where идет с f, так что вы действительно написали то, что, как вы думали, написали, а не то, что я думал, что прочитал. :) :) 19.08.2012
  • @BenMillwood спасибо за исправление! так что разница между привязкой шаблона для f создание f = let sq=[...] in (\n-> sq !! n) и привязка функции для g создания эквивалент g = \n-> let sq = [...] in sq !! n - правильно? Тем не менее, теоретически компилятор может вывести let sq = (верно?) из лямбды в g, поскольку определение sq нигде не ссылается на n... если только оно не полиморфно... 19.08.2012
  • @WillNess: Да, это правильно, и это действительно может быть спущено на воду. Это, вероятно, не очень хорошая идея - sq очень дешево создавать, поэтому хранить его в памяти - пустая трата времени. Лучше быть явным - как правило, вы не хотите зависеть от оптимизации компилятора для улучшения асимптотического поведения вашего кода (как с fib). 19.08.2012
  • @Vitus спасибо за подтверждение! :) что касается sq, это был просто пример... Что касается явности, не могу не согласиться, это именно то, что я пытался сделать в мой ответ здесь. Экспликация сама по себе является ценным принципом проектирования. 19.08.2012
  • Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..