Ответ 1
Когда вы вызываете такую функцию
f 42 (g x y)
тогда поведение во время выполнения выглядит примерно так:
p1 = malloc(2 * sizeof(Word))
p1[0] = &Tag_for_Int
p1[1] = 42
p2 = malloc(3 * sizeof(Word))
p2[0] = &Code_for_g_x_y
p2[1] = x
p2[2] = y
f(p1, p2)
То есть аргументы обычно передаются как указатели на объекты в куче, как в Java, но в отличие от Java эти объекты могут представлять собой приостановленные вычисления, a.k.a. thunks, такие как (g x y
/p2
) в нашем примере. Без оптимизаций эта модель исполнения довольно неэффективна, но есть способы избежать многих из этих накладных расходов.
-
GHC делает много вложений и распаковки. Inlining удаляет служебные вызовы функции и часто позволяет продолжить оптимизацию. Unboxing означает изменение соглашения о вызове, в приведенном выше примере мы могли бы передать
42
непосредственно вместо создания объекта кучиp1
. -
Анализ жесткости выясняет, будет ли оцениваться аргумент. В этом случае нам не нужно создавать thunk, но полностью оценивайте выражение, а затем передавайте конечный результат в качестве аргумента.
-
Малые объекты (в настоящее время только 8 бит
Char
иInt
s) кэшируются.То есть вместо выделения нового указателя для каждого объекта возвращается указатель на кешированный объект.Даже если объект изначально выделен в куче, сборщик мусора будет дедуплицировать их позже ( только малыеInt
иChar
s). Поскольку объекты неизменяемы, это безопасно. -
Ограниченный анализ побега. Для локальных функций некоторые аргументы могут передаваться в стек, поскольку они известны как мертвый код к моменту возврата внешней функции.
Изменить. Более подробную информацию см. в "Внедрение" ленивых функциональных языков на складе ":" Бесконечная безматрица "G-machine" . В этом документе термин "push/enter" используется как соглашение о вызове. В более новых версиях GHC используется соглашение об использовании "eval/apply". Для обсуждения компромиссов и причин этого переключателя см. "Как сделать быстрое карри: push/enter vs eval/apply"