Усечение типа Word

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

truncate1 :: Double -> Word16
truncate1 = fromIntegral . (truncate :: Double -> Int)

Как вы можете прочитать, я сначала усекаю его до Int, и только затем я отбрасываю его на Word16. Я сравнил эту функцию с прямым усечением:

truncate2 :: Double -> Word16
truncate2 = truncate

Удивительно, но первая версия (сначала первая Int) выполнена намного лучше. Или второй гораздо хуже. Согласно результату критерия:

benchmarking truncate/truncate1
mean: 25.42399 ns, lb -47.40484 ps, ub 67.87578 ns, ci 0.950
std dev: 145.5661 ns, lb 84.90195 ns, ub 244.2057 ns, ci 0.950
found 197 outliers among 100 samples (197.0%)
  97 (97.0%) low severe
  100 (100.0%) high severe
variance introduced by outliers: 99.000%
variance is severely inflated by outliers

benchmarking truncate/truncate2
mean: 781.0604 ns, lb 509.3264 ns, ub 1.086767 us, ci 0.950
std dev: 1.436660 us, lb 1.218997 us, ub 1.592479 us, ci 0.950
found 177 outliers among 100 samples (177.0%)
  77 (77.0%) low severe
  100 (100.0%) high severe
variance introduced by outliers: 98.995%
variance is severely inflated by outliers

Честно говоря, я только начал использовать Criterion, поэтому я не эксперт, использующий его, но я понимаю, что 25.42399 ns - это меньшее время выполнения, чем 781.0604 ns. Я подозреваю, что здесь есть определенная специализация. Это truncate2 слишком медленно? В случае, может ли truncate быть улучшенным? Кроме того, кто-нибудь знает еще более быстрый способ? Я чувствую, что делаю что-то неправильное кастинг на тип, который я действительно не использую.

Спасибо заранее.

Я компилирую с GHC-7.4.2, оптимизация включена (-O2).

Ответ 1

Во-первых, обратите внимание, что модуль GHC.Word включает в себя следующие RULE прагма:

"truncate/Double->Word16"
    forall x. truncate (x :: Double) = (fromIntegral :: Int -> Word16) (truncate x)

Это простое правило перезаписи, которое позволяет точно оптимизировать ваш truncate1. Поэтому у нас есть несколько вопросов, которые следует учитывать:

Почему это вообще оптимизация?

Поскольку стандартная реализация truncate является общей, для поддержки любого экземпляра Integral. Разница в скорости, которую вы видите, - это стоимость этой общности; в конкретном случае усечения одного примитивного типа в другой существует гораздо более быстрый метод.

Итак, кажется, что truncate1 извлекается из специализированной формы, а truncate2 - нет.

Почему truncate1 быстрее?

В GHC.Float, где указан экземпляр RealFrac для Double, мы имеем следующую RULE прагму:

"truncate/Double->Int"              truncate = double2Int

Где double2Int - оптимизированная форма, которую мы хотим. Сравните это с ранее упомянутым RULE - по-видимому, нет такой примитивной операции, специально для преобразования Double в Word16.

Почему не перезаписывается truncate2?

Цитата Руководство пользователя GHC:

В настоящее время GHC использует очень простой синтаксический алгоритм для сопоставления правила LHS с выражением. Он ищет замену, которая делает LHS и выражение синтаксически равномерным альфа-преобразованием. Шаблон (правило), но не выражение, является, если необходимо, расширением eta.

Согласованные выражения не являются eta-расширенными, то есть соответствие правил на forall x. foo x будет соответствовать в bar y = foo y, но не в bar = foo.

Так как ваши определения записаны без точек, соответствует RULE для Double -> Int, но RULE для Double -> Word16 не работает.