Skip to content

Instantly share code, notes, and snippets.

@klapaucius
Created November 17, 2011 18:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save klapaucius/1374044 to your computer and use it in GitHub Desktop.
Save klapaucius/1374044 to your computer and use it in GitHub Desktop.
! vs unsafeIndex

Ответ на вопрос в этом посте.

Для начала слегка модифицируем код, чтоб он компилировался.

import qualified Data.List.Stream as S
import qualified Data.Vector.Unboxed as U

f n = let arr = U.enumFromTo 1 n 
      in S.sum $ S.map (\i -> arr U.! (i `rem` n)) $ S.unfoldr (\i -> if i < n then Just (i, i+1) else Nothing) 0

main = print $ f 100000000

Запустив скомпилированный с -O2 код мы получим такой вот отчет:

   2,006,833,012 bytes allocated in the heap
           4,984 bytes copied during GC
     400,026,076 bytes maximum residency (2 sample(s))
         566,376 bytes maximum slop
             383 MB total memory in use (0 MB lost due to fragmentation)

  Generation 0:  3062 collections,     0 parallel,  0.02s,  0.02s elapsed
  Generation 1:     2 collections,     0 parallel,  0.00s,  0.00s elapsed

  INIT  time    0.00s  (  0.13s elapsed)
  MUT   time    1.23s  (  1.67s elapsed)
  GC    time    0.02s  (  0.02s elapsed)
  EXIT  time    0.00s  (  0.00s elapsed)
  Total time    1.25s  (  1.81s elapsed)

  %GC time       1.2%  (1.1% elapsed)

  Alloc rate    1,628,383,629 bytes per MUT second

  Productivity  98.8% of total user, 68.0% of total elapsed

Легко видеть, что через эфемерное поколение со свистом пролетает 2 гигабайта сверхкороткоживущих объектов. Обратите внимание, что до копирования они не доживают.

И это при том, что выделять память в цикле тут, в общем-то, незачем. Конвейер ФВП фьюзится в хвосторекурсивную функцию, которая всю работу и делает. Если ее отчистить от всяких Core-излишеств и подсократить несущественное, то вот что мы получаем:

loop_sum :: Int# -> Int# -> Int#
loop_sum s i =
	case i <# 100000000 of
	  False -> s
	  True -> 
	  	let rem :: Int#
	        rem = remInt# i 100000000 in
	    let boxed_rem :: Int
	        boxed_rem = I# rem in
	    let boxed_n :: Int
	        boxed_n = I# n in
		case rem >=# 0 of
	        False -> error1	... (checkIndex_msg	boxed_rem boxed_n)) ...
	        True ->
	          case rem <# n of
	            False -> error1 ...
	            True ->	loop_sum 
					(s +# (indexIntArray# byteArray (start +# rem)))
	                (i +# 1)

Видно, что все вычисления производятся с анбоксед-типами, в куче память для этого не выделяется. А выделяется она для того, чтоб передать индексы в развесистую функцию error1 (если бы я скопировал ее сюда со всеми аргументами - код бы вырос раза в два) сообщающую о выходе за границы массива. При этом память в куче для этих несчастных индексов выделяется не после того, когда выход за границы обнаружен, а всегда. Зарание. За что, спросите вы? А просто так.

Ну и, понятное дело, если заменить ! на unsafeIndex, то ничего в куче выделяться в этой worker-функции не будет:

     400,607,500 bytes allocated in the heap
           2,376 bytes copied during GC
          26,152 bytes maximum residency (1 sample(s))
          19,708 bytes maximum slop
             383 MB total memory in use (0 MB lost due to fragmentation)

  Generation 0:     1 collections,     0 parallel,  0.00s,  0.00s elapsed
  Generation 1:     1 collections,     0 parallel,  0.00s,  0.06s elapsed

  INIT  time    0.00s  (  0.00s elapsed)
  MUT   time    0.80s  (  1.06s elapsed)
  GC    time    0.00s  (  0.06s elapsed)
  EXIT  time    0.00s  (  0.00s elapsed)
  Total time    0.80s  (  1.12s elapsed)

  %GC time       0.0%  (5.0% elapsed)

  Alloc rate    503,525,618 bytes per MUT second

  Productivity 100.0% of total user, 71.3% of total elapsed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment