Почему небольшие изменения в этом Scala-коде так сильно влияют на производительность?
Я использую 32-разрядную систему Debian 6.0 (Squeeze) (процессор Core 2 с частотой 2,5 ГГц), sun-java6 6.24-1, но с пакетами Scala 2.8.1 от Wheezy.
Этот код, скомпилированный с scalac -optimise
, для запуска требуется более 30 секунд:
object Performance {
import scala.annotation.tailrec
@tailrec def gcd(x:Int,y:Int):Int = {
if (x == 0)
y
else
gcd(y%x,x)
}
val p = 1009
val q = 3643
val t = (p-1)*(q-1)
val es = (2 until t).filter(gcd(_,t) == 1)
def main(args:Array[String]) {
println(es.length)
}
}
Но если я сделаю тривиальное изменение перемещения val es=
одной строкой вниз и внутри области видимости main
затем он запускается всего за 1 секунду, что намного больше, чем я ожидал увидеть, и сопоставимо с производительностью эквивалентного C++. Интересно, что оставив val es=
где это, но квалифицируя это с lazy
также имеет такой же ускоряющий эффект.
Что тут происходит? Почему выполнение вычислений вне области функций происходит намного медленнее?
2 ответа
JVM не оптимизирует статические инициализаторы (что и есть) до того же уровня, что оптимизирует вызовы методов. К сожалению, когда вы там много работаете, это ухудшает производительность - это прекрасный пример этого. Это также одна из причин, почему старый Application
черта была признана проблемной, и почему в Scala 2.9 есть DelayedInit
черта, которая получает немного компилятора, помогает перемещать вещи из инициализатора в метод, который вызывается позже.
(Правка: исправлен "конструктор" в "инициализаторе". Довольно длинная опечатка!)
Код внутри блока объекта верхнего уровня транслируется в статический инициализатор класса объекта. Эквивалент в Java будет
class Performance{
static{
//expensive calculation
}
public static void main(String[] args){
//use result of expensive calculation
}
}
HotSpot JVM не выполняет никаких оптимизаций для кода, встречающегося во время статических инициализаторов, при разумной эвристике, что такой код будет запущен только один раз.