본문으로 바로가기

Java 8 Lambda를 이용한 lazy evaluation

category 개발이야기/Java 2017. 12. 13. 01:00
반응형


이전 포스팅에서 객체의 생성을 뒤로 미루는 방법에 대해서 얘기했었습니다.

이번 글에서는 메서드를 지연시키는 방법에 대해서 알아보겠습니다.


Java의 lazy execution

자바는 논리 operation을 평가할때 lazy execution을 사용합니다.
예를 들어
fn1() || fn2() 에서 fn1()의 return값이 true라면 fn2()는 실행하지 않습니다.
유사하게, fn1() && fn2() 에서 fn1()의 return값이 false 라면 fn2()를 실행하지 않습니다.

이는 불필요한 연산을 줄여서 속도를 높이는 역할을 하며, 더 효율적으로 동작하도록 합니다.

단, Java는 논리연산에서는 lazy order 또는 normal order를 사용하지만 메서드의 인수를 평가할 때는 eager order나 applicative order를 사용합니다.

즉 메서드를 실행하기 전에 인수에 대한 평가가 이미 끝나 있어야 된다는 말입니다.
만약에 인수로 들어온 값이 실제 메서드 내에서 사용되지 않는다면, 인수를 평가하는 작업으로 프로그램 실행에 드는 시간과 노력이 낭비됩니다.

문득 드는 생각으로는 인수를 람다로 넘기면 될것 같습니다.
물론 Lambda를 인수로 사용하면 메서드 내에서 실제 람다가 호출되는 순간에 평가되므로 laze order로 설계할 수 있습니다.

그렇다고 모든 인수를 람다로 wrapping하는것도 사실 바람직하지는 않습니다.

어떻게 람다를 이용하여 인수를 설계해야하는지 알아보시죠~


Eager evaluation

Evaluation이란 클래스를 만들고 evaluate라는 함수를 하나 만듭니다.
이때 evaluate() 메서드는 시간이 많이 걸리는 메서드라고 가정합니다.

public class Evaluation {
  public static boolean evaluate(final int value) {
    System.out.println("evaluating ..." + value);
    simulateTimeConsumingOp(2000);
    return value > 100;
  }
  //...

  public static void simulateTimeConsumingOp(final int millseconds) {
    try { 
      Thread.sleep(2000); 
    } catch(Exception ex) { throw new RuntimeException(ex); }
  }

일단 위 함수를 그냥 호출하도록 코드를 만들어 보겠습니다.

public static void eagerEvaluator(final boolean input1, final boolean input2) {
    System.out.println("eagerEvaluator called...");
    System.out.println("accept?: " + (input1 && input2));
  }

그리고 그냥 evaluate()를 인수로 넘겨서 호출합니다.
public static void main(final String[] args) {

    System.out.println("//" + "START:EAGER_OUTPUT");
    eagerEvaluator(evaluate(1), evaluate(2));
    System.out.println("//" + "END:EAGER_OUTPUT");
  }


결과는 아래와 같이 찍힙니다.
evaluating ...1
evaluating ...2
eagerEva;iatpr called...
accept?: false

위에서 메서드의 인자는 미리 평가된다고 얘기했습니다.

따라서 먼저 메서드 인자를 연산하는데 4초를 사용합니다. (각각 2초씩)

코드상으로 보면 내부적으로 논리연산을 하므로 첫번째 인자만 계산하고 그에 따라 두번째 인자를 계산할지를 결정하도록 하면 좋을것 같네요.

-

-

Lazy evaluation

그럼 의도했던 대로 바꿔보겠습니다.

가장 쉬운 방법은 인자에 boolean을 받고 함수 내부에서 논리식 부분에 evalute()를 호출하도록 하는 방법입니다.

어디까지나 람다를 사용하기 위한 예제이므로 여기서는 인자가 메서드 실행전에 실행되지 않도록 lambda로 변경하는 작업을 합니다.

public static void lazyEvaluator(final Supplier<Boolean> input1, final Supplier<Boolean> input2) {
    System.out.println("lazyEvaluator called...");
    System.out.println("accept?: " + (input1.get() && input2.get()));
  }
public static void main(final String[] args) {   

    System.out.println("//" + "START:LAZY_OUTPUT");
    lazyEvaluator(() -> evaluate(1), () -> evaluate(2));
    System.out.println("//" + "END:LAZY_OUTPUT");
  }


인자로 supplier를 받도록 수정했습니다. 그리고 호출할때는 evaluate() 함수를 람다로 한번 wrapping해 줍니다.

호출 결과는 아래와 같이 찍힙니다.

evaluating ...1
eagerEva;iatpr called...
accept?: false

따라서 수행시간이 2초로 줄었습니다.
사실 이런 상황이라면 성능면에서는 우수해 졌지만, 만약 어차피 내부에서 두개의 함수를 호출해서 쓰는 경우라면 굳이 번잡스럽게 람다를 써서 구현할 필요는 없습니다.
코드작성에 대한 수고는 람다를 사용하면서 더 부담스러워 지기 때문입니다.

반응형