Производительность оператора экспоненты

#haskell

#haskell

Вопрос:

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

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

Для справки, я реализовывал проблему № 29, вот что я придумал

 nub $ [ a^^b | a <- [2..100], b <- [2..100] ]
 

Я заметил ^^ , что использование ** оператора выполняется быстрее, чем ^ для ввода, указанного выше.

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

Спасибо!

Комментарии:

1. Если список не очень короткий (а это не так), не используйте nub . Это O (n ^ 2).

2. В Haskell нет преобразований типов.

Ответ №1:

Все время тратится на nub . С ^^ помощью и ** вы nub продолжаете [Double] . С ^ ним nub включено [Integer] , и сравнение больших целых чисел происходит медленнее, чем сравнение двойных чисел.

Комментарии:

1. Правильно, nub выполняет всю работу в примере. Спасибо, что прояснили это.

Ответ №2:

** и ^^ использует Double , но ^ использует Integer . Вы действительно не можете сравнить операцию с плавающей запятой с функцией большого целого числа. Взгляните на реализацию ^ .

В вашем коде верно следующее:

  • ** реализовано аппаратно.
  • ^ использует big Integer в хвостовом рекурсивном цикле.
  • ^^ то же самое, за исключением Double .

Итак, ваши наблюдения об их относительной производительности имеют смысл.

Ответ №3:

То, что я обнаружил, работая над проблемами Project Euler, заключается в том, что типы могут иметь огромное значение для производительности во время выполнения. Например:

 foo :: Integral a => a -> a
foo' :: Integer -> Integer
foo'' :: Int -> Int
 

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

Производительность также (очевидно) сильно зависит от вашей среды: выполняется ли компиляция или интерпретация? Оптимизированный или неоптимизированный? Дело в том, что в некоторых случаях у вас могут быть распакованные примитивные Int# s под обложками, а не упакованные значения, выделенные из кучи… К сожалению, у меня все еще достаточно n00b, что я не знаю, когда вы получите его против другой 🙁

Итак, возможно, это глупый ответ, но если вы используете GHC и знакомы с программированием на C, попробуйте использовать -keep-hc-files флаг для сравнения промежуточного кода C, сгенерированного при использовании ^ vs ^^ .vs ** .

Комментарии:

1. Вы получите Int#, когда компилятор увидит, что целые числа строго необходимы. Вы можете помочь с шаблонами взрыва, но проверьте ядро, получили ли вы их, и оцените, действительно ли это помогает. С другой стороны, файлы .hc генерируются только незарегистрированными компиляторами (говорится в руководстве пользователя), поэтому у большинства их все равно не будет.