Ответ 1
Позвольте мне сначала ответить, почему mapAndSome
может работать хорошо: то, что вы видите (очень вероятно), влияет на оптимизацию, описанную Филиппом Вадлером в " Устранение некоторых утечек пространства с сборщиком мусора". Краткое описание: Если сборщик мусора видит, что тень формы fst x
и x
уже оценивается конструктором кортежа, например. (y,z)
, он заменит fst x
на y
, возможно высвобождая z
, если он не упоминается нигде.
В вашем коде s'
будет, когда результат go
будет оцениваться кортежем и после одного раунда GCing не будет содержать ссылку на кортеж, но будет заменен накопленным параметром.
Теперь рассмотрим другие шаблоны, исследуя ядро. Связывание foo
скомпилировано с помощью:
foo_r2eT :: ([Type.Integer], Type.Integer)
foo_r2eT =
case $wgo_r2eP mapAndSum1 lvl2_r2eS
of _ { (# ww1_s2d7, ww2_s2d8 #) ->
(ww1_s2d7, ww2_s2d8)
}
Вот код в случае "bad"
(lvl18_r2fd
"плохой" ):
case eqString ds_dyA lvl18_r2fd of _ {
False -> $wa_s2da new_s_a14o;
True ->
case ds1_dyB of _ {
[] ->
case Handle.Text.hPutStr2
Handle.FD.stdout lvl17_r2fc True new_s_a14o
of _ { (# new_s1_X15h, _ #) ->
Handle.Text.hPutStr2
Handle.FD.stdout lvl16_r2fb True new_s1_X15h
};
: ipv_sIs ipv1_sIt -> $wa_s2da new_s_a14o
}
Мы видим, что печатаются два значения на уровне модуля, lvl17_r2fc
и lvl16_r2fb
, вот их код:
lvl17_r2fc :: String
[GblId]
lvl17_r2fc =
case foo_r2eT of _ { (xs_Xqp, s_Xq9) ->
$w$cshowsPrec
0
(Data.List.sum_sum' xs_Xqp Data.List.genericDrop2)
([] @ Char)
}
lvl16_r2fb :: String
[GblId]
lvl16_r2fb =
case foo_r2eT of _ { (xs_apS, s_Xqp) ->
$w$cshowsPrec 0 s_Xqp ([] @ Char)
}
Почему они связаны на уровне модуля, а не внутри выражения? Это эффект ленивого подъема, другая оптимизация, которая увеличивает общий доступ и, следовательно, когда-то оказывает негативное влияние на производительность пространства. См. билет GHC 719 для другого появления этого эффекта.
Итак, происходит то, что оценка lvl17_r2fc
вызывает оценку foo
, а левая запись лениво печатается. К сожалению, lvl16_r2fb
все еще жив и сохраняет полный кортеж. И поскольку сборщик мусора (кажется) не видит, что это селекторный удар, оптимизация Wadlers не срабатывает.
Напротив, здесь приведен код для "good1"
a.k.a. lvl8_r2f1
:
case eqString ds_dyA lvl8_r2f1 of _ {
False -> $wa2_s2dI w3_s2cF;
True ->
case ds1_dyB of _ {
[] ->
Handle.Text.hPutStr2
Handle.FD.stdout lvl7_r2f0 True w3_s2cF;
: ipv_sHg ipv1_sHh -> $wa2_s2dI w3_s2cF
}
} } in
где напечатанное значение - это строка:
lvl7_r2f0 :: String
[GblId]
lvl7_r2f0 =
case foo_r2eT of _ { (x_af6, y_af7) ->
show_tuple
(:
@ ShowS
(let {
w2_a2bY [Dmd=Just L] :: Type.Integer
w2_a2bY = lgo_r2eU mapAndSum1 x_af6 } in
\ (w3_a2bZ :: String) ->
$w$cshowsPrec 0 w2_a2bY w3_a2bZ)
(:
@ ShowS
(\ (w2_a2bZ :: String) ->
$w$cshowsPrec 0 y_af7 w2_a2bZ)
([] @ ShowS)))
([] @ Char)
}
Как вы видите, кортеж разбирается только один раз, и оба значения используются. Поэтому ничто не относится к кортежу в целом, и это может быть сбор мусора. Аналогично для "good2"
и "good3"
.
Теперь в "bad?"
: в неоптимизированном случае мы получим этот код:
case eqString ds_dyA (unpackCString# "bad?")
of _ {
False -> fail2_dyN realWorld#;
True ->
case ds1_dyB of _ {
[] ->
$
@ (Type.Integer, Type.Integer)
@ (IO ())
(System.IO.print
@ (Type.Integer, Type.Integer) $dShow_rzk)
($
@ ([Type.Integer], Type.Integer)
@ (Type.Integer, Type.Integer)
(Control.Arrow.first
@ (->)
Control.Arrow.$fArrow(->)
@ [Type.Integer]
@ Type.Integer
@ Type.Integer
sum'_rzm)
foo_rzl);
: ipv_szd ipv1_sze -> fail2_dyN realWorld#
}
} } in
В реализация из first
через ***
используются опровержимые шаблоны, поэтому сгенерирован тип селектора, который хорошо обрабатывает сборщик мусора.
В оптимизированном случае вещи немного разбросаны, но в любом случае здесь есть соответствующий код (последнее значение - это то, которое печатается):
w_r2f2 :: Type.Integer
w_r2f2 =
case foo_r2eT of _ { (x_aI1, y_aI2) ->
lgo_r2eU mapAndSum1 x_aI1
}
lvl9_r2f3 :: String -> String
[GblId, Arity=1]
lvl9_r2f3 =
\ (w2_a2bZ :: String) ->
$w$cshowsPrec 0 w_r2f2 w2_a2bZ
w1_r2f4 :: Type.Integer
w1_r2f4 = case foo_r2eT of _ { (x_aI6, y_aI7) -> y_aI7 }
lvl10_r2f5 :: String -> String
[GblId, Arity=1]
lvl10_r2f5 =
\ (w2_a2bZ :: String) ->
$w$cshowsPrec 0 w1_r2f4 w2_a2bZ
lvl11_r2f6 :: [ShowS]
[GblId]
lvl11_r2f6 =
:
@ ShowS lvl10_r2f5 ([] @ ShowS)
lvl12_r2f7 :: [ShowS]
[GblId]
lvl12_r2f7 = : @ ShowS lvl9_r2f3 lvl11_r2f6
lvl13_r2f8 :: ShowS
[GblId]
lvl13_r2f8 = show_tuple lvl12_r2f7
lvl14_r2f9 :: String
[GblId]
lvl14_r2f9 = lvl13_r2f8 ([] @ Char)
Использование first
было включено. Мы видим два вызова case foo_r2eT
, поэтому это подвержено утечке пространства, несмотря на то, что w1_rf24
выглядит как селектор (так что Id ожидает, что среда выполнения применит оптимизацию Wadlers). Может быть, это не работает для статических трюков? Действительно, commentary, если он обновлен, говорит только о динамических выделенных селекторах. Поэтому, если ваш foo
не был значением уровня модуля (или, скорее, лениво-лифтингом в один), а скорее зависел от некоторого ввода, w1_rf24
мог бы быть динамически распределен и, следовательно, иметь право на специальное лечение. Но тогда код может выглядеть очень по-другому.