Профиль кучи здесь не очень интересен, поскольку GHC компилирует fib
в функцию, которая работает исключительно в стеке. Просто посмотрите на профиль ... Когда-либо выделяется только 800 байтов, это небольшие накладные расходы вашей main
реализации.
Что касается уровня ядра GHC, то он фактически оптимизируется настолько, насколько это возможно. Генерация кода низкого уровня - другое дело. Давайте быстро погрузимся в код, который генерирует GHC:
_Main_zdwfib_info:
.Lc1RK:
leal -8(%ebp),%eax
cmpl 84(%ebx),%eax
jb .Lc1RM
Это проверка стека. Вероятно, что-то C не нужно, поскольку это может позволить операционной системе обрабатывать выделение стекового пространства. На Haskell есть потоки уровня пользователя, поэтому управление пространством стека осуществляется вручную.
cmpl $2,0(%ebp)
jl .Lc1RO
Сравнение с 2 из вашего кода.
movl 0(%ebp),%eax
decl %eax
Параметр перезагружается из стека и уменьшается для получения параметра для рекурсивного вызова. Перезагрузка, вероятно, не нужна, хотя я не уверен, что это что-то меняет.
movl %eax,-8(%ebp)
movl $_s1Qp_info,-4(%ebp)
addl $-8,%ebp
jmp _Main_zdwfib_info
Параметр и адрес возврата помещаются в верхнюю часть стека, и мы переходим непосредственно к метке для рекурсии.
.Lc1RO:
movl $1,%esi
addl $4,%ebp
jmp *0(%ebp)
Код для случая, когда параметр меньше 2. Возвращаемое значение передается в регистр.
Итог: кажется, все работает так, как должно, вряд ли вы сможете выжать из этого гораздо больше, изменив программу. Пользовательская проверка стека является очевидным источником замедлений, хотя и не уверен, что его можно обвинить в полной разнице во времени.