Designing for Performance

An Android application should be fast. Well, it's probably more accurate to say that it should be efficient. That is, it should execute as efficiently as possible in the mobile device environment, with its limited computing power and data storage, smaller screen, and constrained battery life.

안드로이드 애플리케이션은 빨라야 한다. 아마도 그것은 안드로이드 애플리케이션이 효율적이어야 한다고 말하는 것이 더 정확할 것이다. 즉, 그것은 제한된 컴퓨팅 파워와 데이터 저장 공간, 더 작은 스크린, 그리고 한정적인 배터리 수명을 가진 모바일 디바이스 환경에서 가능한 한 효율적으로 실행되여야 한다.

As you develop your application, keep in mind that, while the application may perform well enough in your emulator, running on your dual-core development computer, it will not perform that well when run a mobile device even the most powerful mobile device can't match the capabilities of a typical desktop system. For that reason, you should strive to write efficient code, to ensure the best possible performance on a variety of mobile devices.

여러분이 애플리케이션을 개발할 때, 여러분의 듀얼 코어 개발 컴퓨터에서 실행되는 여러분의 에뮬레이터에서는 그 애플리케이션이 충분히 잘 동작할지라도, 그것이 모바일 디바이스에서 실행될 때는 그렇게 잘 동작하지 않을 지도 모른다 - 심지어 가장 강력한 모바일 디바이스라도 보통의 데스크탑 시스템의 성능과 일치할 수는 없다. 이런 이유로, 여러분은 다양한 모바일 디바이스에서 가능한 최고의 성능을 낼 수 있도록 효율적인 코드를 작성하기 위해 노력해야 한다.

Generally speaking, writing fast or efficient code means keeping memory allocations to a minimum, writing tight code, and avoiding certain language and programming idioms that can subtly cripple performance. In object-oriented terms, most of this work takes place at the method level, on the order of actual lines of code, loops, and so on.

일반적으로 말해서, 빠르거나 효율적인 코드를 작성한다는 것은 최소한의 메모리 할당을 유지하고, 타이트한tight 코드를 작성하고, 그리고 미묘하게 성능이 떨어질 수 있는 어떤 언어와 프로그래밍 문구를 피하는 것이다. 객체지향적인object-oriented 조건에서, 이러한 대부분의 작업은 메쏘드 레벨에서 코드, 루프 등의 실제 라인들의 순서대로 일어난다.

This document covers these topics:

이 문서는 아래의 토픽을 다룬다.

  • 소개
  • 오브젝트를 생성하는 것을 피하라.
  • 네이티브 메쏘드들을 사용하라.
  • 인터페이스(Interface)보다 버추얼(Virtual)을 선호하라.
  • 버추얼(Virtual) 보다 정적(Static)을 선호하라.
  • 내부적인 Getters/Setters를 피하라.
  • 캐쉬 필드 룩업(Lookup)
  • 상수를 Final로 선언하라.
  • Enhanced For Loop 문법을 주의하여 사용하라.
  • Enums를 피하라.
  • 내부 클래스를 가지는 패키지 범주(Scope)를 사용하라.
  • 부동소수점을 피하라.
  • 약간의 샘플 성능 숫자들.
  • 마무리 노트.

Introduction

There are two basic rules for resource-constrained systems:

리소스가 제한된 시스템에는 두 가지의 기본적 규칙이 있다.

  • Don't do work that you don't need to do.
  • Don't allocate memory if you can avoid it.
  • 여러분이 필요로 하지 않는 작업을 하지 마라.
  • 만약 여러분이 피할 수 있다면, 메모리를 할당하지 마라.

All the tips below follow from these two basic tenets.

아래에 적힌 모든 팁들은 이러한 두 가지의 기본적인 원칙으로부터 나온다.

Some would argue that much of the advice on this page amounts to "premature optimization." While it's true that micro-optimizations sometimes make it harder to develop efficient data structures and algorithms, on embedded devices like handsets you often simply have no choice. For instance, if you bring your assumptions about VM performance on desktop machines to Android, you're quite likely to write code that exhausts system memory. This will bring your application to a crawl let alone what it will do to other programs running on the system!

어떤 사람들은 이 페이지에 적힌 대부분의 조언들이 “성급한 최적화premature optimization”에 해당한다고 주장할 수도 있다. 마이크로한 최적화micro-optimization가 때때로 효율적인 데이터 구조와 알고리즘을 개발하기 더 어렵게 만든다는 것이 사실임에도 불구하고, 핸드폰과 같이 임베디드 디바이스에서는 종종 선택의 여지가 없게 된다. 예를 들어 데스크탑 머신에서의 가상머신 성능에 대한 가정을 안드로이드에 가지고 온다면 시스템 메모리를 소진시키는 코드를 작성하게 될 가능성이 크다. 이것은 시스템에서 실행되는 다른 프로그램은 말할 것도 없이 여러분의 애플리케이션이 천천히 실행되도록 할 것이다.

That's why these guidelines are important. Android's success depends on the user experience that your applications provide, and that user experience depends in part on whether your code is responsive and snappy, or slow and aggravating. Since all our applications will run on the same devices, we're all in this together, in a way. Think of this document as like the rules of the road you had to learn when you got your driver's license: things run smoothly when everybody follows them, but when you don't, you get your car smashed up.

위에서 언급한 것은 이 가이드라인이 중요한 이유이다. 안드로이드의 성공은 여러분의 애플리케이션이 제공하는 사용자 경험user experience에 달려있고 사용자 경험user experience은 여러분의 코드가 재빨리 반응하는 지 또는 느린 지에 부분적으로 의존한다. 우리의 모든 애플리케이션이 하나의 동일한 디바이스에서 실행될 것이기 때문에, 한편으로 우리는 이 문제에 모두 함께 속해 있다. 이 문서를 운전면허를 취득할 때 배워야 했던 도로에 대한 규칙처럼 생각해보라: 모든 사람이 그것을 따른다면 일들이 매끄럽게 처리될 것이지만, 그렇지 않는다면 여러분의 차는 부서질smashed up 것이다.

Before we get down to brass tacks, a brief observation: nearly all issues described below are valid whether or not the VM features a JIT compiler. If I have two methods that accomplish the same thing, and the interpreted execution of foo() is faster than bar(), then the compiled version of foo() will probably be as fast or faster than compiled bar(). It is unwise to rely on a compiler to "save" you and make your code fast enough.

우리가 실제 문제를 다루기 전에 간략한 주의 사항이 있다: 아래에 설명된 거의 모든 이슈들은 가상머신이 JIT(Just-In Time) 컴파일러 특징을 가지는지 여부와 상관없이 유효하다. 만약 내가 동일한 것을 달성하기 위한 두 가지 메쏘드를 가지고 있고, foo()에 대한 인터프리티드interpreted 실행이 bar()보다 더 빠르다면, 그러면 foo()의 컴파일된 버전은 bar()의 컴파일된 버전 만큼 빠르거나 또는 더 빠를 것이다. 컴파일러에 의존해서 여러분의 코드를 충분히 빠르게 할 수 있다고 생각하는 것은 현명하지 않다.

Optimize Judiciously

As you get started thinking about how to design your application, consider the cautionary points about optimization that Josh Bloch makes in his book Effective Java. Here's "Item 47: Optimize Judiciously", excerpted from the latest edition of the book with permission. Although Josh didn't have Android application development in mind when writing this section for example, the java.awt.Component class referenced is not available in Android, and Android uses the Dalvik VM, rather than a standard JVM his points are still valid.

There are three aphorisms concerning optimization that everyone should know. They are perhaps beginning to suffer from overexposure, but in case you aren't yet familiar with them, here they are:

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reasonincluding blind stupidity.

William A. Wulf

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Donald E. Knuth

We follow two rules in the matter of optimization:

  • Rule 1. Don't do it.
  • Rule 2 (for experts only). Don't do it yet that is, not until you have a perfectly clear and unoptimized solution.

M. A. Jackson

All of these aphorisms predate the Java programming language by two decades. They tell a deep truth about optimization: it is easy to do more harm than good, especially if you optimize prematurely. In the process, you may produce software that is neither fast nor correct and cannot easily be fixed.

Don't sacrifice sound architectural principles for performance. Strive to write good programs rather than fast ones. If a good program is not fast enough, its architecture will allow it to be optimized. Good programs embody the principle of information hiding: where possible, they localize design decisions within individual modules, so individual decisions can be changed without affecting the remainder of the system (Item 13).

This does not mean that you can ignore performance concerns until your program is complete. Implementation problems can be fixed by later optimization, but pervasive architectural flaws that limit performance can be impossible to fix without rewriting the system. Changing a fundamental facet of your design after the fact can result in an ill-structured system that is difficult to maintain and evolve. Therefore you must think about performance during the design process.

Strive to avoid design decisions that limit performance. The components of a design that are most difficult to change after the fact are those specifying interactions between modules and with the outside world. Chief among these design components are APIs, wire-level protocols, and persistent data formats. Not only are these design components difficult or impossible to change after the fact, but all of them can place significant limitations on the performance that a system can ever achieve.

Consider the performance consequences of your API design decisions. Making a public type mutable may require a lot of needless defensive copying (Item 39). Similarly, using inheritance in a public class where composition would have been appropriate ties the class forever to its superclass, which can place artificial limits on the performance of the subclass (Item 16). As a final example, using an implementation type rather than an interface in an API ties you to a specific implementation, even though faster implementations may be written in the future (Item 52).

The effects of API design on performance are very real. Consider the getSize method in the java.awt.Component class. The decision that this performance-critical method was to return a Dimension instance, coupled with the decision that Dimension instances are mutable, forces any implementation of this method to allocate a new Dimension instance on every invocation. Even though allocating small objects is inexpensive on a modern VM, allocating millions of objects needlessly can do real harm to performance.

In this case, several alternatives existed. Ideally, Dimension should have been immutable (Item 15); alternatively, the getSize method could have been replaced by two methods returning the individual primitive components of a Dimension object. In fact, two such methods were added to the Component API in the 1.2 release for performance reasons. Preexisting client code, however, still uses the getSize method and still suffers the performance consequences of the original API design decisions.

Luckily, it is generally the case that good API design is consistent with good performance. It is a very bad idea to warp an API to achieve good performance. The performance issue that caused you to warp the API may go away in a future release of the platform or other underlying software, but the warped API and the support headaches that come with it will be with you for life.

Once you've carefully designed your program and produced a clear, concise, and well-structured implementation, then it may be time to consider optimization, assuming you're not already satisfied with the performance of the program.

Recall that Jackson's two rules of optimization were "Don't do it," and "(for experts only). Don't do it yet." He could have added one more: measure performance before and after each attempted optimization. You may be surprised by what you find. Often, attempted optimizations have no measurable effect on performance; sometimes, they make it worse. The main reason is that it's difficult to guess where your program is spending its time. The part of the program that you think is slow may not be at fault, in which case you'd be wasting your time trying to optimize it. Common wisdom says that programs spend 80 percent of their time in 20 percent of their code.

Profiling tools can help you decide where to focus your optimization efforts. Such tools give you runtime information, such as roughly how much time each method is consuming and how many times it is invoked. In addition to focusing your tuning efforts, this can alert you to the need for algorithmic changes. If a quadratic (or worse) algorithm lurks inside your program, no amount of tuning will fix the problem. You must replace the algorithm with one that is more efficient. The more code in the system, the more important it is to use a profiler. It's like looking for a needle in a haystack: the bigger the haystack, the more useful it is to have a metal detector. The JDK comes with a simple profiler and modern IDEs provide more sophisticated profiling tools.

The need to measure the effects of attempted optimization is even greater on the Java platform than on more traditional platforms, because the Java programming language does not have a strong performance model. The relative costs of the various primitive operations are not well defined. The "semantic gap" between what the programmer writes and what the CPU executes is far greater than in traditional statically compiled languages, which makes it very difficult to reliably predict the performance consequences of any optimization. There are plenty of performance myths floating around that turn out to be half-truths or outright lies.

Not only is Java's performance model ill-defined, but it varies from JVM implementation to JVM implementation, from release to release, and from processor to processor. If you will be running your program on multiple JVM implementations or multiple hardware platforms, it is important that you measure the effects of your optimization on each. Occasionally you may be forced to make trade-offs between performance on different JVM implementations or hardware platforms.

To summarize, do not strive to write fast programs strive to write good ones; speed will follow. Do think about performance issues while you're designing systems and especially while you're designing APIs, wire-level protocols, and persistent data formats. When you've finished building the system, measure its performance. If it's fast enough, you're done. If not, locate the source of the problems with the aid of a profiler, and go to work optimizing the relevant parts of the system. The first step is to examine your choice of algorithms: no amount of low-level optimization can make up for a poor choice of algorithm. Repeat this process as necessary, measuring the performance after every change, until you're satisfied.

Excerpted from Josh Bloch's Effective Java, Second Ed. (Addison-Wesley, 2008).

1 Wulf, W. A Case Against the GOTO. Proceedings of the 25th ACM National Conference 2 (1972): 791797.

2 Knuth, Donald. Structured Programming with go to Statements. Computing Surveys 6 (1974): 261301.

3 Jackson, M. A. Principles of Program Design, Academic Press, London, 1975. ISBN: 0123790506.

Avoid Creating Objects

Object creation is never free. A generational GC with per-thread allocation pools for temporary objects can make allocation cheaper, but allocating memory is always more expensive than not allocating memory.

오브젝트를 생성하는 것은 결코 자유롭지 않다. 임시적인 오브젝트에 대한 쓰레드별 할당 풀per-thread allocation pools을 사용하는 generational GC(Garbage Collection)은 할당을 더 값싸게 할 수 있다. 그러나 메모리를 할당하는 것은 메모리를 할당하지 않는 것보다 항상 더 값 비싸다.

If you allocate objects in a user interface loop, you will force a periodic garbage collection, creating little "hiccups" in the user experience.

여러분이 사용자 인터페이스 루프에 오브젝트를 할당한다면, 여러분은 사용자 인터페이스 내에 작은 “끊김hiccups”을 만드는 주기적인 가비지 콜렉션(GC, Garbage Collection)을 야기할 것이다.

Thus, you should avoid creating object instances you don't need to. Some examples of things that can help:

따라서, 여러분은 필요하지 않은 오브젝트 인스턴스를 생성하는 것을 피해야 한다. 다음과 같은 약간의 예제들이 도움을 줄 것이다.

  • When extracting strings from a set of input data, try to return a substring of the original data, instead of creating a copy. You will create a new String object, but it will share the char[] with the data.
  • If you have a method returning a string, and you know that its result will always be appended to a StringBuffer anyway, change your signature and implementation so that the function does the append directly, instead of creating a short-lived temporary object.
  • 입력 데이터 집합set으로로부터 문자열을 추출할 때, 복사본을 만드는 대신에 원래 데이터의 하위 문자열substring을 리턴하라. 여러분은 새로운 String 오브젝트를 만들 것이다. 그러나 그것은 그 char[]를 데이터와 공유할 것이다.
  • 만약 여러분이 문자열을 리턴하는 메쏘드를 가지고 있고, 그리고 여러분이 그 결과가 항상 StringBuffer에 어떻게든 추가될 것을 알고 있다면, 짧게 생존하는 임시 오브젝트를 생성하는 대신에 그 함수가 직접적으로 추가하도록 여러분의 시그니쳐signature와 구현implementation을 변경하라.

A somewhat more radical idea is to slice up multidimensional arrays into parallel single one-dimension arrays:

다소 극단적인 아이디어는 다차원의 배열array를 병렬적인 일차원 배열들로 조각내는 것이다.

  • An array of ints is a much better than an array of Integers, but this also generalizes to the fact that two parallel arrays of ints are also a lot more efficient than an array of (int,int) objects. The same goes for any combination of primitive types.
  • If you need to implement a container that stores tuples of (Foo,Bar) objects, try to remember that two parallel Foo[] and Bar[] arrays are generally much better than a single array of custom (Foo,Bar) objects. (The exception to this, of course, is when you're designing an API for other code to access; in those cases, it's usually better to trade correct API design for a small hit in speed. But in your own internal code, you should try and be as efficient as possible.)
  • int 배열은 integer 배열보다 훨씬 더 낫다. 그러나, 이것은 또한 두 개의 병렬 int 배열이 하나의 (int,int) 오브젝트의 배열보다 훨씬 더 효과적이라는 사실로 일반화된다. 이와 같은 것은 프리미티브 타입primitive type들의 임의의 조합에도 적용된다.
  • 여러분이 (Foo,Bar) 오브젝트 집합들을 저장하는 컨테이너를 구현할 필요가 있다면, 두 개의 병렬 Foo[]와 Bar[] 배열이 일반적으로 하나의 (Foo,Bar) 오브젝트 배열보다 더 낫다는 점을 기억하라(물론, 여기에 예외는 여러분이 다른 코드가 접근할 수 있도록 하는 API를 디자인할 때이다. 이런 경우에, 보통 정확한 API 디자인보다 속도 면에서 약간의 향상이 더 낫다. 그러나 여러분 자신의 내부 코드에서, 여러분은 가능한 한 효율적으로 되도록 노력해야만 한다).

Generally speaking, avoid creating short-term temporary objects if you can. Fewer objects created mean less-frequent garbage collection, which has a direct impact on user experience.

일반적으로 이야기해서, 가능하다면 단기적인 임시 오브젝트를 생성하는 것을 피하라. 더 적게 생성된 오브젝트는 더 작은 빈도의 가비지 콜렉션(GC, Garbage Collection)을 의미하며, 이것은 사용자 경험user experience에 직접적인 영향을 준다.

Use Native Methods

When processing strings, don't hesitate to use specialty methods like String.indexOf(), String.lastIndexOf(), and their cousins. These are typically implemented in C/C++ code that easily runs 10-100x faster than doing the same thing in a Java loop.

문자열을 처리할 때, String.indexOf(), String.lastindexOf(), 그리고 그와 유사한 것들과 같은 전문speciality 메쏘드를 사용하는데 주저하지 마라. 이러한 것들은 보통 Java 루프에서 동일한 것을 하는 것 보다 10배에서 100배 더 빨리 쉽게 실행되는 C/C++ 코드로 구현되어 있다.

The flip side of that advice is that punching through to a native method is more expensive than calling an interpreted method. Don't use native methods for trivial computation, if you can avoid it.

이 조언의 이면에는 네이티브 메쏘드를 통한 입력punching이 인터프리티드 메쏘드를 호출하는 것보다 더 비싸다는 것이 있다. 만약 여러분이 그것을 피할 수 있다면, 사소한 연산에는 네이티브 메쏘드를 사용하지 마라.

Prefer Virtual Over Interface

Suppose you have a HashMap object. You can declare it as a HashMap or as a generic Map:

여러분이 HashMap 오브젝트를 가지고 있다고 가정하자. 여러분은 그것을 HashMap 또는 일반적인 Map으로 선언할 수 있다.

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

Which is better?

어떤 쪽이 더 나은가?

Conventional wisdom says that you should prefer Map, because it allows you to change the underlying implementation to anything that implements the Map interface. Conventional wisdom is correct for conventional programming, but isn't so great for embedded systems. Calling through an interface reference can take 2x longer than a virtual method call through a concrete reference.

전통적인 지식은 여러분이 Map을 더 선호해야 한다고 말한다. 왜냐하면 그것은 여러분으로 하여금 기본 구현을 Map 인터페이스 구현으로 변경하는 것을 허용하기 때문이다. 전통적인 프로그래밍에 대해서서는 전통적 지식이 맞다. 그러나, 임베디드 시스템에 대해서는 그렇지 않다. 인터페이스 레퍼런스를 통해 호출하는 것은 구체적인 레퍼런스를 통해 가상 메쏘드를 호출하는 것 보다 두 배 더 오래 걸릴 수 있다.

If you have chosen a HashMap because it fits what you're doing, there is little value in calling it a Map. Given the availability of IDEs that refactor your code for you, there's not much value in calling it a Map even if you're not sure where the code is headed. (Again, though, public APIs are an exception: a good API usually trumps small performance concerns.)

만약 여러분이 하고 있는 것에 잘 맞기 때문에 HashMap을 선택했다면, 그것을 Map이라고 부르는 것에는 거의 의미가 없다. 여러분을 위해 여러분의 코드를 재분해하는refactor IDEs의 유효성 하에서 여러분이 그 코드가 향하는 곳이 어디인 지를 확실히 모름에도 불구하고 그것을 Map이라고 부르는 것에는 별로 의미가 없다(다시말해, public APIs는 예외이다: 좋은 API는 대개 사소한 성능 우려사항을 무시한다).

Prefer Static Over Virtual

If you don't need to access an object's fields, make your method static. It can be called faster, because it doesn't require a virtual method table indirection. It's also good practice, because you can tell from the method signature that calling the method can't alter the object's state.

만약 여러분이 오브젝트의 필드에 접근할 필요가 없다면, 여러분의 메쏘드를 정적static으로 만들어라. 그것은 더 빠르게 호출될 수 있다. 왜냐하면 그것이 버추얼 메쏘드 테이블 인다이렉션indirection을 요구하지 않기 때문이다. 그것은 또한 좋은 사례이다. 왜냐하면 여러분은 그 정적인 메쏘드를 호출하는 것이 오브젝트 상태를 변경할 수 없다는 것을 그 정적 메쏘드 시그니처signature로부터 알 수 있기 때문이다.

Avoid Internal Getters/Setters

In native languages like C++ it's common practice to use getters (e.g. i = getCount()) instead of accessing the field directly (i = mCount). This is an excellent habit for C++, because the compiler can usually inline the access, and if you need to restrict or debug field access you can add the code at any time.

C++와 같은 네이티브 언어들에서 필드를 직접 접근(i = mCount)하는 것 대신에 getters(i = getCount())를 사용하는 것은 일반적인 사례이다. 이것은 컴파일러가 보통 그 접근을 인라인inline할 수 있기 때문에, C++을 위해서는 아주 훌륭한 방법이다. 그리고 만약 여러분이 필드 접근을 제한하거나 디버그하는 것이 필요하다면 여러분은 언제라도 그 코드를 추가할 수 있다.

On Android, this is a bad idea. Virtual method calls are expensive, much more so than instance field lookups. It's reasonable to follow common object-oriented programming practices and have getters and setters in the public interface, but within a class you should always access fields directly.

안드로이드에서 이것은 나쁜 생각이다. 버추얼 메쏘드 호출은 인스턴스 필드 룩업lookup 보다 훨씬 더 값비싸다. 일반적인 객체지향object-oriented 프로그래밍 사례를 따르고 퍼블릭public 인터페이스에서 getter와 setter를 가지는 것은 합리적이다. 그러나 하나의 클래스 내에서 여러분은 항상 필드들을 직접 접근해야 한다.

Cache Field Lookups

Accessing object fields is much slower than accessing local variables. Instead of writing:

오브젝트 필드에 접근하는 것이 지역 변수에 접근하는 것보다 훨씬 느리다. 다음과 같이 작성하는 것 대신에

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

You should write:

여러분은 이렇게 작성해야 한다.

  int count = this.mCount;
  Item[] items = this.mItems;
 
  for (int i = 0; i < count; i++)
      dumpItems(items[i]);

(We're using an explicit "this" to make it clear that these are member variables.)

(우리는 이것들이 멤버 변수들이라는 것을 명료하게 하기 위해 명시적인 “this”을 사용하고 있다)

A similar guideline is never call a method in the second clause of a "for" statement. For example, the following code will execute the getCount() method once per iteration, which is a huge waste when you could have simply cached the value as an int:

비슷한 가이드라인은 “for” 구문의 두 번째 절에서 메쏘드를 결코 호출하지 않는 것이다. 예를 들어 아래의 코드는 반복iteration될 때마다 한 번씩 getCount() 메쏘드를 실행할 것이다. 그것은 엄청난 낭비이다. 여러분은 그 값을 int로 간단히 캐쉬할 수 있었다.

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

It's also usually a good idea to create a local variable if you're going to be accessing an instance field more than once. For example:

만약 여러분이 한번 이상 인스턴스에 접근하려고 한다면 지역 변수를 생성하는 것은 또한 보통 좋은 생각이다. 예를 들어 다음과 같다.

    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);
        }
    }

That's four separate lookups of the member field mScrollBar. By caching mScrollBar in a local stack variable, the four member field lookups become four stack variable references, which are much more efficient.

위의 것은 멤버 필드 mScrollBar의 네 가지의 별도의 룩업lookup이다. 로컬 스택 변수에 mScrollBar를 캐쉬함으로써 네 가지 멤버 필드 룩업lookup은 스택 변수 레퍼런스가 되고, 그것이 훨씬 더 효율적이다.

Incidentally, method arguments have the same performance characteristics as local variables.

덧붙여 말하자면, 메쏘드 아규먼트들은 로컬 변수와 같은 동일한 성능 특징을 가진다.

Declare Constants Final

Consider the following declaration at the top of a class:

클래스의 맨 위에 있는 다음과 같은 선언을 생각해 보라.

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

The compiler generates a class initializer method, called <clinit>, that is executed when the class is first used. The method stores the value 42 into intVal, and extracts a reference from the classfile string constant table for strVal. When these values are referenced later on, they are accessed with field lookups.

컴파일러는 <clinit>라고 불리는 클래스 이니셜라이저initializer 메쏘드를 생성한다. 클래스 이니셜라이저는 클래스가 처음으로 사용될 때 실행된다. 이 메쏘드는 intVal에 값 42를 저장한다. 그리고 strVal을 위한 클래스 파일 문자열 상수 테이블로부터 하나의 레퍼런스를 추출한다. 이러한 값들이 나중에 레퍼런스될 때면, 그것들은 필드 룩업을 사용해서 접근된다.

We can improve matters with the "final" keyword:

우리는 “final” 키워드를 사용해서, 이 문제들을 개선할 수 있다.

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

The class no longer requires a <clinit> method, because the constants go into classfile static field initializers, which are handled directly by the VM. Code accessing intVal will use the integer value 42 directly, and accesses to strVal will use a relatively inexpensive "string constant" instruction instead of a field lookup.

그 클래스는 더 이상 <clinit> 메쏘드를 요구하지 않는다. 왜냐하면 그 상수들은 클래스 파일 정적static 필드 이니셜라이저initializers 안으로 가고, 가상머신에 의해 직접적으로 처리되기 때문이다. intVal을 접근하는 코드는 정수 값 42를 직접 사용할 것이다. 그리고 strVal에 대한 접근들은 필드 룩업lookup 대신에 상대적으로 값싼 “string constant” 명령어를 사용할 것이다.

Declaring a method or class "final" does not confer any immediate performance benefits, but it does allow certain optimizations. For example, if the compiler knows that a "getter" method can't be overridden by a sub-class, it can inline the method call.

메쏘드 또는 클래스 “final”을 선언하는 것이 즉각적인 성능 잇점을 주지는 않는다. 그러나 그것은 어느 정도의 최적화를 허용한다. 예를 들어 만약 컴파일러가 “getter” 메쏘드는 서브클래스에 의해 오버라이드 될 수 없다는 것을 안다면, 그 컴파일러는 메쏘드 호출을 인라인inline할 수 있다.

You can also declare local variables final. However, this has no definitive performance benefits. For local variables, only use "final" if it makes the code clearer (or you have to, e.g. for use in an anonymous inner class).

여러분은 또한 지역 변수를 final로 선언할 수 있다. 하지만 이것은 명확한 성능 잇점을 가지지 않는다. 지역 변수에 대해서, “final”이 그 코드를 더 명료하게 만든다면 그것을 사용하라(또는 예를 들어 익명의 내부(inner) 클래스에서 final을 사용하라).

Use Enhanced For Loop Syntax With Caution

The enhanced for loop (also sometimes known as "for-each" loop) can be used for collections that implement the Iterable interface. With these objects, an iterator is allocated to make interface calls to hasNext() and next(). With an ArrayList, you're better off walking through it directly, but for other collections the enhanced for loop syntax will be equivalent to explicit iterator usage.

(때때로 “for-each” loop로 알려져 있는) enhanced for loop는 Iterable interface를 구현하는 콜렉션을 위해 사용될 수 있다. 이러한 오브젝트 콜렉션을 가지고 iterator는 hasNext()와 next()에 대한 인터페이스 호출을 위해서 할당된다. ArrayList로 여러분은 그 콜렉션을 직접 더 잘 다룰 수 있다. 그러나 다른 콜렉션에 대해서 enhanced for loop 문법은 명시적인 iterator 사용법과 같다.

Nevertheless, the following code shows an acceptable use of the enhanced for loop:

그럼에도 불구하고 다음의 코드는 enhanced for loop의 수용가능한 사용 예제를 보여준다.

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() retrieves the static field twice and gets the array length once for every iteration through the loop.

zero()는 정적 필드static field를 두 번 얻어오고 루프를 통해 반복할 때마다 배열 길이array length를 얻는다.

one() pulls everything out into local variables, avoiding the lookups.

one()은 모든 것을 지역 변수local variables로 바꾸고 검색lookup을 피하게 한다.

two() uses the enhanced for loop syntax introduced in version 1.5 of the Java programming language. The code generated by the compiler takes care of copying the array reference and the array length to local variables, making it a good choice for walking through all elements of an array. It does generate an extra local load/store in the main loop (apparently preserving "a"), making it a teensy bit slower and 4 bytes longer than one().

two()는 Java 프로그래밍 언어 버전 1.5에서 소개된 enhanced for loop 문법을 사용한다. 컴파일러에 의해 발생한 이 코드는 배열 레퍼런스array reference와 배열 길이array length를 지역 변수local variables로 복사하며, 이것은 배열의 모든 구성요소를 다루는 데 좋은 선택이 되게 한다. 이것은 (분명히 “a”를 보존하는) main loop에서 추가적인 지역적인 로드/저장local load/store를 발생시키고 one()보다는 약간 느리고 4 바이트 길어진다.

To summarize all that a bit more clearly: enhanced for loop syntax performs well with arrays, but be cautious when using it with Iterable objects since there is additional object creation.

이 모든 것을 좀 더 분명하게 요약하자면: enhanced for loop 문법은 배열arrays와 함께 잘 동작하지만 그것을 Iterable(반복) 오브젝트와 함께 사용할 때면 추가적인 오브젝트 생성이 있으므로 주의하여야 한다.

Avoid Enums

Enums are very convenient, but unfortunately can be painful when size and speed matter. For example, this:

Enums는 매우 편리하다. 그러나 불행하게도 크기와 속도가 문제가 될 때 힘들어 질 수 있다. 예를 들어,

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

turns into a 900 byte .class file (Foo$Shrubbery.class). On first use, the class initializer invokes the <init> method on objects representing each of the enumerated values. Each object gets its own static field, and the full set is stored in an array (a static field called "$VALUES"). That's a lot of code and data, just for three integers.

위의 문장은 900 바이트의 .class 파일 (Foo$Shrubbery.class)로 바뀐다. 처음 사용할 때, 클래스 이니셜라이저(initialize)는 열거된 각각의 값들을 나타내는 오브젝트들에 대한 <init> 메쏘드를 호출한다. 각각의 오브젝트는 그 자신의 정적static 필드를 얻는다. 그리고 그 전체 집합은 하나의 배열(“$VALUES”로 불리는 정적 필드)에 저장된다. 이것은 단지 세 개의 정수integer에 대해서는 많은 양의 코드와 데이터이다.

This:

Shrubbery shrub = Shrubbery.GROUND;

causes a static field lookup. If "GROUND" were a static final int, the compiler would treat it as a known constant and inline it.

위의 문장은 정적(static) 필드 룩업을 초래한다. 만약 “GROUND”가 static final int라면 컴파일러는 그것을 하나의 알려진 상수로 취급할 것이고, 그것을 인라인inline할 것이다.

The flip side, of course, is that with enums you get nicer APIs and some compile-time value checking. So, the usual trade-off applies: you should by all means use enums for public APIs, but try to avoid them when performance matters.

물론 이면에는, 여러분이 enums를 사용해서 더 나은 API들과 약간의 컴파일 시점의 값 체크를 얻는다. 그러므로 통상적인 트레이드오프trade-off가 적용된다: 여러분은 반드시 public API에서는 enums를 사용해야 한다. 그러나 성능 문제가 있을 때는 enum 사용을 피하도록 하라.

In some circumstances it can be helpful to get enum integer values through the ordinal() method. For example, replace:

어떤 환경에서는 그것은 ordinal() 메쏘드를 통해서 enum 정수integer 값을 얻는 것이 도움이 될 수 있다. 예를 들어 아래의 코드를

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
}

with:

다음과 같이 바꾸어라.

   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
   }

In some cases, this will be faster, though this is not guaranteed.

어떤 경우에는, ordinal() 메쏘드를 통해 enum 정수 값을 얻는 것이 완전히 보장되지는 않아도 더 빠를 것이다.

Use Package Scope with Inner Classes

Consider the following class definition:

다음의 클래스 정의를 생각해 보라.

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);
        }
    }
}

The key things to note here are that we define an inner class (Foo$Inner) that directly accesses a private method and a private instance field in the outer class. This is legal, and the code prints "Value is 27" as expected.

여기서 주목해야 할 중요한 것은 외부outer 클래스에 있는 프라이빗private 메쏘드와 프라이빗private 인스턴스 필드에 직접 접근하는 내부inner 클래스(Foo$Inner)를 정의했다는 점이다. 이것은 적법하다. 그리고 그 코드는 기대한 대로 “Value is 27”을 출력한다.

The problem is that Foo$Inner is technically (behind the scenes) a totally separate class, which makes direct access to Foo's private members illegal. To bridge that gap, the compiler generates a couple of synthetic methods:

문제는 Foo$Inner가 기술적으로 (배후의) 하나의 완전히 별도의 클래스이고, 이는 Foo의 프라이빗private 멤버들에 대한 직접 접근을 불법으로 만든다는 데 있다. 그 간격을 메우기 위해, 컴파일러는 두개의 합성 메쏘드들synthetic methods을 생성한다.

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

The inner-class code calls these static methods whenever it needs to access the "mValue" field or invoke the "doStuff" method in the outer class. What this means is that the code above really boils down to a case where you're accessing member fields through accessor methods instead of directly. Earlier we talked about how accessors are slower than direct field accesses, so this is an example of a certain language idiom resulting in an "invisible" performance hit.

내부 클래스Inner-class 코드는 외부 클래스outer class에 있는 “mValue” 필드를 접근하거나 또는 “doStuff” 메쏘드를 호출해야 할 필요가 있을 때마다 이러한 정적static 메쏘드를 호출한다. 이것이 의미하는 바는 위의 코드가 여러분이 직접적으로 멤버 필드들을 접근하는 대신에 접근자accessor 메쏘드를 통해 실제로 그 멤버 필드들을 접근하는 경우가 되는 것이다. 앞에서 우리는 접근자accessors가 어떻게 직접적인 필드 접근보다 느린지에 대해 이야기 했다. 그러므로 이것은 보이지 않는 성능 문제를 일으키는 특정 언어 구문의 예제이다.

We can avoid this problem by declaring fields and methods accessed by inner classes to have package scope, rather than private scope. This runs faster and removes the overhead of the generated methods. (Unfortunately it also means the fields could be accessed directly by other classes in the same package, which runs counter to the standard OO practice of making all fields private. Once again, if you're designing a public API you might want to carefully consider using this optimization.)

우리는 이 문제를 내부 클래스에 의해 접근되는 필드와 메쏘드를 프라이빗private 범주private scope보다는 패키지 범주package scope을 가지도록 선언함으로써 피할 수 있다. 이것은 더 빨리 실행되고 생성된 메쏘드의 과부하overhead를 제거한다(불행하게도 그것은 또한 그 필드가 동일한 패키지에 있는 다른 클래스들에 의해서 직접적으로 접근될 수 있다는 것을 의미하며, 이것은 모든 필드를 프라이빗(private)하게 만드는 표준 OO 사례에 어긋난다.다시 한번, 여러분이 public API를 디자인하고 있다면, 여러분은 주의깊게 이러한 최적화를 사용하는 것을 고려하기를 원할 수도 있다).

Avoid Float

Before the release of the Pentium CPU, it was common for game authors to do as much as possible with integer math. With the Pentium, the floating point math co-processor became a built-in feature, and by interleaving integer and floating-point operations your game would actually go faster than it would with purely integer math. The common practice on desktop systems is to use floating point freely.

펜티엄 CPU가 나오기 이전에, 게임 개발자가 가능한 한 많은 것을 정수integer 수치연산을 사용하는 것이 일반적이었다. 펜티엄과 함께, 부동소수점 수치 연산 보조 프로세서가 내장되었다. 그리고 정수integer와 부동소수점floating-point 오퍼레이션을 끼워 넣음으로써 여러분의 게임은 순수하게 정수 수치 연산만을 사용했을 때보다 실제로 더 빨라지게 되었다. 데스크탑 시스템상에서 일반적인 사례는 부동소수점을 자유롭게 사용하는 것이다.

Unfortunately, embedded processors frequently do not have hardware floating point support, so all operations on "float" and "double" are performed in software. Some basic floating point operations can take on the order of a millisecond to complete.

불행하게도, 임베디드 프로세서들은 종종 하드웨어 부동 소수점 지원을 지원하지 않는다. 그러므로 “float”와 “double”에 대한 모든 오퍼레이션들은 소프트웨어에서 수행된다. 약간의 기본적인 부동 소수점 오퍼레이션들을 완료하기 위해 대략 1 밀리세컨드를 가질 수 있다.

Also, even for integers, some chips have hardware multiply but lack hardware divide. In such cases, integer division and modulus operations are performed in software something to think about if you're designing a hash table or doing lots of math.

또한, 심지어 정수integer에 대해서도, 일부 칩은 하드웨어적 곱셈은 가지고 있지만 하드웨어적인 나눗셈은 가지고 있지 않다. 이러한 경우에, 정수 나눗셈과 몫 오퍼레이션은 소프트웨어로 수행된다 - 여러분이 해쉬 테이블hash table을 디자인하고 있거나 또는 많은 수치 연산을 하고 있는지 생각해 봐야 한다.

Some Sample Performance Numbers

To illustrate some of our ideas, here is a table listing the approximate run times for a few basic actions. Note that these values should NOT be taken as absolute numbers: they are a combination of CPU and wall clock time, and will change as improvements are made to the system. However, it is worth noting how these values apply relative to each other for example, adding a member variable currently takes about four times as long as adding a local variable.

우리의 생각들 일부를 설명하기 위해서, 여기에 몇 가지 기본 액션에 대한 대략적인 실행 시간을 나열하는 테이블이 있다. 이 값들은 절대 숫자로 받아들여져서는 안된다는 사실에 주의하라: 이 값들은 CPU와 벽 시계 시간의 조합이며, 시스템이 향상되면 변하게 된다. 하지만 이 값들이 각기 다른 것에 대해 얼마나 상대적으로 적용되는 지에 주목할 가치가 있다. 예를 들어 일반적으로 하나의 멤버 변수를 추가하는 것은 하나의 로컬 변수를 추가하는 시간의 네 배가 걸린다.

Action Time
Add a local variable 1
Add a member variable 4
Call String.length() 5
Call empty static native method 5
Call empty static method 12
Call empty virtual method 12.5
Call empty interface method 15
Call Iterator:next() on a HashMap 165
Call put() on a HashMap 600
Inflate 1 View from XML 22,000
Inflate 1 LinearLayout containing 1 TextView 25,000
Inflate 1 LinearLayout containing 6 View objects 100,000
Inflate 1 LinearLayout containing 6 TextView objects 135,000
Launch an empty activity 3,000,000

Closing Notes

The best way to write good, efficient code for embedded systems is to understand what the code you write really does. If you really want to allocate an iterator, by all means use enhanced for loop syntax on a List; just make it a deliberate choice, not an inadvertent side effect.

임베디드 시스템에 대한 좋고, 효율적인 코드를 작성하는 최고의 방법은 여러분이 작성한 코드가 실제로 무엇을 하는지를 이해하는 것이다. 만약 여러분이 반복실행자iterator 를 할당하고자 한다면, 반드시 List에 대한 enhanced for loop 문법을 사용하라: 그것이 의도하지 않은 부작용으로 작용하지 않게 하고, 신중한 선택이 되도록 하라.

Forewarned is forearmed! Know what you're getting into! Insert your favorite maxim here, but always think carefully about what your code is doing, and be on the lookout for ways to speed it up.

사전경고는 사전 무장과 같다!(유비무환). 여러분이 어디로 가고 있는지를 알라! 여러분이 가장 좋아하는 좌우명을 여기에 넣어라. 그러나 항상 여러분의 코드가 무엇을 하고 있는 지에 대해 주의깊게 생각하라. 그리고 그것의 속도를 올리기 위한 방법에 대해 고려하라.

↑ Go to top