일상 #5

aboutMe 2016. 8. 10. 02:32

요새는 통 블로그 쓸 시간이 나질 않습니다. 새로 입사한 회사에도 적응하는 시간도 좀 필요했고 집에서는 개인적으로  MIT에서 펴낸 알고리즘 책 읽느라 블로그 쓸 겨를이 없었습니다. 시간이 날때면 Context switching에 관한 논문도 보면서 프로세서내의 캐쉬를 효율적으로 사용하기 위한 선조 프로그래머들의 발자취를 따라가고 있습니다. 또 Virtual memory에도 관심이 가서 Virtual memory 관련 논문도 읽으면서 TLB의 쓰임새를 알아가면서 paging을 다시 복습해 나가고 있습니다. OS내의 메모리 관리쪽도 한번 쫙 훑어봐야 제가 고수준 언어로 작성한 언어가 하위 수준과 물리 수준에서 어떻게 동작하는지 밑그림이 그려지지 않겠습니까? 사실 안드로이드 응용 프로그램, 서비스 개발하면 조금 따분한 것도 있어서 남는 시간에는 이런 부분을 보면서 재미를 느끼고 있습니다;;; Hotspot JVM 스펙 문서도 한번 읽어봐야 하는데...

지금 다니는 회사에서는 상단히 오래된 라이브러리와 UI 프레임워크(?) 기반으로 앱이 개발된 상태라 기본적으로 현재 사용하는 라이브러리로 교체하고 최신화하는 작업을 하고 있습니다. 멀티쓰레딩도 좀 수정해야 할 부분이 있어서 수정하고 있고, 여러가지로 손 볼곳이 많네요. 제가 IO쪽은 일부로 분석하지 않고 있었는데 팀내 다른분이 IO 관련된 부분을 보고 있는데, 한번 IO쪽 완벽 해부해서 가이드해야 할 것 같은 기분이 드네요.

서버쪽을 좀 손대고 싶었었는데, 최근에 입사한 회사의 비지니스 영역이 제가 관심있는 부분이기도 하고 굳이 제가 서버 프로그램을 개발할 이유가 없어져서 서버쪽은 당분간 손대지 않으려 합니다.(혼자 서버, 클라 구현하면서 뭐좀 만들려고 했거든요...) 굳이 서버쪽을 손대지 않아도 알고리즘, OS, JVM, Computer architecture 쪽을 조금씩 다시 들여다 보니 시간이 흐르면 저절로 서버와 클라를 구분하지 않아도 될 경지에 이르겠다란 느낌적인 느낌이 듭니다.

저는 이 언어 저 언어 스쳐가면서 다루기 보다는 자바가 전세계적으로 많이 쓰이고 인력 풀이 크기 때문에 주력 언어를 자바로 정하고 타 언어를 공부하지 않고 깊게 파들어가는 엔지니어가 되려 합니다. 자바도 발전하고 있기도 하고 사실 실무에서 이슈가 발생하면 개발자가 얼마나 깊이 있게 내부를 잘 파악하고 있느냐에 따라 그 이슈의 해결 가능성이 높아지지 언어를 모던 언어를 사용한다해서 이슈 해결 가능성이 높아진다고 생각하진 않습니다. 사실 이미 많이 쓰인 언어가 특별하게 타언어에 의해 교체될 가능성도 적다고 주관적으로는 생각하고 있고 만일 그런 일이 발생한다면 그때가서 언어를 어떤 걸 공부할지 생각해도 늦이 않다고 봅니다.

아무튼 블로그글을 안쓰기도 했고 머리도 좀 정리할 겸 늦은 시간에 이렇게 글을 쓰는 시간을 가져서 좋습니다. 얼마나 많은 사람들이 블로그를 읽는지 알진 못하지만 글 쓰는게 저한테 도움이 되기 때문에 쭉 이런 시간을 가져야 하겠습니다.

'aboutMe' 카테고리의 다른 글

일상 #7  (0) 2017.03.03
일상 #6  (0) 2016.11.11
일상 #4  (0) 2016.07.16
일상 #3  (0) 2016.06.17
일상 #2  (0) 2016.05.23
Posted by 제이제이랩
,

일상 #4

aboutMe 2016. 7. 16. 00:51

요새는 회사에서 하던 일 마무리 잘 지으려고 야근을 좀 했더니 다행히 QA팀에서 이상이 없는지 오늘 정오 이후로는 저에게 업무 관련 메세지가 오지 않았습니다.ㅋ 정말 다행입니다.


증권 서비스를 하는곳으로 곧 회사를 옮길 예정이어서 남은 시간동안 RxJava, RxAndroid 좀 익히고 새로 입사할 회사에 어떻게 적용할지 고민 좀 하려고  합니다. 이직할 회사의 안드로이드 팀원들의 실력이 어느정도인지 모르기 때문에 레거시 코드를 좀 보면서 객체지향 설계 및 코딩 능력을 살펴보고 저보다 나은 점이 있으면 습득하고 저보다 못하다고 느껴지면 갭을 메꿀 방법을 찾아야 겠습니다.


알고리즘 책을 보다가 RxJava 때문에 멈췄는데, RxJava 가지고 놀 정도되면 다시 읽으려 합니다. MIT에서 나온 introduction to algorithms 3rd edition 인데, 1300페이지 정도 되는데 그 책 하나 쓰려고 저자가 수백권을 논문과 책을 얼마나 읽었을지 감히 가늠이 안될정도로 마음에 드는 책입니다. 제가 좋아하는 스타일의 책인데 정작 대학원때 그 책의 수업이 있었던 걸로 기억하는데 수강신청을 왜하지 않았을까 한탄이 나오네요ㅠㅠ 한번 정독을 하고 나면 사실 머리에 남는게 없지만 이번해에 최소 한번 정독하고 다음해에 또 한번 정독하고 그 다음해에도 또 한번 정독하는 방식으로 책을 정복해야 겠습니다. 주로 컴퓨팅 문제에 다루지만 읽다보니 문제해결 능력을 상당히 키울 수 있을 것 같아 인생의 동반자(?)로 같이 가야할듯 합니다.ㅎㅎㅎ


회사에서 성능 이슈 때문에 멀티 쓰레딩, 쓰레드 풀, HandlerThread 등등을 제가 도입 했었습니다. CPU가 한 쓰레드를 실행하다가 다른 쓰레드를 실행할 때 Context switching을 해야하는데 그 비용이 얼마나 되는지 정확하지 알지 못한 채 Context switching 비용이 좀 있다는 수준으로만 알고 있었습니다. 멀티 쓰레딩 코드를 손볼 때마다 얼마나 될까 궁금하여 구글링 해보니 역시 이미 이러한 궁금증을 가지셨던 선조 프로그래머들이 "Quantifying The Cost of Context Switch"[http://www.cs.rochester.edu/u/cli/research/switch.pdf] 논문으로 그 비용을 측정하고 결과를 학계에 공유하였습니다. 2007년도 논문인데 오래되었지만 공유하면 좋을것 같아 완벽히 논문의 의도와 측정 방법에 대한 이해가 되면 블로그에 Context switching 비용을 어떤 환경에서 어떻게 측정하였는지 쓸 시간을 가지겠습니다.ㅋ


Spring boot, Nginx, MySql, MongoDB, Tomcat, Apache 조합의 서버 백엔드 개발도 재미로 하려고 했는데... 너무나도 공부할 것도 많고 확인할 것도 많아서 당분간 여유가 없어서 안드로이드 기술에 대해 좀더 깊이를 더하는데 시간을 쏟으려 합니다. 원래 자바면 뭐든지 오케이인데 너무 시간이 안나는 요즘입니다...

'aboutMe' 카테고리의 다른 글

일상 #6  (0) 2016.11.11
일상 #5  (0) 2016.08.10
일상 #3  (0) 2016.06.17
일상 #2  (0) 2016.05.23
2015 MobiCom, MobiSys program과 session 정보  (0) 2016.05.06
Posted by 제이제이랩
,

2016/03/15 - [java] - benchmarking 코드를 작성해 보자 #1

2016/06/06 - [java] - Dynamic Proxy를 꼭 써야해? #1


이 글에서는 Dynamic Proxy를 꼭 써야하나? 라는 고민을 해봅시다. 


안드로이드 대표적인 HTTP 라이브러리를 꼽자면 Retrofit2, Volley, Okhttp3 입니다. Retrofit2는 안드로이드 개발자들이 HTTP 통신을 쉽게 사용하도록 Okhttp3 기반의 상위 레이어에 불과합니다. Volley는 구글에서 만들었는데, 2013년 어떤 분이 마이크로 벤치마킹 한 결과가 별로 좋지 않아서(참조 http://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/) 외국에서는 거의 Retrofit을 주로 사용하는 것으로 알고 있습니다.(오픈소스로 된 샘플앱들이 HTTP 라이브러리를 Retrofit으로 된걸로 보면요.) 티몬, 쿠팡, 위메프, 11번가등 apk를 까보면 Volley를 쓰고 있습니다. Volley의 HurlStack을 Okhttp로 사용하게 하면 그나마 성능 이슈는 해결되긴 합니다만, 굳이 사용성 측면에서나 okhttp3나 Retrofit2가 성능이나 메모리 사용에서 더 효율적이기 때문에 새로운 앱을 만들때 Volley를 선택할 필요는 없을 것입니다.


결국엔 서비스앱을 만들 때 Retrofit2를 사용할지 Okhttp3를 사용할지가 문제입니다. 앱의 가독성 측면을 보면 Retrofit2가 더 좋아 보이는데, 한가지 걸리는 것이 있습니다. 그건 바로 Dynamic Proxy를 내부적으로 런타임시 사용하고 있다는 것입니다. 개발자가 선언한 Interface를 런타임에 읽어서 그 인터페이스에서 선언한 메소드 및 annotation 등을 Reflection을 사용해서 처리하여 Proxy class를 만들어 그 구현체를 개발자가 사용하는 방식입니다. 실제로 서버에 HTTP 요청을 보내고 응답을 받는 작업은 Okhttp3가 하고있는 상황입니다. 이 사실만으로도 Reflection의 오버헤드를 피해서 조그이나마 성능 이점을 얻기 위해서는 개발자가 Retrofit2를 피하고 Okhttp3를 직접 사용하는 것이 나을것이라는 막연한 생각이 들수 있습니다. 그러면 어느 정도 성능 이점이 있을지 살펴 보도록 하겠습니다.


JMH를 이용해서 Retrofit2, Okhttp3를 이용하여 각각 동일한 GET 요청을 보내는 코드를 작성하도록 합니다. 동일한 요청을 보내는데 각 라이브러리가 얼마나 시간이 걸리는지 비교하도록 합니다. Benchmarking 코드와 결과는 이 Github[https://github.com/jayjaylab-benchmark/retrofitvsokhttp] repository에 저장하였으니 확인하세요.


하기는 벤치마킹코드입니다. 혹시나 보시고 제가 실수한 사항이 있으면 지적해 주시면 감사하겠습니다. 서버에 GET 요청을 보낸 후 응답으로 오는 json 스트링을 POJO 객체로 unmarshalling하는 지점까지를 성능 측정하는 범위로 정하였습니다. 본 벤치마킹을 실행한 환경은 MAC OS X yosemite version 10.10.5(14F27), 2.7GHz Intel core i5, 8GB 1867MHz DD3이고 JDK 1.8.0_45, VM 25.45-b02 자바환경에서 JVM warm up없이 cold start인 상황에서 측정하였습니다. JMH에서 코드 warm up한 상태에서 벤치마킹할 각 메소드를 각 iteration당 1번만 실행하는 옵션은 없어서 부득히하게 cold start인 경우에만 측정하게 되었습니다. 안드로이드의 경우 client mode로 dalvik이나 ART가 실행될 것으로 추측이 되고 warm up 과정은 없으리라 추측이 되는데, AOSP에서 런타임 까봐야 알겠죠?ㅋ...

public class AndroidHttpLibraryBenchmark {
    @org.openjdk.jmh.annotations.State(Scope.Benchmark)
    public static class State {
        public Retrofit retrofit;
        public OkHttpClient okHttpClient;
        public ObjectMapper objectMapper;

        @Setup(Level.Trial)
        public void setUp() {
            System.out.println("########## setUp() ##########");
            retrofit = new Retrofit.Builder()
                    .baseUrl("https://api.github.com/")
                    .addConverterFactory(JacksonConverterFactory.create())
                    .build();
            okHttpClient = new OkHttpClient();
            objectMapper = new ObjectMapper();
        }

        @TearDown(Level.Trial)
        public void tearDown() {
            System.out.println("########## tearDown() ##########");
            retrofit = null;
            objectMapper = null;
            okHttpClient = null;
        }
    }

    @Benchmark
    @BenchmarkMode({Mode.SingleShotTime})
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void testRetrofit2(State state, Blackhole blackhole) {
        GitHubService service = state.retrofit.create(GitHubService.class);
        Call<List<Repo>> repos = service.listRepos("octocat");
        List<Repo> result = null;

        try {
            retrofit2.Response<List<Repo>> response = repos.execute();
            result = response.body();
        } catch(Exception e) {
            e.printStackTrace();
        }

        blackhole.consume(result);
    }

    @Benchmark
    @BenchmarkMode({Mode.SingleShotTime})
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void testOkhttp3(State state, Blackhole blackhole) {
        HttpUrl url = new HttpUrl.Builder()
                .scheme("https")
                .host("api.github.com")
                .addPathSegment("users")
                .addPathSegment("octocat")
                .addPathSegment("repos")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .build();
        List<Repo> result = null;

        try {
            Response response = state.okHttpClient.newCall(request).execute();
            result = state.objectMapper.readValue(response.body().charStream(),
                    new TypeReference<List<Repo>>(){});
        } catch (Exception e) {
            e.printStackTrace();
        }

        blackhole.consume(result);
    }
}


우선 성능만 보자면... 어떤걸 택하시겠나요? Retrofit2, Okhttp3를 사용하는 메소드를 10번씩 돌린 결과입니다. 네트워크 상황이 벤치마킹 돌릴때마다 일정하고 안정된 상황이라는 가정하에 아래 결과를 보면 Okhttp3를 사용하는게 성능상 이점이 있다고 볼 수 있습니다. 특히나 앱 시작할 때 몇개의 Restful API를 사용하는 경우라면 XXXms의 이점이 있을것으로 보입니다. reddit androiddev subreddit에 이 결과를 올려서 공유를 했습니다.(참고 : https://www.reddit.com/r/androiddev/comments/4rpdlv/retrofit2_vs_okhttp3/).

MicroBenchmark Result #1(2016-07-07T22:41:40+09:00)

Benchmark                                  Mode  Cnt     Score     Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2389.107 ± 550.925  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  2476.903 ± 327.647  ms/op
MicroBenchmark Result #2(2016-07-08T00:47:12+09:00)

Benchmark                                  Mode  Cnt     Score     Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2472.586 ± 245.455  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  2920.377 ± 760.955  ms/op
MicroBenchmark Result #3(2016-07-08T12:39:23+09:00)

Benchmark                                  Mode  Cnt     Score      Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2427.628 ±  556.801  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  3110.812 ± 1659.656  ms/op

Jake wharton이 proxy class instance를 State class에서 만들어 써야 한다는 댓글을 남겼습니다. 그러면 결과는 좀 달라지겠지요. 그렇다면 Okhttp3의 Request instance 또한 State class에서 만들어서 써야하는데 그렇게 되면 제 예상으로는 수치상 차이는 없어질 가능성이 많아 보입니다. 제가 지적하고자 하는 점은 dynamic proxy 사용이 염려되어 proxy class의 인스턴스를 얻는 코드를 벤치마킹에 포함하였고 그와 동일한 행위를 하는 Okhttp3 코드 또한 벤치마킹 코드에 포함하였습니다. Retrofit2는 한번 만들어진 인스턴스를 내부적으로 캐쉬하는 것으로 보이고 Okhttp3에서 사용하는 Request 인스턴스는 개발자가 직접 캐쉬하도록 코드를 작성해야 하는 번거러움이 있습니다. 저에게는 직접 캐쉬하는 코드 작성이 별로 어렵지 않은 터라 다른 경쟁사 앱들과 조금이나마 차이를 만들고자 Okhttp3 선택하고 싶네요. 


마치기에 앞서 이 벤치마킹 수치에 지대한 영향을 미치는 통신 overhead에 관해 언급을 해야겠습니다. Retrofit2의 내부에서 Okhttp3를 사용하기에 HTTP 통신 로직의 오버헤드는 둘 다 동일할 것이고 나머지 Retrofit2 자체의 오버헤드와 네트워크 상태(?)가 벤치마크 수치를 다르게 만들 수 있습니다. 네트워크 상태가 각 벤치마크를 돌릴때마다 다르다고 해도 위 수치는 OkHttp3가 좀더 좋은 결과를 나타내고 있다고 말할 수 있는 수준이라고 보입니다. 또한 Retrofit2, Okhttp3는 안드로이드용 이기 때문에 Hotspot JVM에서 벤치마킹을 하는 JMH에서의 결과를 신뢰할 수 있을지 의문이 들긴 합니다. 그런데 Dalvik이나 ART에서 동일한 벤치마킹을 수행한다고 할 때 결과 양상이 바뀔까? 라는 질문을 했을 때, 글쎄요 수치는 바뀔 수 있을지언정 위 JMH에서 나온 결과 패턴은 바뀔 것 같아 보이진 않아 보입니다. 아무튼 안드로이드상에서 벤치마킹을 수행할 수 있는 툴이 현재로선 제가 없는걸로 알고 있어서 위 결과로 유추할 수 밖에 없겠습니다.


요약 : Retrofit2의 오버헤드가 반드시 Dynamic proxy 사용 때문이라고는 결론을 내릴 순 없습니다. 위 결과로부터 okhttp3을 직접 사용하는 것이 retrofit2를 사용하는 것보다 좀더 나은 성능을 줄 수 있다라고 볼 수 있을것 같습니다. 

Posted by 제이제이랩
,