스칼리 :: call by name, call by value
Evalution in Scala
스칼라에서 자주 쓰이지만 햇갈리는 개념이 있어 정리한다.
- call-by-name
- call-by-value
값을 계산하는 데 소요되는 시간이 작은 경우에는 위의 차이가 별로 나지 않기 때문에 큰 데이터를 입력받을 때를 기준을 정리하려고 한다.
아래의 예에서는 값을 계산하는 과정을 평가라고도 부를 것이다. 여기에서 등장하는 tree는 백 만개의 노드를 가진 트리 데이터를 집어넣었다.
=> arg vs arg
함수의 인자로 들어가는 모든 값들은 함수가 call되어 body가 실행되기 직전에 값이 계산되어 내부적으로 val로 저장된다.
evalTest (tree : MyTree[Int]) = {
}
//argument evaluation time : 538 ms
인자 tree가 def, val, lazy val 중 어떤 것으로 정의되었던지 함수에 입력된 순간에는 전혀 상관이 없다. 함수로 들어와 함수가 실행되는 순간 평가를 거치게 된다. 또한 한 번 평가가 이루어지면 함수 내부에서는 tree가 val tree처럼 사용된다. 따라서 처음 인자의 값을 계산하는 데 많은 시간을 소비하게 되지만 이 후에는 계산시간이 들지 않는다.
evalTest (tree : => MyTree[Int]) = {
}
// argument evaluation time : 3 ms
위의 함수는 =>을 사용한다. tree의 입력으로 def, val, lazy val 어떤 것이 오더라도 val이 된다고 했지만 => 을 사용하면 계산을 다음으로 미룬다. 즉 내부적으로는 lazy val tree 로 저장하는 것과 같다. argument evaluation time이 크게 줄은 것을 볼 수 있다.
def vs val
def 와 val의 차이는 2 가지가 있다.
- def x
def evalTest (tree : => MyTree[Int]) : Int = {
val t0 = System.nanoTime()
def x = tree.iter
val t1 = System.nanoTime()
println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
//evaluation time : 0 ms
argument에서 값의 평가를 한 번 미루었기 때문에 def x 에서 평가되어야 하지만 def를 사용해서 평가를 다음으로 미룬다. 따라서 시간이 거의 들지 않게 된다.
- val x
def evalTest (tree : => MyTree[Int]) : Int = {
val t0 = System.nanoTime()
val x = tree.iter
val t1 = System.nanoTime()
println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
// evaluation time : 683 ms
argument에서 미룬 값의 평가를 val x에서 해야 하기 때문에 evaluation time이 크게 드는 것을 볼 수 있다.
- 차이점
def와 val의 또 다른 차이는 val은 한 번 계산해 놓으면 다음에 사용할 때 계산해 놓은 값을 사용하지만 def는 매번 사용할 때마다 다시 계산한다.
cf)
참고로 입력받을 때 함수 인자를 =>로 받지 않으면 val로 이미 한번 평가가 이루어지기 때문에 def x이든 val x 이든 evaluation time은 0에 가깝게 든다.
def evalTest (tree : MyTree[Int]) : Int = {
val t0 = System.nanoTime()
val x = tree.iter
val t1 = System.nanoTime()
println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
// evaluation time : 1 ms
def vs lazy val
def 와 lazy val의 차이는 간단하다. lazy val과 def는 모두 평가를 다음으로 미룬다는 공통점이 있지만 def가 매번 평가하는 것에 반해 lazy val은 한 번 평가가 일어난 후에 더 이상 평가가 이루어지지 않고 val처럼 행동한다.
lazy val이 처음 평가에서 exception을 일으켰을 때는 다음에 한 번 더 평가가 이루어지기도 한다.
테스트 한 코드
abstract class Iter[A]{
def getValue : Option[A]
def getNext : Iter[A]
}
abstract class Iterable[A] {
def iter : Iter[A]
}
def sumElement[A] (f : A => Int) (xs : Iter[A]) : Int = {
xs.getValue match {
case Some(n) => f(n) + sumElement (f) (xs.getNext)
case None => 0
}
}
def sumElementGen[A](f : A => Int)(itr : Iterable[A]): Int ={
sumElement(f)(itr.iter)
}
def rev[A](f : MyList[A])(result : MyList[A]) : MyList[A] = {
f match {
case MyNil() => result
case MyCons(hd, tl) => rev(tl)(MyCons(hd, result))
}
}
def concat[A] (f : MyList[A], s : MyList[A]) : MyList[A] = {
f match {
case MyNil() => s
case MyCons(hd, tl) => concat(tl, MyCons(hd, s))
}
}
sealed abstract class MyList[A] extends Iter[A] {
def concatenate(l : MyList[A]) = concat(rev(this)(MyNil()), l)
}
case class MyCons[A](value : A, next : MyList[A] ) extends MyList[A]{
def getValue = Some(value)
def getNext = next
}
case class MyNil[A]() extends MyList[A]{
def getValue = None
def getNext = throw new Exception ("Null point error")
}
sealed abstract class MyTree[A] extends Iterable[A] {
def iter : MyList[A]
}
case class Empty[A]() extends MyTree[A] {
val iter : MyList[A] = MyNil()
}
case class Node[A](value : A, left: MyTree[A], right: MyTree[A]) extends MyTree[A]{
val iter : MyList[A] = MyCons(value, concat[A](left.iter, right.iter))
}
def generateTree(n: Int) : MyTree[Int] = {
def gen(lbnd : Int, ubnd : Int) : MyTree[Int]= {
if (lbnd > ubnd) Empty()
else {
val mid = (lbnd + ubnd) / 2
Node(mid, gen(lbnd, mid - 1), gen(mid + 1, ubnd))
}
}
gen(1, n)
}
def time[R] (func : => R) : R = {
val t0 = System.nanoTime()
val result = func
val t1 = System.nanoTime()
println("Elapsed time: " + ((t1 - t0)/1000000) + "ms"); result
}
def sumN[A] (f : A => Int) (xs :Iterable[A]) : Int = {
def aux[A](f : A => Int)(x : Iter[A], res : Int) : Int = {
x.getValue match {
case Some(a) => aux(f)(x.getNext, res + f(a))
case None => res
}
}
aux(f)(xs.iter, 0)
}
def evalTest (tree : MyTree[Int])(st : Long) : Int = {
val t0 = System.nanoTime()
println("\n\nargument evaluation time : " + ((t0 - st)/1000000) + " ms\n")
def x = tree.iter
val t1 = System.nanoTime()
println("evaluation time : " + ((t1 - t0)/1000000) + " ms\n")
val et0 = System.nanoTime()
if (tree.iter.getValue != None)
if(tree.iter.getValue != None)
if(tree.iter.getValue != None)
println("")
tree.iter.getValue match {
case Some(n) =>
val et1 = System.nanoTime()
println("evaluation time : " + ((et1 - et0)/1000000) + " ms\n")
n
case None =>
val et1 = System.nanoTime()
println("evaluation time : " + ((et1 - et0)/1000000) + " ms\n")
0
}
}
def y = generateTree(1000000)
val start_time = System.nanoTime()
evalTest(y)(start_time)
댓글
댓글 쓰기