Tag Archives: 안드로이드

[Android] 디자이너와의 협업을 위한 몇가지 팁

안드로이드 개발을 하다 보면 새로운 골치거리중에 하나가 바로 디자이너에게 어떻게 디자인을 요청해야 할지 애로사항이 꽃피어 오른다는 점입니다. 저도 매번 어떻게 해달라고 요청은 하지만 저도 명확한 기준이 없이 닥치는대로 요청해 보는 식이었네요.

그래서 이번에 한번 알아봤습니다. 아이콘등의 크기에 대한 언급이나 버튼의 4가지 상태에 대한 이미지에 대한 언급은 검색하면 많이 나와있습니다만 이미지들의 사이즈를 어떻게 정의해야 할까요.

사용자 삽입 이미지
위는 가장 최근의 해상도(정확히는 픽셀 밀집도)에 따른 실제 사용률입니다. 전세계 기준의 데이터지만 우리나라도 마찬가지라고 생각합니다. 그 이유는 물론 갤럭시S님 때문이죠.

위의 데이터를 읽어보자면 Low Density는 실제로 거의 사용되지 않습니다. 실제로 사용자가 사용하는 Density는 Medium과 High 두종류 뿐입니다. 그리고 밑에 거기에 따른 해상도를 좀 더 보자면 Medium Density의 경우에는 사실상 국내에서 접할수 있는 모델은 안드로원, 옵티머스원, 디자이어팝등의 320×480뿐입니다. 그리고 High Density의 경우에는 갤럭시S, 넥서스원, 디자이어등의 대부분의 하이엔드급 안드로이드폰이 480×800을 사용하고 모토롤라의 스마트폰들이 480×854를 사용하고 있습니다.

실제로 안드로이드 관련 컨퍼런스를 가봐도 Resource Identifier를 적절히 이용하여 이 수많은 해상도를 모두 지원해 주길 바라고 있습니다. 하지만 상식적으로 대한민국의 업무 진행 심리에 의거해 그럴수는 없겠네요.

제가 결론을 내린 디자인 협업을 위한 타겟은 두가지로 압축을 합니다.


1. 160 Density의 320×480
2. 240 Density의 480×800

실제로 이리저리 해본 결과 480×854의 경우 좀 무시하는 수준에서 개발을 하다가 런칭 직전에 조금 검토만 해보는 정도면 될꺼 같습니다. 480×800에 맞춰 개발해 놓으면 늘어나거나 조금 안맞거나 하는 수준으로 사용에는 지장이 없게 잘 동작하더군요.

2가지로 작업의 타겟층을 압축하였습니다. 하지만 디자이너에게 저 두가지버젼의 디자인을 해달라고 요청하기에 역시나 좀 무리가 있어 보입니다. 그래서 어떻게 하면 한가지 버전으로 압축할 수 있을까 고민해 보았습니다.

위의 저런 수많은 이상한 고민을 해야 하는 이유는 바로 PIXEL이라는 일반적으로 디자이너가 이해할 수 있는 단위입니다. 안드로이드에서는 dp 혹은 sp와 같은 해상도에 비의존적인 단위를 사용합니다. 어찌보면 답답하군요;


dp : Density-independent Pixels
sp : Scale-independent Pixels

안드로이드 컨퍼런스에 가서 알게 된 사실인데 일반적으로 단위는 dp를 쓰되 글자는 sp를 사용해 달라고 하더군요. 물론 pt를 쓰시는 분들도 많이 계신것으로 알고 있습니다만 프로젝트를 몇개 진행하면서 느낀것인데 pt는 기기에 따라 다르게 보이는 현상이 심하더군요. pt의 사용을 자제하시는 것이 좋을 것 같습니다. 저의 경우에는 dp와 sp만으로 프로젝트를 수행하시기를 권장합니다.

우선 폰트에 대해 예제를 보여드리겠습니다. 클릭을 하여 확대후 보세요.

사용자 삽입 이미지
왼쪽이 160 Density고 오른쪽이 240 Density의 화면입니다. 제가 사이즈별로 폰트를 쭉 찍어보았습니다. 같이 일하시는 디자이너에게 이 이미지를 보내주십시오. 그리고 사용하는 단위와 이 글자의 크기를 비교하여 단위를 표기해 달라고 하십시오.

대부분의 모바일 어플리케이션이 5개 이하의 폰트 사이즈를 사용할 것입니다. 그 사이즈와 이 sp단위를 맞추어 개발자에게 알려줄 수 있을 것이라 생각합니다.

다음은 좀 더 중요한 px단위를 어떻게 표기할 수 있을까에 대한 고민에 대한 예제 입니다.

사용자 삽입 이미지
마찬가지로 왼쪽은 160 Density이고 오른쪽은 240 Density입니다. 실제의 px사이즈를 빨간색으로 표시해 보았습니다. 보시면 아시겠지만 160 Density에서의 dp는 px과 똑같습니다.

안드로이드 컨퍼런스에 갔을때도 언급하기를 160 Density에 맞추어 개발후에 240 Density를 추가개발하라고 언급하였었습니다. 하지만 그럴 여유가 한국의 개발자/디자이너들에게 있을까요.

240 Density 기준으로 디자인을 할때 다음과 같이 간단히 dp값을 알 수 있습니다.


dp = px / 3 * 2
dp = px * 0.66

밑에는 위의 식을 좀더 간단히 해보자고 쓴거긴 한데 아무래도 3으로 나누다 보니 값이 엉망이 됩니다. 그냥 나누기 3하시고 곱하기2를 하시는것이 좀더 정확한 값을 얻을 수 있을것 같습니다.

다음은 실제로 디자이너에게 이미지를 전달받았다고 가정하고 어떻게 적용되는지 테스트 해보겠습니다. 이미지의 크기는 256x256px입니다.

사용자 삽입 이미지
나중에 결론에서 정리 하겠지만 저의 작업 방식은 갤럭시S, 즉 240 Density의 480×800 해상도만을 고려하여 개발하는것을 골자로 하고 있습니다. 그리고 이미지는 Resource Identifier중에 drawable-hdpi 폴더안에만 넣어 mdpi를 사용하지 않고 있습니다.

왼쪽부터 160 Density, 240 Density, 마지막은 240 Density화면을 160 Density의 크기로 줄여 겹쳐본 화면입니다. hdpi 폴더안에 이미지를 넣었기 때문에 240 Density의 화면에서는 px크기가 정확하게 유지가 됩니다. 하지만 160 Density의 화면에서는 2/3 크기로 변합니다.

그리고 화면을 줄여본 화면을 보면 1px정도의 오차가 있지만 거의 정확하게 비율이 맞는것을 확인할 수 있습니다. 저정도의 오차는 디자이너가 포기해야 하는 부분이 아닐까 싶네요. 다양한 디스플레이를 가지고 있는 안드로이드의 한계랄까요.

저의 위의 실험 내용을 쭉 일어보셨더라도 무슨 말인지 이해가 안되실 수 있겠네요. 정리를 해보겠습니다.


1. 우리는 240 Density의 480×800 해상도를 가지고 있는 갤럭시S를 대상으로 개발/디자인 한다.
2. 모든 개발에서 사용하는 단위는 dp와 sp로만 이루어진다. (단, 이미지는 px사용)
3. 디자이너는 모든 px사이즈의 표기에 대하여 나누기 3 곱하기 2를 한 후 dp라는 단위를 사용한다.
4. 디자이너는 사용 폰트 크기에 대하여 위에 나와있는 sp 테이블 이미지를 참고하여 사용한다.
5. 디자이너는 모든 제작된 이미지들의 크기를 480×800 대상으로 제작하되 특별한 계산은 하지 않는다.
6. 개발자는 Resource Identifier중 drawable-hdpi만을 사용한다. (개인적으로 아이콘은 mdpi도 만들길 추천함)

위의 방법은 비록 좋은 방법은 아닙니다만, 인적/시간적으로 여유가 없는 회사라면 가장 최대의 효과를 낼 수 있는 방법이라고 생각합니다. 개발자도 한가지만 바라보고 개발하고 디자이너도 한번만 작업하면 되니깐요. 혹시 더 좋은 아이디어가 있으시면 알려주시면 감사하겠습니다^^

[Android] 성능을 위한 설계 – 이동훈님

블로그에 왠만하면 펌질은 피할려고 생각중이지만 너무 좋은 글이라 두고두고 보고 싶어서 퍼왔습니다.
항상 많은곳에서 활동하시는 마메럴핀(이동훈)님이 번역하신 글입니다.




마메렐핀님(이동훈님) – 2009.06.05

성능을 위한 설계




안드로이드 애플리케이션의 속도는 빨라야만 합니다. 음, 효율적이어야 한다고 말하는 쪽이 더 정확할 듯싶네요. 다시 말해, 제한된 컴퓨팅 파워와 데이터 저장소, 작은 화면, 갑갑한 배터리 수명 같은 모바일 장치 환경에서 가능한 한 효율적으로 실행되어야 한다는 것입니다.


애플리케이션을 개발할 때에는 이것을 명심하세요. 듀얼코어 개발 컴퓨터에서 실행하는 에뮬레이터에서는 충분히 잘 작동할지도 모르지만, 모바일 기기에서 실행할 때엔 그리 잘 되지 않을 것입니다. — 최고 성능의 모바일 기기라도 일반적인 데스크탑 시스템의 성능을 따라잡을 수는 없습니다. 그런 이유로, 다양한 모바일 기기들에게 최상의 성능을 보장하기 위해 여러분은 효율적인 코드를 작성하도록 열심히 노력하셔야 합니다.


일반적으로, 빠르거나 효율적인 코드라는 것은 메모리 할당을 최소화 하고, 꽉 짜인 코드를 작성하고, 특정 프로그래밍 언어나 잠재적으로 성능상 문제가 될만한 프로그래밍 어법들을 피하는 것을 말합니다. 객체지향 용어로 말하자면, 이러한 일이 가장 빈번히 일어나는 곳은 메소드 레벨이며, 이와 비슷하게 실제 코드 라인들과 반복문 등에서 발생합니다 .


이 문서는 다음 주제들을 다룹니다:



소개


자원 제한적 시스템에는 두 가지 기본 규칙이 있습니다:



  • 필요 없는 일은 하지 말 것
  • 메모리 할당을 피할 수 있다면 그렇게 할 것

아래의 모든 팁들은 이 두 가지 기본 주의를 따르고 있습니다.


누군가는 이 페이지상의 많은 조언이 “섣부른 최적화”나 마찬가지라고 비판할지도 모릅니다. 미시 최적화는 때로는 효율적인 데이터 구조와 알고리즘을 개발하는 것을 더 어렵게 만든다는 것은 사실입니다. 하지만, 핸드셋과 같은 임베디드 기기에서는 때로는 별다른 선택지가 없습니다. 예를 들어, 여러분이 데스크탑에서 개발할 때 생각하는 VM의 성능에 대한 가정을 안드로이드에도 적용한다면, 여러분은 시스템 메모리를 소진해버리는 코드를 꽤나 작성해 버리고 말 것입니다. 이것은 여러분의 애플리케이션이 바닥을 기도록 할 수 있습니다 — 시스템에서 동작하는 다른 프로그램들에게 무엇을 하는지 지켜보세요!


이것이 바로 이 가이드라인이 중요한 이유입니다. 안드로이드의 성공은 여러분의 애플리케이션이 제공하는 사용자 경험(UX)에 달렸고, 사용자 경험이란 것은 여러분의 코드가 빠르고 팔팔하게 반응하는지, 아니면 느리고 무거운지에 달렸습니다. 모든 우리의 애플리케이션들은 같은 장치에서 동작할 것이기 때문에, 어떤 의미로, 우리 모두 함께 이 것들을 지키도록 최선을 다해야 합니다. 이 문서를 운전면허를 딸 때 배워야만 하는 도로교통법이라고 생각하세요: 모든 이가 따르면 문제없이 원활하겠지만, 따르지 않는다면 사고가 날 것처럼 말입니다.


자세한 내용을 다루기 전에, 간단한 주의사항입니다: 아래 설명된 대부분의 이슈들은 VM이 JIT 컴파일러이든 아니든 효과적입니다. 같은 기능을 수행하는 두 메소드가 있고 interpret 방식에서 foo()의 실행속도가 bar()보다 빠르다면, 컴파일 된 버전에서도 아마 foo()가 bar()과 비슷하거나 더 빠른 속도를 보여줄 것입니다. 컴파일러가 여러분을 “구해줄”것이라던가 충분히 빠르게 만들어줄 것이라고 의존하는 건 현명하지 못하다는 것이죠.


객체 생성을 피하라


객체의 생성은 결코 공짜가 아닙니다. 임시 객체들을 위해 쓰레드-당(per-thread) 할당 풀을 사용하는 세대형(generational) GC는 더 낮은 비용으로 할당 할 수 있지만, 메모리를 할당한다는 것은 메모리를 할당하지 않는 것 보다 언제나 더 높은 비용이 듭니다.


만약 사용자 인터페이스 루프에서 객체를 할당한다면, 주기적으로 가비지 컬렉션을 강요하게 될 것이고 사용자 경험에 있어서 조그마한 “딸꾹질(거북함)”을 만들게 될 겁니다.


그러므로, 필요로 하지 않는 객체 생성을 피해야 합니다. 도움이 될 몇 가지 예제들이 있습니다.



  • 입력 데이터 셋에서 문자열을 추출할 때, 복사 생성된 것 대신 원본 데이터의 부분문자열을 받으십시오. 새로운 String 객체가 만들어졌더라도 원본 데이터의 char[]을 공유할 것입니다.
  • 문자열을 반환하는 메소드가 있고 그 결과가 언제나 StringBuffer에 더해지게 되는 경우에 있다면, 짧은 수명의 임시 객체를 생성하는 대신 직접적으로 더해지는 방식으로 식별자와 구현방식을 바꾸세요.

좀 더 급진적인 아이디어는 다차원 배열을 병렬의 단일 일 차원 배열로 잘라내는 것입니다:



  • int 배열은 Integer 배열보다 더 좋습니다만, 이것으로 또한 int형의 두 병렬 배열이 (int,int) 객체의 배열보다 더 많이 효과적이라는 사실을 추론할 수 있습니다.
  • 만약 (Foo,Bar) 튜플로 저장하는 컨테이너를 구현할 필요가 있다면, 직접 만든 (Foo,Bar) 객체의 단일 배열보다 두 개의 병렬 Foo[] 와 Bar[] 배열이 일반적으로 더욱 더 좋다는 것을 기억하십시오. (물론, 다른 코드들이 접근해야 하는 API를 설계할 때에는 예외가 있습니다; 이 경우 작은 속도 향상을 노리는 것 보다 좋은 API설계가 항상 좋습니다. 그러나 여러분의 내부 코드를 작성할 때에는 가능한 한 효율적인 코드가 되도록 해야 하겠습니다.)

일반적으로, 가능하다면 짧은 수명의 임시 객체 생성을 피하십시오. 더 적은 객체들을 만든다는 것은 사용자 경험에 직접적인 영향을 주는 가비지 컬렉션 줄여줌을 뜻합니다.


네이티브 메소드를 사용하라


문자열을 처리할 때, String.indexOf(), String.lastIndexOf() 와 그 밖의 특별한 메소드를 사용하는 것을 주저하지 마십시오. 이 메소드들은 대체적으로, 자바 루프로 된 것 보다 대략 10-100배 빠른 C/C++ 코드로 구현이 되어있습니다.


이 조언의 반대적 측면은 네이티브 메소드를 호출하는 것이 interpret방식의 메소드 호출보다 더 비용이 높다는 것입니다. 피할 수 있다면, 사소한 계산에는 네이티브 메소드를 사용하지 마십시오.


인터페이스보다 가상 연결을 선호하라


여러분이 HashMap 객체를 하나 가지고 있다고 합시다. 여러분은 HashMap이나 제네릭 Map 으로 선언을 할 수 있습니다.

Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();

어떤것이 더 좋은가요?


전통적인 지혜에서는 Map을 사용해야 한다고 할 것입니다. Map 인터페이스를 구현한 어떤 것으로라도 구현체를 바꿀 수 있기 때문입니다. 전통적인 지혜는 전통적인 프로그래밍에는 맞습니다만, 임베디드 시스템에는 그다지 대단하지 않습니다. 인터페이스 참조를 통해 호출하는 것은 명확한 참조를 통한 가상 메소드 호출보다 2배 더 소요될 수 있습니다.


여러분이 하는 일에 적합하여 HashMap사용을 선택했다면 Map으로 호출하는 것은 거의 가치가 없습니다. 코드를 리팩터링 해 주는 IDE의 가능성을 고려해 보더라도, Map으로 호출하는 것은 큰 가치가 없습니다. 여러분이 코드의 방향을 확신하지 못한다 해도 말입니다. (다시금 이지만, 공용 API는 예외입니다: 작은 성능 고려보다 좋은 API가 언제나 으뜸입니다.)


가상 연결보다 정적 연결을 선호하라


만약 객체의 필드에 접근할 필요가 없다면, 여러분의 메소드를 정적(static)으로 만드세요. 가상 메소드 테이블을 필요로 하지 않기 때문에 그게 더 빠르게 불려집니다. 또한, 메소드 식별자를 보고 메소드 호출이 객체의 상태를 바꿀 수 없다고 말할 수 있으므로, 좋은 관습이 됩니다.


내부에서 Getter/Setter 사용을 피하라


C++와 같은 네이티브 언어에서 필드에 직접적으로 접근하는 것 (예. i = mCount) 보다 getter를 사용하는 것 (i = getCount())은 일반적인 관습입니다. 이 방법은 C++에서는 훌륭한 습관입니다. 왜냐하면 항상 접근을 inline화 할 수 있는 컴파일러를 사용하고 있고, 필드에 접근을 제한하거나 디버그 해야 한다면 언제나 코드를 추가할 수 있기 때문입니다.


안드로이드에서는 나쁜 생각입니다. 가상 메소드 호출은 인스턴스 필드 참조보다 더 비용이 높습니다. 일반적인 객체 지향 프로그래밍 관습에 따르거나, 공용 인터페이스에서 getter, setter을 가지는 것은 이치에 맞습니다. 그러나 클래스 내부에서는 언제나 직접적으로 필드 접근을 해야합니다.


필드 참조들을 캐시하라


객체의 필드에 접근하는 것은 지역 변수에 접근하는 것 보다 더 느립니다. 이렇게 작성하는 것 대신:

for (int i = 0; i < this.mCount; i++)
dumpItem(this.mItems[i]);

이렇게 하십시오:

  int count = this.mCount;
Item[] items = this.mItems;

for (int i = 0; i < count; i++)
dumpItems(items[i]);


(멤버 변수라는 것을 명확히 하기 위해 명시적인 “this”를 사용하고 있습니다.)


유사한 가이드라인은, 결코 “for”문의 두 번째 조건에서 메소드를 호출하지 말라는 것입니다. 예를 들어, 다음 코드는 간단하게 int 값으로 캐쉬 할 수 있는 경우임에도 큰 낭비가 되는 getCount()메소드를 매번 반복 마다 실행하게 됩니다:

for (int i = 0; i < this.getCount(); i++)
dumpItems(this.getItem(i));

인스턴스 필드를 한번 이상 접근해야 한다면, 지역 변수를 만드는 것 또한 좋은 생각입니다. 예를 들어:

    protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
if (isHorizontalScrollBarEnabled()) {
int size = mScrollBar.getSize(false);
if (size <= 0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height – size, width, height);
mScrollBar.setParams(
computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
mScrollBar.draw(canvas);
}
}

mScrollBar 멤버 필드에 네 개의 분리된 참조가 있습니다. 지역 스택 변수로 mScrollBar를 캐싱 함으로써, 네 개의 멤버 필드 참조가 더욱 효율적인 네 개의 스택 변수 참조로 바뀌었습니다.


덧붙여 말하자면, 메소드 인자들은 지역 변수와 같은 성능 특성을 가집니다.


상수를 Final로 선언하라


클래스의 상단에 있는 다음 선언을 고려해 봅시다:

static int intVal = 42;
static String strVal = “Hello, world!”;

컴파일러는 클래스가 처음 사용될 때 실행하게 되는 <clinit>라 불리는 ‘클래스 초기화 메소드’를 생성합니다. 이 메소드가 intVal에 42 값을 저장하고, strVal에는 클래스파일 문자 상수 테이블로부터 참조를 추출하여 저장합니다. 나중에 참조될 때 이 값들은 필드 참조로 접근됩니다.


이를 “final” 키워드로 향상시킬 수 있습니다:

static final int intVal = 42;
static final String strVal = “Hello, world!”;

클래스는 더이상 <clinit> 메소드를 필요로 하지 않습니다. 왜냐하면 상수들은 VM에 의해 직접적으로 다루어 지는 ‘클래스파일 정적 필드 초기자’에 들어가기 때문입니다.intVal의 코드 접근은 직접적으로 정수 값 42를 사용할 것이고, strVal로의 접근은 필드 참조보다 상대적으로 저렴한 “문자열 상수” 명령을 사용하게 될 것입니다.


“final”으로 메소드나 클래스의 선언을 하는 것은 즉각적인 성능 이득을 주지는 못하지만, 특정한 최적화를 가능하게 합니다. 예를 들어, 컴파일러가 서브클래스에 의해 오버라이드될 수 없는 “getter”메소드를 알고 있다면, 메소드 호출을 inline화 할 수 있습니다.


여러분은 또한 지역 변수를 final로 선언할 수 있습니다. 하지만 이것은 결정적인 성능 이득은 없습니다. 지역 변수에는 오직 코드를 명확히 하기 위해서 “final”을 사용합니다 (또는 예를 들어 익명 내부 클래스를 사용해야 한다면 가능).


주의 깊게 향상된 반복문(Enhanced For Loop)을 사용하라


향상된 반복문(때로 “for-each”로 알려진 반복문)은 Iterable 인터페이스를 구현한 컬렉션들을 위해 사용될 수 있습니다. 이러한 객체들로, 반복자는 hasNext() 와 next()을 호출하는 인터페이스를 만들기 위해 할당됩니다. ArrayList의 경우 여러분이 직접 탐색하는 것이 좋을 수 있습니다만, 다른 컬렉션들에서는 향상된 반복문 구문이 명시적인 반복자의 사용과 동등한 성능을 보여줍니다.


그럼에도, 다음 코드로 향상된 반복문의 만족스러운 사용법을 볼 수 있습니다:

public class Foo {
int mSplat;
static Foo mArray[] = new Foo[27];

public static void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; i++) {
sum += mArray[i].mSplat;
}
}

public static void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;

for (int i = 0; i < len; i++) {
sum += localArray[i].mSplat;
}
}

public static void two() {
int sum = 0;
for (Foo a: mArray) {
sum += a.mSplat;
}
}
}


zero() 는 반복되는 매 주기마다 정적 필드를 두 번 부르고 배열의 길이를 한번 얻습니다.


one() 은 참조를 피하기 위해 지역 변수로 모든 것을 끌어냈습니다.


two() 는 자바 언어의 1.5버전에서 소개된 향상된 반복문 구문을 사용합니다. 컴파일러에 의해 생성된 코드는 배열 참조와 배열의 길이를 지역 변수로 복사해주어, 배열의 모든 원소를 탐색하는데 좋은 선택이 될 수 있습니다. 주 루프 내에 추가적인 지역 읽기/저장이 만들어지고(명백하게 “a”에 저장), one()보다 쪼금 느리고 4 바이트 길어지게 하긴 합니다.


좀 더 명확하게 모든 것을 종합하자면: 향상된 반복문 구문은 배열과 잘 동작하지만, 추가적인 객체 생성이 있게 되는 Iterable 객체와 함께 사용할 때엔 조심해야 합니다.


열거형(Enum)을 피하라


열거형은 매우 편리합니다, 그러나 불운하게도 크기와 속도 측면에서 고통스러울 수 있습니다. 예를들어, 다음의 코드는:

public class Foo {
public enum Shrubbery { GROUND, CRAWLING, HANGING }
}

900 바이트의 클래스 파일 (Foo$Shrubbery.class) 로 변환됩니다. 처음 사용할 때, 클래스 초기자는 각각의 열거화된 값들을 표기화 하는 객체상의 <init>메소드를 호출합니다. 각 객체는 정적 필드를 가지게 되고 총 셋은 배열(“$VALUES”라 불리는 정적 필드)에 저장됩니다. 단지 세 개의 정수를 위해 많은 코드와 데이터를 필요로 하게 됩니다.


다음 코드:

Shrubbery shrub = Shrubbery.GROUND;

는 정적 필드 참조를 야기합니다. “GROUND”가 정적 final int 였더라면, 컴파일러는 알려진 상수로서 다루고, inline화 했을 수도 있습니다.


물론, 반대적 측면에서 열거형으로 더 좋은 API를 만들 수 있고 어떤 경우엔 컴파일-타임 값 검사를 할 수 있습니다. 그래서 통상의 교환조건(trade-off)이 적용됩니다: 반드시 공용 API에만 열거형을 사용하고, 성능문제가 중요할 때에는 사용을 피하십시오.


어떤 환경에서는 ordinal() 메소드를 통해 정수 값 열거를 갖는 것이 도움이 될 수 있습니다. 예를 들어, 다음 코드를:

for (int n = 0; n < list.size(); n++) {
if (list.items[n].e == MyEnum.VAL_X)
// do stuff 1
else if (list.items[n].e == MyEnum.VAL_Y)
// do stuff 2
}

다음 코드로 대신합니다:

   int valX = MyEnum.VAL_X.ordinal();
int valY = MyEnum.VAL_Y.ordinal();
int count = list.size();
MyItem items = list.items();

for (int n = 0; n < count; n++)
{
int valItem = items[n].e.ordinal();

if (valItem == valX)
// do stuff 1
else if (valItem == valY)
// do stuff 2
}


때로는, 보장할 수 없습니다만, 이것이 더 빠를 수 있습니다.


내부 클래스와 함께 패키지 범위를 사용하라


다음 클래스 정의를 고려해 봅시다:

public class Foo {
private int mValue;

public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}

private void doStuff(int value) {
System.out.println(“Value is ” + value);
}

private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}
}


여기서 주목해야 할 중요한 것은, 외부 클래스의 private 메소드와 private 인스턴스 필드에 직접 접근하고 있는 내부 클래스(Foo$Inner)를 정의했다는 것입니다. 이것은 적법하고, 코드는 기대했던 대로 “Value is 27″을 출력합니다.


문제는 Foo$Inner는 기술적으로는 (비밀로써) 완전히 분리된, Foo의 private 멤버로 직접적인 접근을 하는 것은 적법하지 못한 클래스라는 것 입니다. 이 차이를 연결짓기 위해, 컴파일러는 두 개의 합성 메소드를 만듭니다:

/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}

내부 클래스 코드는 외부 클래스에 있는 “mValue” 필드에 접근하거나 “doStuff” 메소드를 부르기 위해 이 정적 메소드를 부릅니다. 이것은 이 코드가 결국은 직접적인 방법 대신 접근자 메소드를 통해 멤버 필드에 접근하고 있다는 것을 뜻합니다. 이전에 우리는 어째서 접근자가 직접적인 필드 접근보다 느린지에 대해 이야기 했었는데, 이 문제로서 “보이지 않는” 성능 타격 측면에서 특정 언어의 어법이 야기하게 되는 문제에 대한 예제가 될 수 있겠습니다.


이 문제는 내부 클래스가 접근하는 필드와 메소드 선언에 private 범위가 아닌 package 범위를 가지도록 함으로써 피할 수 있습니다. 이로써 더욱 빠르게 동작하게 되고 자동 생성되는 메소드에 의한 오버헤드를 제거할 수 있습니다. (불운하게도 이 또한 직접적으로 같은 패키지 내의 다른 클래스들이 필드들에 접근할 수 있다는 것을 뜻하게 되며, 모든 필드들은 private로 해야 한다는 표준적인 OO 관습에 거스르게 됩니다. 다시 한번 더 말하자면, 공용 API를 설계하게 된다면 이 최적화를 사용하는 것을 조심스럽게 고민해야만 할 것입니다.)


Float를 피하라


펜티엄 CPU가 출시되기 전, 게임 제작자들에겐 정수 계산에 최선을 다하는 것이 일반적이었습니다. 펜티엄과 함께 부동소수점 계산 보조 프로세서는 일체형이 되었고, 정수와 부동소수점 연산을 넣음에 따라 순수하게 정수 계산만을 사용하는 것 보다 게임은 더 빠르게 되었습니다. 자유롭게 부동소수점을 사용하는 것은 데스크탑 시스템에서는 일반적입니다.


불운하게도, 임베디드 프로세서에게는 빈번하게 하드웨어적으로 부동소수점 계산이 제공되지 않고 있어, “float” 와 “double”의 모든 계산이 소프트웨어적으로 처리됩니다. 어떤 기초적인 부동소수점 계산은 완료까지 대략 일 밀리 초 정도 걸릴 수 있습니다.


또한, 정수에서도 어떤 칩들은 하드웨어 곱셈을 가지고 있지만 하드웨어 나눗셈이 없기도 합니다. 이러한 경우, 정수 나눗셈과 나머지 연산은 소프트웨어적으로 처리됩니다 — 만약 해시 테이블을 설계하거나 많은 계산이 필요하다면 생각해 보아야 할 것입니다.


성능 예시 숫자 몇 개


우리의 몇 가지 아이디어를 설명하기 위해, 약간의 기초적인 행동들에 대해 대략적인 실행시간을 나열한 테이블을 만들었습니다. 이 값들은 절대적인 숫자가 아니라는 것을 주목해 주십시오: CPU시간과 실제 구동 시간의 조합이고, 시스템의 성능 향상에 따라 변화할 수 있습니다. 그러나 이 값들 사이에 관계를 적용해 보는 것은 주목할 만한 가치가 있습니다 — 예를 들어, 멤버 변수를 더하는 것은 지역 변수를 더하는 것보다 대략 네 배가 걸립니다.
















































행동 시간
지역 변수 더하기 1
멤버 변수 더하기 4
String.length() 호출 5
빈 정적 네이티브 메소드 호출 5
빈 정적 메소드 호출 12
빈 가상 메소드 호출 12.5
빈 인터페이스 메소드 호출 15
HashMap의 Iterator:next() 호출 165
HashMap의 put() 호출 600
XML로부터 1 View 객체화(Inflate) 22,000
1 TextView를 담은 1 LinearLayout 객체화(Inflate) 25,000
6개의 View 객체를 담은 1 LinearLayout 객체화(Inflate) 100,000
6개의 TextView 객체를 담은 1 LinearLayout 객체화(Inflate) 135,000
빈 activity 시작 3,000,000

맺음 말


임베디드 시스템을 위해 좋고 효율적인 코드를 작성하는 최선의 방법은 여러분이 작성하는 코드가 실제로 무엇을 하는지 이해하는 것 입니다. 여러분이 정말로 반복자를 할당하기를 원한다면, List에 향상된 반복문을 반드시 사용하십시오; 부주의한 부작용이 아닌 신중한 선택을 통해서 말입니다.


유비무환입니다! 무엇을 하는지 알고 하세요! 좋아하는 좌우명을 여기에 넣으세요, 그러나 언제나 여러분의 코드가 무엇을 하는지 주의 깊게 생각하고, 속도를 높이는 방법을 찾도록 경계하십시오.