Профилировал этот ваш пример, и кажется, что класс Stream
(ну ... какая-то анонимная функция, связанная с ним - забыл свое имя, так как visualvm обрушился на меня) занимает большую часть кучи. Это связано с тем, что Stream
s в Scala имеют утечку памяти - см. Scala Trac # 692 . Исправления в Scala 2.8. . Комментарий EDIT: Daniel справедливо указывает на то, что он не связан с этой ошибкой. Это потому, что «val ints
указывает на заголовок потока, сборщик мусора не может ничего собрать» [ Даниэль ]. Тем не менее, я нашел, что комментарии в этом сообщении об ошибке приятно читать в связи с этим вопросом.
В вашей функции добавления вы держите ссылку на a.head
, поэтому сборщик мусора не может собрать заголовок, что приводит к потоку, который содержит 9999998 элементов в конце, который не может быть GC-ed.
[Маленькая интерлюдия]
Вы также можете хранить копии хвостов, которые продолжаете передавать, я не уверен, как Stream
с этим справляются. Если бы вы использовали список, то хвосты не были бы скопированы. Например:
val xs = List(1,2,3)
val ys = 1 :: xs
val zs = 2 :: xs
Здесь и ys
, и zs
'разделяют' один и тот же хвост, по крайней мере, в куче (ys.tail eq zs.tail
, то есть равенство эталонного равенства true
).
[Эта небольшая интерлюдия состояла в том, чтобы показать, что прохождение большого количества хвостов в принципе не так уж и плохо :), они не копируются, по крайней мере, для списков]
Альтернативная реализация (которая работает довольно быстро, и я думаю, что она более понятна, чем чисто функциональная) - это использовать императивный подход:
def addTo(n: Int, init: Int): Long = {
var sum = init.toLong
for(i <- 1 to n) sum += i
sum
}
scala> addTo(9999998, 0)
В Scala вполне нормально использовать императивный подход для производительности и ясности (по крайней мере, мне эта версия add
более понятна для ее намерений). Для еще большей краткости вы можете написать
(1 to 9999998).reduceLeft(_ + _)
(работает немного медленнее, но все же разумно и не взрывает память)
Я считаю, что Clojure может быть быстрее, поскольку он полностью функционален, поэтому возможна большая оптимизация, чем в Scala (которая сочетает в себе функциональность, OO и императив). Хотя я не очень знаком с Clojure.
Надеюсь, это поможет:)