안드로이드에서 Java와 NDK(C/C++) 성능에 대해서..



안드로이드 어플리케이션 개발을 위해 JAVA를 쓴다. 하지만 안드로이드에서 C/C++을 쓸수 있도록 구글에서는 NDK라는 것을 만들었다. 그렇다면 NDK(C/C++)에서 JAVA보다 얼마만큼의 성능을 가지는가에 대한 궁금증이 생겼다.

이런 궁금증을 해결 하기위해 용어먼저 설명 하겠다.

NDK

안드로이드는 JAVA를 기반으로 어플리케이션을 만들수 있는데, 기존 C/C++로 된 라이브러리를 JAVA로 재 개발 해야 되는 문제점들을 위해 구글에서 네이티브코드로 만들 수 있는 방법을 제공한 개발킷이다.

JNI

JAVA에서 NDK의 C/C++을 연결 해주는 인터페이스 

그렇다면 수많은 연산을 통해 JAVA와 NDK의 성능을 테스트 해보겠다.

JAVA VS C

똑같은 연산을 하는 JAVA와 C코드를 준비 한다.

 

public int calc() {
    int count = 3;
    int i = 0;
    for (i = 0; i < 100000000; ++i) {
        count = count * 2 / 3 + 5 - 1;
    }

    return count;
}

jint Java_com_example_ndkspeedtest_MainActivity_calc( JNIEnv* env, jobject this)
{
    int count = 3;
    int i = 0;
    for ( i = 0; i < 100000000; ++i )
    {
        count = count * 2 / 3 + 5 - 1;
    }

    return count;
}

JAVA코드와 C코드의 각각 성능 측정을 하였다.

07-20 01:09:20.066: E/Test(19435): Java : 3377, ret : 10

07-20 01:09:22.716: E/Test(19435): native : 2650, ret : 10

—————————————————————-

20%

07-20 01:09:30.326: E/Test(19435): Java : 3366, ret : 10

07-20 01:09:32.966: E/Test(19435): native : 2640, ret : 10

—————————————————————-

27%

07-20 01:09:38.551: E/Test(19435): Java : 3315, ret : 10

07-20 01:09:41.196: E/Test(19435): native : 2645, ret : 10

—————————————————————-

25%

07-20 01:09:47.626: E/Test(19435): Java : 3373, ret : 10

07-20 01:09:50.301: E/Test(19435): native : 2671, ret : 10

—————————————————————-

27%

07-20 01:09:59.601: E/Test(19435): Java : 3362, ret : 10

07-20 01:10:02.276: E/Test(19435): native : 2672, ret : 10

—————————————————————-

25%
평균 C가 JAVA에 비해 25% 빠른 성능을 보여주고 있다. 
단순 연산만으로 측정한 값이며, 그래픽작업등 더 많은 연산의 경우 C가 더욱 우수한 성능을 보여줄 것으로 예상된다.

– powered by hoon038




Android NDK 개발을 위한 환경셜정 for Mac

1) http://developer.android.com/tools/sdk/ndk/index.html

NDK를 받아 적당한 곳에 압축을 푼다. 

2) XCode설치 
3) macPort 설치 
http://www.macports.org/install.php 에서 .pkg파일을 받아 설치 
4) .profile에 ndk path를 지정해준다. 
user$open -e .profile 을 입력 해서 path를 지정한다. 
export PATH=/opt/local/bin:/opt/local/sbin:/android-sdk/platform-tools:/android-sdk/tools:/android-ndk-r8e:$PATH 
5) 명령 창에서 macPort 업데이트를 하고, gmake를 설치 한다. 
$sudo port.selfupdate 
$sudo port install gmake gawk 
만약 아래와 같은 메시지가 나오면 
Computing dependencies for gmakeError: Unable to execute port: can’t read “build.cmd”: Failed to locate ‘make’ in path: ‘/opt/local/bin:/opt/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin’ or at its MacPorts configuration time location, did you move it? 
https://developer.apple.com/downloads/index.action?name=for%20Xcode%20-# 에서 Commend Line Tools을 설치후 다시시도 한다.

 여기까지 설치가 끝나면 완료!

UncaughtExceptionHandler를 이용한 앱 비정상 종료시 Log전송 및 재실행 하기

UncaughtExceptionHandler를 이용한 앱 비정상 종료시 Log전송 및 재실행 하기






요즘 앱을 보면 앱실행중 Exception이 발생하면 앱이 종료 되었다가 재 실행 되는것을 볼 수 있다. 어떻게 앱이 비정상 종료되는 순간을 Catch한것일까?


 


방법은 UncaughtExceptionHandler를 이용하면 된다.






보통 Thread는 try{} catch(Exception e){}외에 발생 하는 예외는 UncaughtExceptionHandler의 uncaughtThread(Thead thread, Throwable ex)를 호출 하게 되어 있다. 그래서 Thread의 UncaughtExceptionHandler 인스턴스를 Thread에 등록하면 되는데, Thread의 static메소드인 setDefaultUncaughtExceptionHandler()를 이용하여 UncaughtExceptionHandler를 set할 수 있다. 






이렇게 하면 별도로 예외처리 하지 않은 부분에서 예외가 발생 하게되면 uncaughtThread(Thead thread, Throwable ex)가 호출되어 유용한 작업들을 할 수있다.




그럼 간략하게 구현 해보자.




import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;

import android.app.Application;
import android.util.Log;

public class MyApplication extends Application {

private UncaughtExceptionHandler mUncaughtExceptionHandler;

@Override
public void onCreate(){

    mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerApplication());

    super.onCreate();
}

/**
 * 메시지로 변환
 * @param th
 * @return
 */
private String getStackTrace(Throwable th) {

    final Writer result = new StringWriter();
    final PrintWriter printWriter = new PrintWriter(result);

    Throwable cause = th;
    while (cause != null) {
        cause.printStackTrace(printWriter);
        cause = cause.getCause();
    }
    final String stacktraceAsString = result.toString();
    printWriter.close();

    return stacktraceAsString;
}

class UncaughtExceptionHandlerApplication implements Thread.UncaughtExceptionHandler{

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        //예외상황이 발행 되는 경우 작업
        Log.e("Error", getStackTrace(ex)); 

        //예외처리를 하지 않고 DefaultUncaughtException으로 넘긴다.
        mUncaughtExceptionHandler.uncaughtException(thread, ex);
    }

}

}




안드로이드에서 Application onCreate()에서 Thread.setDefaultUncaughtExceptionHandler를 구현해주면 끝이다.




예외 상황 발생시 AlarmManager로 앱을 재 실행 하던가, 로그를 저장해두었다가 앱이 재 실행 되면 서버로 전송하는 기능을 구현하면 될 듯 하다.


실제로 ACRA이라는 버그리포팅 해주는 라이브러리가 이런 방법으로 구현 되어 있다. 




레퍼런스도 참고 하기 바란다.


http://developer.android.com/reference/java/lang/Thread.UncaughtExceptionHandler.html











안드로이드 DexClassLoader를 이용한 dex동적으로 로딩


안드로이드 DexClassLoader를 이용한 dex동적으로 로딩











안드로이드의 Dalvik VM은 실행 파일인 dex를 지정된 위치에서 로딩하는 대신, 동적인 위치에서 읽어들일 수 있다. 


DexClassLoader을 이용하는 방법인데, 동적인 위치에서 읽음으로 다음과 같은 장점이 있다.






1. 무거운 어플리케이션을 개발하였는데, Java Heap 사이즈 제한에 걸려 어플리케이션 실행시 메모리 할당을 할 수 없는 경우.


2. 런타임 중 동적으로 기능을 확장이 필요한경우. (티스토어/구글마켓등 앱 업데이트 없이 기능들이 달라진다.)








대부분 어플리케이션에서는 불필요한 작업 이지만, 이러한 장점으로 때때로 유용한 경우가 있다.










클래스 로딩 및 메소드 호출






DexClassLoader cl = new DexClassLoader(dexPath,
dexOutputDir,
null,
getClassLoader());
Class libProviderClazz = null;
try {

 libProviderClazz =
      cl.loadClass("com.example.dex");

  LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();  //라이브러리 인터페이스 클래스
  lib.myMethod(this, "Hello");

} catch (Exception e) { … }




DexClassLoader 클래스 인스턴스를 생성후, dex파일을 로드한다. 이런 방식으로 로드된 메소드 호출을 위해서는 여러가지 방법이 있는데, 위의 방법은 클래스 인스턴스를 바로 인터페이스 캐스팅 한 후, 메소드를 호출하는 방법을 활용하였다. 






Constructor cons = cls.getConstructor();
obj = cons.newInstance();
Method m = cls.getMethod(“myMethod”, String.class); //직접 메소드 호출
m.invoke(obj, “Hello”);




또 다른 방법으로는 위와 같은 Relflection API(Method, Dield, Constructure 클래스)를 이용하는 방법인데, 미리 약속한 인터페이스를 지정하지 않아도 된다는 장점이 있긴 하나, 코드가 지저분해지고 느려진다.




이 Relflection API를 통해 저번에 설명 했던 노티피케이션 Disable을 할 수 있었던 것이다.


2013/01/19 – [개발관련/Android] – StatusBar Disable










이 처럼 외부의 dex를 불러와서 클래스를 로딩 후 메소드를 호출 할 수있다. 외부저장 공간(sdcard)에서 dex파일을 불러오는 방법은 보안에 취약 하기때문에 어플리케이션의 private 파일 패스로 복사하는등 보안문제에 신경써서 사용 하기 바란다.






이 외 빌드프로세스에 대해서 좀 더 알아 보고 싶으면 아래 링크를 참조하자.


http://android-developers.blogspot.kr/2011/07/custom-class-loading-in-dalvik.html









Android Native Heap Size

Android Native Heap Size








Android에서 Memory는 크게 VM Heap Size와 Native Heap Size로 나뉩니다.





CTS관련해서 Gingerbread CDD 문서에 보면 Android VM Memory에 대한 항목이 있습니다.








Device implementations with screens classified as medium- or low-density MUST configure


Dalvik to allocate at least 16MB of memory to each application. Device implementations


with screens classified as high-density or extra-high-density MUST configure Dalvik


to allocate at least 24MB of memory to each application.


Note that device implementations MAY allocate more memory than these figures.






위의 규정에 의하면, Dalvik에서 각각의 애플리케이션에게 메모리 할당을 보장해 주어야 하는 크기는 최소 16M입니다. 하지만,


스크린 density에 따라 24M 까지 보장해야 한다는 말입니다.




Dalvik 상에서 메모리 할당이란 차원에서 이는 Dalvik에서 사용가능한 heap이라고 정의해도 무방할 듯 합니다.


하지만 이것은 최소 요구사항이고 실제 각각의 디바이스에서 제공하는 heap Size를 알기 위해서는 다음과 같은 메쏘드를 사용해서 그 값을 체크해 볼 수 있습니다.











ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

int heapSize = am.getMemoryClass();





더불어, 안드로이드 3.1(HoneyComb)에서는 좀 더 큰 heap을 사용할 수 있는 구조를 지원하고


있는데, 이를 사용하기 위해서는 다음과 같은 방식으로 사용할 수 있습니다.












– AndroidManifest.xml에 android:largeHeap=”true”추가한다. (Android ICS부터 default 속성)



또는




– ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);


int largeHeapSize = am.getLargeMemoryClass();






지금까지의 내용은 전부 Dalvik heap과 관련된 제약(또는 최소요구사항)을 의미합니다.


그렇기 때문에 만약 NDK를 사용해서 shard library를 만들고 그 내부에서 malloc()을 수행한다면 이는 Dalvik에 의해 관리되는 메모리 범위 밖에 존재하게 됩니다.




그러므로 위의 메모리 제약의 제어범위 밖에 놓이게 되고 디바이스가 허용하는 최대 메모리까지 할당 가능합니다.







더불어 안드로이드는 하나의 Process가 하나의 Dalvik VM을 가지기 때문에 위의 제약은 프로세스내의 Dalvik VM 상의 heap 크기에 대한 제약을 의미합니다.




또한 NDK를 통해 malloc()통해 사용되는 메모리는 해당 프로세스 내의 heap을 의미합니다.


32bit 머신에서 개별 프로세스에 부여되는 Virtual Memory 어드레스는 4G이지만, 실제 Physical Memory와 swap지원 여부에 따라 NDK에서 malloc()을 통해 할당할 수 있는 heap 크기는 디바이스별로 다를 수 있습니다.








즉 Dalvik heap은 Process내에서 max size 제약이 있다면, Native heap은 Linux kernel에 의해 관리되는 system wide한 heap내에서 제약을 갖는다고 보시면 됩니다.


 


 



ActivityManager am
= (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);


ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
Log.i(“HEAP”, “avaialble : ” + Long.toString(mi.availMem));

위의 MemoryInfo의 availMem은 Kernel에서 지원하고 있는
현재 사용가능한 heap입니다.


 


 


Virtual Machine Compatibility

Screen Size : Screen Density : Application Memory
small / normal / large ldpi / mdpi 16MB
small / normal / large hdpi / xhdpi 32MB
xlarge mdpi 32MB
xlarge hdpi / xhdpi 48MB


 


 


출처: http://lsit81.tistory.com/59


 

Bitmap OutOfMemoryError

메모리 부족. 안드로이드에서 OutOfMemoryError라 발생하는 가장 많은 경우는 비트맵 로딩때문이다.
안드로이드는 어플리케이션 프로세스별 메모리가 제한되어 있다.(16M, 24M, 32M 등)

문제는 위의 메모리 에러가 DDMS에서 가장 쉽게 확인할 수 있는 메모리 값인 VM Heap 사이즈와는
크게 상관없이 발생한다는 것이다.
Bitmap을 로딩할 경우  VM 내의 힙메모리를 사용하는 게 아니라 VM 밖의 Native 힙메모리 영역을 사용한다고 한다.
그리고 BitmapFactory의 decode함수들은 메모리 Leak이 존재한다고 알려져 있다.

해결 방안 ::

1. 가용 메모리의 확인
 – 아래 API들을 활용해서 Native Heap 값을 확인할 수 있다. 
   Debug.getNativeHeapSize()
   Debug.getNativeHeapFreeSize()
   Debug.getNativeHeapAllocatedSize()

2. 아주 큰 이미지 파일을 불러오는 경우 BitmapFactory.Options.inSampleSize 설정을 통해 축소해서 메모리에 로드한다. 마찬가지로 BitmapFactory.Options.inPureable = true 도 사용한다.(메모리 관리 메소드)











1

2

3

4

5

6

7

8


Bitmap bitmap;

          

BitmapFactory.Options option = new BitmapFactory.Options();

option.inSampleSize = 1;

option.inPurgeable = true;

option.inDither = true;

  

bitmap = BitmapFactory.decodeResource(mRes, mRes.getIdentifier(fileName, null, null), option);

 참고 : http://www.androidpub.com/31659

 참고 : http://gogorchg.tistory.com/tag/java.lang.OutOfMemoryError:%20bitmap%20size%20exceeds%20VM%20budget

3. 더이상 쓰지 않는 Bitmap의 경우 recycle을 호출해서 바로 가용 메모리를 늘려준다.











1

2

3


bitmap.recycle();

bitmap = null;

((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();



4. BitmapFactory를 사용하지 않고 이미지를 바로 불러온다.











1

2

3

4

5

6


ImageView im = new ImageView(this);

File file = new File(“/sdcard/default.jpg”);

Uri uri = Uri.fromFile(file);


Bitmap bm = Images.Media.getBitmap(getContentResolver(), uri);

im.setImageBitmap(bm);


참고 : http://blog.naver.com/wono77?Redirect=Log&logNo=140112928147
참고 : http://www.androidpub.com/?_filter=search&mid=android_dev_info&search_target=title_content&search_keyword=gallery&document_srl=3957

5. BitmapDrawable을 사용한다.











1

2

3


BitmapDrawable drawable = 

            (BitmapDrawable) getResources().getDrawable(R.drawable.icon);

Bitmap bitmap = drawable.getBitmap();


 참고 : http://blog.vizpei.kr/105116344

6. 이미지의 경우 시스템이 알아서 판단해서 적합한 형식으로 로딩하는데 디폴트인 RGB8888(픽셀당 4byte)로
로딩하는 경우가 많다. 이미지를 많이 사용하는 게임등의 경우 투명 이미지는 RGB4444, 불투명 이미지는 RGB565로
충분한 경우가 많으니 BitmapFactory.Options.inPreferredConfig 설정값을 어떻게 주고 있는지 확인해 본다.

* 메모리 릭이 발생하지는 않는지 확인하는 것이 기본이다.




출처 ::
http://www.androidpub.com/1282821