Upgrade PyxisPub to v2

기존에 사용하고 있던 Hestia 테마가 2.0 버전으로 올라가면서 Gutenberg 에디터에 대한 호환이 되지만, 여전히 Gutenberg 가 가지고 있는 블록에 대한 지원은 반 정도만 지원하는 문제가 있었습니다.

그래서, Gutenberg 호환 및 블록 사용 가능 및 content-width 조정이 가능한 테마를 찾아보니 Atomic Blocks Theme 가 사용 가능한 것 같아 바로 적용을 시작했습니다.

따라서 이 글에서는 주요 변경 사항을 공지합니다.

로고 & 새 블로그 부제목

버전 2에 맞춰 블로그 부제목을 Developing History 에서 Development Life 로 변경하였습니다. 사용한 폰트는 Hanken round 이며 라이센스가 공개된 폰트를 사용했습니다.

사용한 색상은 #363a46(글자), #2e3340(원) 입니다.

단, 흰색의 P가 작아서 안 보이는 경우가 있기 때문에 실제 로고에 사용할 때에는 P 글자는 제외합니다.

나름대로 균형은 맞춘 것이라고 볼 수는 있을 것 같습니다(..)

테마 변경에 따른 구조 변경

기존 메인 페이지는 요약된 글 목록이 무한 스크롤 되게 처리되었지만, Atomic Blocks의 블로그 페이지에서는 글의 모든 내용이 펼쳐지므로 블로그에 접속할 때 성능 이슈가 있습니다.

따라서, 기존에 글 목록으로 제공하던 메인 페이지를 정적인 페이지로 제공하였습니다.

또한 하단에서 소개할 Full-width 지원을 위해 사이드바도 제외하였습니다. 카테고리 / 글 목록 / 히트 수에 대해서는 하단에 제공됩니다.

Full-Width 이미지 지원

Medium 처럼 이미지의 가로가 화면 기준 100%로 꽉 차게 하는 Full-width 지원을 시작했습니다. 또한 화면 기준 100% 이외에도 본문 기준 100%도 지원합니다.

라이센스 표기

사이트 맨 하단에 컨텐츠 라이센스에 대해 표기하였습니다.

그 외

그 외 사항으로는 사용하지 않는 이미지 70개에 대한 삭제 및 Opcache, CSS Cache 등을 비워 서버가 무리하지 않도록 반영하였습니다.

어느새 블로그를 운영한지 1년 4개월이 넘어간 것 같습니다. 앞으로도 더 꾸준히 포스팅 할 수 있도록 노력하겠습니다.

Delegation in Kotlin

Image from https://project-management.com/12-rules-of-delegation/

위임(Delegation) 이란 하나의 인스턴스에서 다른 인스턴스로 권한을 부여하는 것이다. 즉, 클래스 간의 상속을 이용해 고정적 관계를 형성하는 것이 아닌 가변적으로 형성하여 상속 기능을 구현할 수 있는 대체제라고 표현할 수 있다. 흔히 이런 점이 위임을 강력한 기제로 작용하는 이유가 된다.

위임은 발신 객체를 수신 객체로 명시적으로 전달하는 것으로 이루어 질 수 있는데, 이를 명시적 위임(Explicitly Delegation) 이라 부르며 객체지향의 개념을 가지고 있는 언어를 사용하고 있을 경우에 쉽게 구현이 가능하다. 또는, 명시적으로 전달하지 않고 암시적으로 전달할 수 있는데 이 것을 암시적 위임(Implicitly Delegation) 으로 부른다.

글에서 사용할 언어인 Kotlin 은 일반적인 명시적 위임 말고도 언어적 기능으로 클래스 위임(Implementation by Delegation) 과 위임 프로퍼티(Delegated Properties) 을 지원하는데, 이 글에서는 명시적 위임과 클래스 위임, 위임 프로퍼티에 대해 각각 알아보고 어떤 목적엔 어떤 위임을 사용해야 하는지 알아보려 한다.

Explicitly Delegation

상기했듯이 명시적 위임은 발신 객체를 수신 객체로 명시적으로 전달하는 것을 의미한다. 이는 객체지향의 개념을 가지고 있는 언어라면 구현이 가능한 위임 패턴이다. 즉, 발신 객체를 수신 객체로 전달하는 과정에서 모든 과정이 코드로서 나타낸 경우를 의미한다. 다음은 명시적 위임의 예제이다.

interface Coffee {
    fun build()
}

open class CoffeeImpl : Coffee {
    override fun build() {
        println("Delivered!")
    }
}

class CoffeeMaker(private val coffee: Coffee) : Coffee {
    override fun build() {
        coffee.build()
    }
}

fun main(args: Array<String>) {
    println("Welcome to Pyxis Cafe!")
    println()

    val cappuccino = CoffeeImpl()
    val cappuccinoMaker = CoffeeMaker(cappuccino)
    cappuccino.build()
}

CoffeeCoffeeMaker란 클래스가 있다고 가정해본다.  Coffee 는 특정한 메서드를 가진 인터페이스로 여기에서는 만들어진 커피를 고객에게 전달하는 build() 메서드가 있다.

CoffeeMaker 클래스는 Coffee 클래스 자체를 파라미터로 가지고 있다. 즉, 여기서 CoffeeCoffeeMaker 사이에 위임 통로(Delegation link) 가 발생한다. build() 메서드에서는 받은 Coffee 파라미터의 build() 메서드를 실행해 고객에게 전달되게 한다.

마지막으로, 실제로 코드가 실행될  main 메서드에선 Coffee 의 새 인스턴스인 카푸치노(cappuccino)를 생성하고, CoffeeMaker 의 인스턴스를 만들어 build() 를 실행한다. 생성한 카푸치노 인스턴스를 CoffeeMaker 클래스의 생성자에 삽입하는데, 이 때 두 객체 간의 위임이 실행된다. 즉, Coffee 의 기능을 CoffeeMaker 에 위임하는 것이다.

실제로 실행해보면 다음과 같은 결과가 나온다.

Coffee 는 상속 가능한 클래스이므로 Coffee 를 상속하는 하위 클래스를 만들어서  CoffeeMaker 에 위임시킬 수 있다.

...

class Latte : CoffeeImpl() {
    override fun build() {
        println("Latte Delivered!")
    }
}

fun main(args: Array<String>) {
    ...

    val latte = Latte()
    val latteMaker = CoffeeMaker(latte)
    latteMaker.build()
}

상기했듯이 위임 패턴이 강력한 기제로 작용되는 이유는 Coffee 의 인스턴스나 하위 클래스를 수 없이 만들어도 CoffeeMaker 자체는 한 가지 형태만 필요하다는 것이다. 즉, 어떤 형태가 와도 유연하고 강력하게 대응할 수 있다.

특히, 상기 예제에서는 언어직 기능을 사용하지 않았기 때문에 객체지향 언어라면 모두 사용이 가능하다. 다만, 명시적 위임에 문제가 있다면 CoffeeMakerCoffee 의 모든 public 메서드를 구현하여 원래의 Coffee 메서드를 수동으로 실행해야 하므로 작성해야 되는 코드가 늘어난다는 것이다. (이를 객체지향에서는 Forwarding 라 부른다.)

이를 해결하기 위해 언어적으로 발신 객체를 수신 객체로 전달하는 과정을 작성할 수 있는 기능이 필요한데, 여기에서 나오는 것이 Kotlin 에서 지원하는 클래스 위임이다.

Implementation by Delegation

클래스 위임은 명시적 위임 방식으로 구현함에 있어 상용적인 코드를 제거할 수 있는 기능이다. 즉, 명시적 위임에서 CoffeeMakerCoffee 의 모든 public 메서드를 구현하여 원래의 Coffee 메서드를 수동으로 실행해주었다면, 클래스 위임에서는 이러한 작업을 컴파일러가 대신하여 작업하는 것이다.

클래스 위임은 기반이 되는 객체가 interface일 때, 인터페이스 이름 by 변수 이름으로 사용할 수 있다. 즉, 여기에서는 class CoffeeMaker(coffee: Coffee) : Coffee by coffeeCoffeeMaker 클래스를 구성할 수 있다. 실제로 컴파일 되었을 때에는 명시적 위임과 같이 CoffeeMaker 클래스가 Coffee 클래스를 구현하고 coffee 의 메서드를 실행시키는 코드를 확인할 수 있는데, 컴파일된 코드는 다음과 같다.

여기까지 내용을 정리하자면, 명시적 위임은 클래스를 기반으로 발신 객체를 수신 객체로 명시적으로 전달하는 것이다. 그리고 클래스 위임은 언어적 기능을 사용하여 전달하는 기능을 컴파일러에 위임한 것이다.

Delegated Properties

위임은 클래스에 기반해서 이루어지는 것 뿐만 아니라, 특정 변수를 대상으로 하여 기능을 위임하는 역할도 있다.  이러한 역할을 가진 역할을 위임 프로퍼티 라고 부른다.

예를 들어, 해당 속성이 변경되었을 때에 대한 알림을 주는 Delegates.observable() 나 변수에 처음 접근할 때에 주어진 함수로 변수를 초기화 하고, 다음부터 접근할 때에는 설정된 값을 반환하는 lazy() 가 있다.

위임 프로퍼티를 사용하기 위해서는 특정 조건을 만족해야 하는데, 그 조건은 다음과 같다.

  • Val를 대상으로 할 경우
    • getValue 메서드 구현 필요
    • operator fun getValue(thisRef: Any?, property: KProperty<*>): T
    • thisRef: 해당 속성의 소유자와 동일하거나 상위 타입이어야 함
    • property: kotlin.reflect 패키지에 있는 클래스이며, 해당 속성에 대한 정보를 가짐 (이름, 반환 타입 등)
  • Var를 대상으로 할 경우
    • getValue, setValue 메서드 구현 필요
    • operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    • value: 변수에 설정될 값

위 조건을 만족하는 클래스를 만들었을 경우 val(var) 이름 : 타입 by 위임 클래스 이름 으로 사용이 가능하며, 만일 Delegation 이라는 클래스를 만들었다면 val name: String by Delegation() 으로 사용할 수 있다.

예제로, JVM 환경에서 GC가 발생되기 전 까지는 참조를 유지하고, 발생하면 회수되는 WeakReference 에 대해 만들었는데, 다음은 그 코드이다.

class WeakReferenceDelegation<T>(private var value: WeakReference<T?>) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
        return value.get()
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        this.value = WeakReference(value)
    }
}

fun <T> weak(value: T) = WeakReferenceDelegation(WeakReference(value))

그리고 사용법은 다음과 같다.

var coffee: Coffee? by weak(null)

fun main(args: Array<String>) {
    coffee = CoffeeImpl()
    coffee?.build()
}

기존에 WeakReference 를 사용하려면 해당 변수의 타입을 WeakReference<Coffee> 로 선언하고, 값을 설정할 때 coffee = new WeakReference<>(coffee) 로 설정했던 것에 대비해 위임 프로퍼티를 사용하면 변수 타입을 그대로 유지하고, 값을 설정하는 것 또한 일반적인 방법으로 할 수 있다.

단, 글을 작성하는 일자(2018-10-13) 기준으로 coffee 객체가 mutable 하여 non-null 로 cast 되지 않는 버그가 있는데, 이는 방법을 더 찾아봐야 될 것 같다. 예상하기로는 Kotlin 1.3 에 추가될 contract 로 해결될 것으로 보인다.

정리

이번 글에서는 Kotlin 에서 사용이 가능한 세 가지 위임에 대해 살펴보았다. 다소 예제가 부족한 느낌은 있지만 정리하면서 그동안 혼동이 있었던 사실에 대해 이해한 것 같았다.

ByteArray to Hex Performance Comparison in Kotlin

최근, BLE 관련 프로젝트를 진행하다가 Notify로 받아온 ByteArray 를 Hex String 로 변환하는 과정에서 딜레이가 발생하는 일이 있었다.

따라서 이번에는 구글로 찾아보면 나오는 ByteArray to Hex 에 대해 여러 방법에 대해 알아보고, 간단한 벤치마크를 작성하여 어느 방법이 성능이 더 잘 나오는지 비교해보려 한다.

비교 대상

비교 대상은 총 5개로, 코틀린 코드와 JVM 디컴파일 결과는 다음과 같다. 괄호 안의 텍스트는 벤치마크 코드 / 결과에 있어서의 이름으로 간주된다.

StringBuilder + String.format (BuilderFormat)

fun toHexString(byteArray: ByteArray): String {
    val sbx = StringBuilder()
    for (i in byteArray.indices) {
        sbx.append(String.format("%02X", byteArray[i]))
    }
    return sbx.toString()
}

StringBuilder 를 매번 메서드 실행할 때 마다 생성하고, 각 바이트를 %02X, 즉 Hex 형태로 format 시키는 단순한 코드이다.

String.format (StringFormat)

fun toHexString(byteArray: ByteArray): String {
    var target = ""
    for (i in byteArray.indices) {
        target += String.format("%02X", byteArray[i])
    }
    return target
}

위 방법에서 StringBuilder 만 해제한 것이다.

다만 JVM으로 컴파일 되었을 때 똑같이 StringBuilder 로 컴파일 되므로 차이가 없다고 봐도 무방하다.

Right Shift (CharArrayRightShift)

private val digits = "0123456789ABCDEF"

fun toHexString(byteArray: ByteArray): String {
    val hexChars = CharArray(byteArray.size * 2)
    for (i in byteArray.indices) {
        val v = byteArray[i].toInt() and 0xFF
        hexChars[i * 2] = digits[v.ushr(4)]
        hexChars[i * 2 + 1] = digits[v and 0x0F]
    }

    return String(hexChars)
}

StringBuilder 를 사용하지 않는 대신, 바이트의 2배 길이만큼 가진 CharArray를 만들고 각 쌍의 첫번째 인자에는 digits 에서 해당 바이트 >>> 4 한 만큼의 위치값, 두번째 인자에는 해당 바이트 & 0x0F 한 만큼의 위치값을 넣는다. 마지막으로 CharArray 를 String 로 만들어 반환한다.

StringBuilder + Shift (BuilderShift)

private val digits = "0123456789ABCDEF"

fun toHexString(byteArray: ByteArray): String {
    val buf = StringBuilder(byteArray.size * 2)
    for (i in byteArray.indices) {
        val v = byteArray[i].toInt() and 0xff
        buf.append(digits[v shr 4])
        buf.append(digits[v and 0xf])
    }
    return buf.toString()
}

StringBuilder 를 사용하지만,  String.format 를 사용하는 것이 아닌 해당 바이트 >> 4 와 해당 바이트 & 0xF 한 값을 각각 append 하여 String로 만든다.

CharArray + Shift (CharArrayShift)

private val digits = "0123456789ABCDEF"

fun toHexString(byteArray: ByteArray): String {
    val hexChars = CharArray(byteArray.size * 2)
    for (i in byteArray.indices) {
        val v = byteArray[i].toInt() and 0xff
        hexChars[i * 2] = digits[v shr 4]
        hexChars[i * 2 + 1] = digits[v and 0xf]
    }
    return String(hexChars)
}

기본 접근 방법은 CharArray + Right Shift 와 동일하나, 여기에서는 Right Shift 가 아닌 일반 Shift 가 사용되었다.

성능 비교 방법

먼저, 측정에 들어가기 전 32의 사이즈를 가진 ByteArray 를 랜덤으로 생성하고, 지정된 카운트 만큼 반복하여 걸린 시간을 나노초 단위로 구한다.

private fun measure(block: () -> Unit): String {
    val nano = measureNanoTime {
        for (i in 0 until count) {
            block()
        }
    }

    val ms = nano / 1000000.0
    return String.format("%.3f", ms.toFloat())
}

일반적으로 Kotlin 에서 제공되는 measureNanoTime 를 약간 변형하여 block 에는 toHexString 메서드가 실행되도록 만들었다.

결과 분석

결과에 쓰여진 기기의 사양은 다음과 같다.

OS: Apple macOS 10.13.5 (High Sierra) build 17F77
Processors: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
Total Memory: 16GB
Time: 2018-10-08 AM 02:39:56

벤치마크를 구동했을 때, 아래와 같은 결과가 나온다.

먼저, 코드 상에서 StringBuilder가 있음과 없음은 그렇게 많이 차이나지않는다는 것이다.  중요한 것은 String.format 가 있는가 없는가의 차이로, 최대 58배 이상 시간이 소요된다.

비트 연산을 진행하는 3개 방법의 경우 큰 차이점은 보이지 않으나, 대체적으로 마지막 방법인 CharArray + Shift 가 다소 빠른 측정 결과를 보여주었다.

코드 공유

벤치마크 구동에 있어 작성된 코드는 WindSekirun/ByteArrayToHexPerformance 레포지토리에서 찾아볼 수 있고, 구동시 출력되는 결과는 다음과 같다.

Please enter the number you want to execute.
10
Please enter the length of bytes to be generated.
32

==== ByteArray to Hex String Benchmark ====
OS: Apple macOS 10.13.5 (High Sierra) build 17F77
Processors: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
Total Memory: 16GB
Time: 2018-10-08 AM 03:02:10

Starting with count as 10 with length of ByteArray as 32
Measure in progress...

BuilderFormat 9.632ms
StringFormat 5.407ms
CharArrayRightShift 0.321ms
BuilderShift 0.338ms
CharArrayShift 0.273ms

Finished! in 15.971ms