Рассуждение о производительности в Хаскеле

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

SELECT COLUMN_NAME
  FROM INFORMATION_SCHEMA.COLUMNS
  WHERE table_schema = 'db_name'

Однако, когда я запускал этот запрос в phpmyadmin, он отображал ряд ошибок. Тем не менее, это сработало. Поэтому используйте его с осторожностью.

10
задан 6 revs, 2 users 89% 6 November 2010 в 13:02
поделиться

5 ответов

В вашем конкретном примере Fibonacci не очень трудно понять, почему второй должен работать быстрее (хотя вы не указали, что F2).

Это в основном алгоритмический вопрос:

  • FIB1 реализует чисто рекурсивный алгоритм и (насколько я знаю) Haskell не имеет механизма для «неявной памяти».
  • FIB2 использует явную мемузаризацию (используя список FIBARR для хранения ранее вычисленных значений.

В общем, гораздо сложнее сделать предположения для ленивого языка, как Haskell, чем для нетерпеливого. Тем не менее, если Вы понимаете базовые механизмы (особенно для лени) и собирают некоторые опыта , вы сможете внести некоторые «прогнозы» о производительности.

Ссылательная прозрачность Потенциально) производительность в (по крайней мере) два способа:

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

Если вы хотите прочитать больше о рассуждениях за выбором дизайна (лень, известность) Haskell, я предложил чтение этого .

11
ответ дан 3 December 2019 в 16:29
поделиться

Это истинная рекурсия, и * RECURSION * не является реальным сообщением об ошибке. Это не проблематично, потому что $ test не является активно повторяющимся, и в этом случае var _ dump достаточно умен, чтобы остановиться перед истощением памяти.

-121--3978150-

Как ответили другие и руководство, это не обязательно. Но если вы удивитесь использованию; как правило, это необходимо делать только в том случае, если в скрипте PHP необходимо добавить дополнительные данные и убедиться в том, что в этом случае необходимо использовать другое соединение/транзакцию.

-121--3460699-

Рассуждения о производительности, как правило, трудно в Haskell и ленивых языках в целом, хотя и не невозможно. Некоторые методы описаны в Чисто функциональные структуры данных Криса Окасаки (также доступны онлайн в предыдущей версии).

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

В примере можно вычислить числа «снизу вверх» и передать предыдущие два числа в каждую итерацию:

fib n = fib_iter(1,1,n)
    where
      fib_iter(a,b,0) = a
      fib_iter(a,b,1) = a
      fib_iter(a,b,n) = fib_iter(a+b,a,n-1)

Это приводит к линейному алгоритму времени.

При наличии алгоритма динамического программирования, в котором каждый результат зависит от N предыдущих результатов, можно использовать этот метод. В противном случае вам придется использовать массив или что-то совсем другое.

6
ответ дан 3 December 2019 в 16:29
поделиться

Так как выделение является основной стоимостью на любом функциональном языке, важной частью Понимание характеристики - это понимать, когда объекты выделены, как долго они живут, когда они умирают, и когда они будут восстанавливаться. Чтобы получить эту информацию вам нужна Profiler Heap . Это необходимый инструмент, и к счастью, судна GHC с хорошим.

Для получения дополнительной информации читать Polin Runciman документы.

1
ответ дан 3 December 2019 в 16:29
поделиться

Ваша реализация FIB2 использует мемузаризацию, но каждый раз, когда вы называете FIB2, он восстановил «целый» результат. Включите профилирование времени и размера GHCI:

Prelude> :set +s

Если оно выполняет мемузацию «между» вызовыми звонками, последующие вызовы будут быстрее и не использовать память. Вызовите FIB2 20000 дважды и убедитесь сами.

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

-- the infinite list of all fibs numbers.
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

memoFib n = fibs !! n

На самом деле используйте мемузацию, явную, как вы видите. Если вы запускаете MemOfib 20000 в два раза, вы увидите время и пространство, впервые принятое, то второй вызов мгновенный и не принимайте память. Нет волшебства и никакой неявной комментарии, как комментарий, мог наметить.

Теперь о вашем первоначальном вопросе: оптимизация и рассуждения о производительности в Haskell ...

Я бы не назвал себя экспертом в Haskell, я бы только использовал его в течение 3 лет, 2 из которых на моем рабочем месте, но Я должен был оптимизировать и понять, как несколько разумно рассуждать о его производительности.

Как упоминается, что в другом посте лень - ваш друг и может помочь вам получить производительность, однако вы должны контролировать то, что лениво оценивается и то, что строго оценивается.

Проверьте это сравнение Foldl VS Foldr

Foldl на самом деле хранит «Как» вычислить значение I.E. Лениво. В каком-то случае вы экономит время и пространство, ленивые, такие как «бесконечные» фонари. Бесконечные «Fibs» не генерируют все они, но знают, как. Когда вы знаете, вам понадобится ценность, которую вы могли бы просто получить его «строго». Говоря ... Вот где строгообразная аннотация полезна, чтобы вернуть вам контроль.

Я вспоминаю много раз читаю, что в Lisp вы должны «минимизировать» консицию.

Понимание того, что строго оценивается и как заставить его важно, но так понимают, насколько «мусорным» вы делаете с памятью. Помните, что Haskell неизменяется, это означает, что обновление «переменная» на самом деле создает копию с модификацией. Кресть с (:) значительно более эффективен, чем присоединение к (++), потому что (:) не копирует память против (++). Всякий раз, когда обновляется большой блок блока (даже для одного CHAR), весь блок должен быть скопирован для представления «обновленной» версии. То, как вы структурируете данные и обновление, могут оказать большое влияние на производительность. Profiler GHC - твой друг и поможет вам найти их. Конечно, сборщик мусора быстро, но не имеет ничего быстрее!

ура

5
ответ дан 3 December 2019 в 16:29
поделиться

Помимо вопроса о воменения, FIB1 также использует рекурсию без хвоста. Рекурсия Taill Call может быть восстановлена ​​автоматически в простое GOTO и выполнить очень хорошо, но рекурсия в FIB1 не может быть оптимизирована таким образом, поскольку вам нужен рамка стека из каждого экземпляра FIB1, чтобы рассчитать результат. Если вы переписали FIB1, чтобы пройти проконсультирование в качестве аргумента, что позволяет вызову хвоста вместо того, чтобы нуждаться в том, чтобы сохранить рамку стека для конечного добавления, производительность улучшится чрезвычайно. Но не так много, как воменутый пример, конечно:)

2
ответ дан 3 December 2019 в 16:29
поделиться
Другие вопросы по тегам:

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