본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin] Kotlin 의 숨겨진 비용 #3

by 돼지왕 왕돼지 2018. 1. 18.
반응형

[Kotlin] Kotlin 의 숨겨진 비용 #3


https://medium.com/@BladeCoder/exploring-kotlins-hidden-costs-part-3-3bf6e0dbf0a4


!in, .., 2개 이상의 함수, API, array, assign, Boxing, by, checkParametersIsNotNull, class constructor, closedrange, closedrange interface, collection, collection indices, Comparable, constant, delegate instance, delegated property, delegates.notnull, Delegates.observable, direct call, double-chck lock, downto, extension fucntion, extension function, extension function delegate, for loop, foreach, FragmentDelegate, Full, fun, function, generic delegate, generic delegates, getvalue, in, inclusion test, increase, indices extension property, inline, intarray, integral type range, intrinsics, iterable, iterate, Kotlin, kotlin hidden cost, kotlin overhead, kproperty, LAMBDA, Lazy, lazy inline, lazythreadsafetymode, LazyThreadSafetyMode.NONE, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.SYNCHRONIZED, local delegate, local variable delegate, map, map delegate, metadata overhead, Mode, mutablemap, mutablemap delegate, mutableproperty0, mutableproperty1, mutbleproperty, non-primitive type, None, nonnull primitive type, object instance, operator, operator function, optimize, optional argument, overhead, Primitive type, progression, property delegate, property metadata, property name key, property0, property1, publication, RANGE, range expression, read-only property, Reflection, setValue, simple form, single function, single thread, standard delegates, synchronized, unboxing, Until, when, [Kotlin] Kotlin 의 숨겨진 비용 #3, 돼지왕 왕돼지, 원리 이해, 이해, 최적화, 최적화된 코드


Delegated property


-

class Example{

    var p: String by Delegate()

}


property 에 delegate 를 사용할 경우에 해당 delegate 는 operator function 인 getValue 와 setValue 를 구현해야 한다.

해당 function 들은 object instance 와 property metadata 를 받는다.


public final class Example{

    @NonNull

    private final Delegate p$delegate = new Delegate();


    static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Example.class), “p”, “getP()Ljava/lang/String;”))};


    @NotNull

    public final String getP(){

        return this.p$delegate.getValue(this, $$delegatedProperties[0]);

    }


    public final void setP(@NotNull String var1){

        Intrinsics.checkParametersIsNotNull(var1, “<set-?>”);

        this.p$delegate.setValue(this, $$delegatedProperties[0], var1);

    }

}



-

Delegate instances


위의 예제에서 새로운 delegate instance 가 생성되는 것을 보았다.

이는 delegate instance 가 state 가 있을 때, delegate 가 추가 param 이 있을 때에 해당한다.

( 바로 아래 다루는데, 위의 조건이 아닌 경우에는 singleton 으로 만들어 재활용하면 된다. )



-

만약 stateless 하면서 하나의 single instance 로 모든 instance 의 property 를 대응하고 싶다면 

delegate class 를 object keyword 를 사용해서 singleton 으로 만들어주면 된다.

object FragmentDelegate {

    operator fun getValue(thisRef: Activity, property: KProperty<*>): Fragment? {

        return thisRef.fragmentManager.findFragmentByTag(property.name)

    }

}



-

어떤 object 던지 delegate 로 확장시킬 수 있다.

getValue, setValue 가 extension function 으로 정의 가능하기 때문이다.


Map, MutableMap 등이 대표적인 extension function 으로 확장된 delegate 이다.

그들은 property name 을 key 로 사용한다.



-

만약 한 local delegate 를 여러개의 property 의 delegate 로 쓰고 싶다면 (예를 들면 Map 의 경우)

class constructor 에서 초기화 해주어야 한다. 

( Kotlin 1.1 부터는 function 안의 local variable  을 delegated property 로 쓸 수 있게도 된다... )



-

모든 delegated property 는 관련된 delegate object 와 metadata 를 만드는 overhead 가 생긴다.

여러개의 property 에 delegates 를 재사용하는 것도 가능하며, 가능하다면 이 형태로 가는 것도 좋다.

사실 delegate 를 써야 하는지를 먼저 생각해보는 것이 좋다. 간단한 것이나 꼭 필요없는데 위임하는 케이스는 안 하는 것이 좋다.



-

Generic delegates


generic delegate 를 사용하는 경우 동일하게 작동한다.

하지만 primitive type 을 쓰는 경우에는 마찬가지로 boxing, unboxing 을 고려해야 한다.


그래서 generic delegate 대신 IntArray 와 같이 specialized 된 class 를 가져다 쓰거나 정의해서 쓰는 것이 좋을 수 있다.



-

Standard delegates: lazy()


Delegates.notNull(), Delegates.observable(), lazy() 와 같은 특별한 delegate 를 제공한다.



-

lazy() 는 read-only property 에 대해 lazy 하게 lambda 를 수행해서 값을 assign 해서 return 하도록 하는 역할을 한다.

private val dateFormat: DateFormat by lazy{

    SimpleDateFormat(“dd-MM-yyyy”, Locale.getDefault())

}


이 때 또 주의해야 할 것은 lazy 가 inline 이 아니기 때문에 lazy 에 전달되는 lambda 는 Function 으로 instantiate 된다.



-

lazy 를 사용될 때 간과되기 쉬운 점 중 하나는 lazy 가 “mode” 라는 optional argument 를 받는다는 것이다.

public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)


public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = 

    when(mode){

        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)

        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)

        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)

    }


default mode 는 LazyThreadSafetyMode.SYNCHRONIZED 이다.

그리고 이 로직은 비싼 double-check lock 을 사용한다.

따라서 single thread 에서만 사용된다는 것을 guarantee 할 수 있다면 NONE 으로 해주는 것이 좋다.







Ranges


-

Ranges 는 유한한 범위를 나타내는 특별한 녀석이다.

Range 의 value 는 Comparable 을 구현한 녀석이면 가능하다.

Range 문법을 사용하면 ClosedRange interface 를 구현한 녀석을 만들어낸다.



-

Inclusion tests


Range 를 사용하면 in ,!in 을 통해 포함관계를 알기 쉽다.

여기서 primitive type 에 대해서는 잘 최적화되어 작동한다.

if (i in 1..10){

    println(i)

}


if (1 <= i && i <= 10) {

    System.out.println(i)

}



-

when 에서도 잘 작동한다. overhead 도 없다.

val message = when(statusCode){

    in 200..299 -> “OK”

    int 300..399 -> “Find it somewhere else”

    else -> “Oops”

}



-

Range 는 direct call 이 아닐 경우 cost 가 발생할 수 있다.

private val myRange get() = 1..10


fun rangeTest(i: Int) {

    if ( i in myRnage ) {

        println( i )

    }

}


private final IntRange getMyRange(){

    return new IntRange(1, 10);

}


public final void rangeTest(int i){

    if( this.getMyRange().contains(i) ){

        System.out.println(i);

    }

}


따라서 non-null primitive type range 의 경우는 가능하다면 direct 로 쓰는 것이 더 좋을 수 있다.



-

non-primitive type 의 경우는 ClosedRange 가 항상 생성되며 다음과 같이 비교된다.

if (name in “Alfread”..”Alicia”){

    println(name)

}


if(RangesKt.rangeTo((Comparable)”Alfred”, (Comparable)”Alicia”).contains((Comparable)name)){

    System.out.println(name);

}


따라서 non-primitive type 의 경우에는 constant 로 만들어 새로운 ClosedRange 가 매번 생성되는 것을 막는 것이 좋다.



-

Iterations: for loops


Integral type ranges( Float, Double 을 제외한 primitive type ) 은 progress 를 가질 수 있다.

이 말은 해당 type 의 range 는 for loop 에서 사용될 수 있다는 것이다.

일반적인 increase 형태는 overhead 가 전혀 없다.

downTo 를 사용한 decrease 형태 역시 overhead 가 전혀 없다.



-

대부분의 plain 한 케이스에는 optimize 가 잘 되어 overhead 가 없는데..

다음과 같은 케이스에는 문제가 된다.

for (i in (1..10).reversed()){

    println(i)

}


compile 되면 아래와 같다고...

IntProgression var10000 = RangesKt.reversed((IntProgression)(new IntRange(1, 10)));

int i = var10000.getFirst();

int var3 = var10000.getLast();

int var4 = var10000.getStep();

if(var4 > 0) {

   if(i > var3) {

      return;

   }

} else if(i < var3) {

   return;

}


while(true) {

   System.out.println(i);

   if(i == var3) {

      return;

   }


   i += var4;

}


progression 을 만드는데 2개 "이상"의 함수를 사용하면 overhead 가 생긴다고 보면 된다.


따라서 for loop iterate 할 때 range expression 을 쓴다면

single function 만 쓰는 것이 좋다.

( .., downTo, until )



-

Iterations: forEach()


for loop 대신 forEach extension function 을 써서 iterate 하는 방법도 있다.

forEach 는 Iterable 에 대해서만 최적화가 되어 있다.

그래서 Range 에 대해 forEach 를 수행하면, Range 를 Iterable 로 치환해서 forEach 를 수행한다.


따라서 range 에 대해 iterate 할 때는 forEach 보다 for loop 가 더 좋다.



-

Iterations: collection indices


Kotlin 에서는 Array 와 Collection 에 대해 “indices" extension property 를 제공한다.

val list = listOf(“A”, “B”, “C”)

for ( i in last.indices ) {

    println( list[i] )

}


compile 하면 overhead 도 없다.


그렇다면.. 내가 만든 indices 도 잘 작동하지 않을까 하고 다른 class 에 IntRange 를 return 하는 extension function 을 추가하면 똑같이 overhead 없이 컴파일 될까?


그렇지 않다...

그래서 가능하면 indices 같은거 정의하지 말고 간단한 form 으로 사용해라.



-

Kotlin 의 숨겨진 비용을 최종적으로 보고 돼지왕 왕돼지가 결론을 내려보면..


1. Kotlin 이 발전함에 따라 최적화는 조금씩 이루어질 것이지만, 원리를 이해하고 가급적 최적화된 코드를 짜는 것이 좋다. 최적화를 할 수 없는 경우도 분명히 있을 것이고, 언제 최적화가 이루어질지도 모르기 때문!

2. 가급적 제공되는 api 와 기능을 정확하게 full 로 이해하고 쓰는 것이 좋다. (예를 들면 inline, const, primitive specific classes 등 )

3. 가급적 간단한 형태로 쓰는 것이 좋다. 괜히 꼬아서 쓰면 optimize 가 더 힘든가보다.




반응형

댓글