Подтвердить что ты не робот

Почему performGC не может освободить всю память?

Учитывая программу:

import Language.Haskell.Exts.Annotated -- from haskell-src-exts
import System.Mem
import System.IO
import Control.Exception

main :: IO ()
main = do
  evaluate $ length $ show $ fromParseResult $ parseFileContents $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}     }"
  performGC
  performGC
  performGC

Использование GHC 7.0.3, когда я запускаю:

$ ghc --make Temp.hs -rtsopts && Temp.exe +RTS -G1 -S
    Alloc    Copied     Live    GC    GC     TOT     TOT  Page Flts
    bytes     bytes     bytes  user  elap    user    elap
 ...
 29463264        64   8380480  0.00  0.00    0.64    0.85    0    0  (Gen:  0)
       20        56   8380472  0.00  0.00    0.64    0.86    0    0  (Gen:  0)
        0        56   8380472  0.00  0.00    0.64    0.87    0    0  (Gen:  0)
    42256       780     33452  0.00  0.00    0.64    0.88    0    0  (Gen:  0)
        0                      0.00  0.00

Похоже, что вызов performGC оставляет 8 Мб памяти вживую, хотя кажется, что вся память должна быть мертвой. Почему?

(Без -G1 я вижу 10Mb вживую в конце, что я также не могу объяснить.)

4b9b3361

Ответ 1

Вот что я вижу (после вставки print перед последним performGC, чтобы помочь пометить, когда что-то произойдет.

   524288    524296  32381000  0.00  0.00    1.15    1.95    0    0  (Gen:  0)
   524288    524296  31856824  0.00  0.00    1.16    1.96    0    0  (Gen:  0)
   368248       808   1032992  0.00  0.02    1.16    1.99    0    0  (Gen:  1)
        0       808   1032992  0.00  0.00    1.16    1.99    0    0  (Gen:  1)
"performed!"
    39464      2200   1058952  0.00  0.00    1.16    1.99    0    0  (Gen:  1)
    22264      1560   1075992  0.00  0.00    1.16    2.00    0    0  (Gen:  0)
        0                      0.00  0.00

Итак, после GCs все еще 1M в куче (без -G1). С -G1 я вижу:

 34340656  20520040  20524800  0.10  0.12    0.76    0.85    0    0  (Gen:  0)
 41697072  24917800  24922560  0.12  0.14    0.91    1.01    0    0  (Gen:  0)
 70790776       800   2081568  0.00  0.02    1.04    1.20    0    0  (Gen:  0)
        0       800   2081568  0.00  0.00    1.04    1.20    0    0  (Gen:  0)
"performed!"
    39464      2184   1058952  0.00  0.00    1.05    1.21    0    0  (Gen:  0)
    22264      2856     43784  0.00  0.00    1.05    1.21    0    0  (Gen:  0)
        0                      0.00  0.00

Так около 2M. Это на x86_64/Linux.

Подумайте о модели хранения машин STG, чтобы узнать, есть ли что-то еще в куче.

Вещи, которые могут быть в этом 1M пространства:

  • CAF для таких вещей, как [], строковые константы и небольшой пул Int и Char, а также вещи в библиотеках, stdin MVar?
  • Объекты состояния потока (TSOs) для потока main.
  • Любые выделенные обработчики сигналов.
  • Код Haskell менеджера IO.
  • Искры в искровом пуле

Из опыта эта цифра чуть меньше 1 М кажется по умолчанию "следностью" двоичного кода GHC. Это о том, что я видел и в других программах (например, программа перестрелки с наименьшими отпечатками не менее 900K).

Возможно, профайлер может что-то сказать. Здесь профиль -hT (без необходимости профилирования libs), после того, как я вставляю в конец минимальный цикл занятости, чтобы вывести хвост:

 $ ./A +RTS -K10M -S -hT -i0.001    

Результаты на этом графике:


enter image description here


Победа! Посмотрите на этот объект стека ~ 1M, сидящий там!

Я не знаю, как уменьшить TSOs.


Код, создавший приведенный выше график:

import Language.Haskell.Exts.Annotated -- from haskell-src-exts
import System.Mem
import System.IO
import Data.Int
import Control.Exception

main :: IO ()
main = do
  evaluate $ length $ show $ fromParseResult 
           $ parseFileContents 
           $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}     }"
  performGC
  performGC
  print "performed!"
  performGC

  -- busy loop so we can sample what left on the heap.
  let go :: Int32 -> IO ()
      go  0 = return ()
      go  n = go $! n-1
  go (maxBound :: Int32)

Ответ 2

Компиляция кода с помощью -O -ddump-simpl, я вижу следующее глобальное определение в выводе упрощения:

lvl2_r12F :: [GHC.Types.Char]
[GblId]
lvl2_r12F =
  GHC.Base.unpackAppendCString# "data C = C {a :: F {- " lvl1_r12D

Ввод функции анализатора стал глобальной строковой константой. Глобалы никогда не собираются мусором в GHC, поэтому, возможно, что занимает 8 МБ памяти после сбора мусора.