Tistory View

쓰레드를 생성 후 만들어진 쓰레드를 CPU의 특정 코어로 이동할 수 있다. 필자가 알기로는 쓰레드를 생성하면서 코어를 지정하는 방법은 없는 것으로 알고 있다. 따라서, 쓰레드를 만들고 만들어진 쓰레드를 특정 코어로 이동시키는 방식으로 사용해야 한다.

 

일단 이 작업을 하기 위해서는 NDK를 이용해야 한다. Java파트에서 지원하는 지는 모르겠지만, 지원하지 않는다는 가정하고 이 글을 작성한다. 안드로이드도 리눅스 커널을 사용하기 때문에 리눅스와 대부분 사용법이 동일하다.

 

 

이 작업을 하기위해서는 사전작업이 좀 필요한 데, 일단 시스템내에 core가 몇 개나 있는 지 알아야 하고, 어떤 녀석이 Big코어인지, Little코어인지도 구분을 해야 한다. 또한 현재 쓰레드가 어느 core에 할당되어 있는 지도 알아야 할 것이다.

 

몇 개의 코어가 있는지 알아내기

방법 1

시스템에 코어가 몇 개인지 알아내는 가장 쉬운 방법은 Java에서 알아내는 방법이다.

Runtime.getRuntime().availableProcessors();

하지만 이 방법에는 좀 문제가 있는 데, OS가 전력사용을 줄이려 코어의 일부를 일부러 가동시키지 않는 경우 이 값은 실제 코어수가 4개라도 4보다 적은 값을 리턴하는 경우가 있다. 따라서 정확한 코어수를 알아내는 데에는 문제가 있지만, 대부분의 상황에서 실제 코어수를 잘 리턴해주기 때문에 큰 문제 없이 사용할 수는 있다.

단점은 코어의 개수만 파악이 가능하고 어떤 코어가 빠른 것인지, 저전력인지 구분을 할 수 없다.

 

방법 2

두번째 방법은 좀 더 정확히 정보를 알아낼 수 있다.

/proc/cpuinfo파일을 보면 processer라는 필드가 있고 이 processer라는 것이 몇 개인지 세기만 하면 된다. 다른 추가정보는 분석해내는 데에는 일일이 작업하기에는 번거로우니 신경쓰지 말자.

 

"/proc/cpuinfo"

"/sys/devices/system/cpu/cpu[XXXX]/cpufreq/cpuinfo_max_freq"
"/sys/devices/system/cpu/cpu[XXXX]/topology/physical_package_id"

 

위 세 개파일을 잘 조합하면 processer갯수와 속도와 BIG.little방식을 구분해 낼 수 있다.

"physical_package_id"파일을 읽으면 해당 코어가 어떤 Cluster에 묶여있는 지 파악이 가능하다.

 

보통 뒤쪽에 빠른 코어가 배치되어 있고, 앞쪽에는 저전력 코어가 배치된다. 이 규칙은 바뀔 수 있지만, 필자가 테스트한 모든 기기에서는 이 배치를 다 지키고 있었다. cpuinfo_max_freq를 이용하여 Big.little을 구분할 수 있을 것 같지만, 필자의 기기중이 Big.Little임에도 불구하고 모든 코어의 최대속도가 동일하게 표시되는 경우가 있어 cpuinfo_max_freq를 이용하여 Big.Little구분은 할 수 없었다.

 

방법 3

이 방법은 실제 사용하지 않아도 되나.. 필자의 기기 중 좀 특이하게 꾸진놈이 있는 데, cpuinfo파일에 processer라는 필드가 전혀 존재하지 않는 어이없는 경우가 발생했다.(주1)

방법 2에서 cpuinfo파일을 읽지 않고 "/sys/devices/system/cpu/cpu[XXXX]/cpufreq/cpuinfo_max_freq"파일들을 읽어서 개수를 세는 방법이다.

이 방법의 문제는 방법1과 같이 OS가 일부러 죽인 코어가 중간에 있을 때, 모든 코어의 정보를 알 수 없다는 것이다.

 

 

역시 안드로이드는 한번에 되는 것이 하나도 없다.

이 세가지 방법을 조합하여 가장 많은 코어수를 알아내는 방식으로 코드를 작성하기로 했다.

 

 

 

Affinity(굳이 번역해서 "친화도")

쓰레드에 친화도는 어떤 processer를 사용할 것인지를 나타낸다. 이 것은 사용할 코어를 bit로 켜고 끄고 하는 방식을 사용한다.

cpu_set_t라는 타입이 있는 데, 이 것은 실제 단순한 bit들의 연속일 뿐이다.

 

만약 다음과 같이 설정하면 4,5,6,7프로세서 중 1개를 사용하게 된다.

Affinity와 Bits

 

어떤 것이 잡힐 지는 상황에 따라 달라질 것이고, 심지어는 이 네 개의 프로세서들을 상황에 따라 수시로 이동(Switching)한다. 한번 실행될 프로세서가 정해졌다고 쓰레드가 이 Procceser에서 끝날 때까지 지속되는 것이 아니다. 실제 테스트를 해보면 정말 2초도 안지난 것 같은 데 바뀌는 경우가 많다.  1개만 사용하도록 하면 지정한 한 개의 프로세서를 사용하겠지만, 초저전력모드와 같은 특이한 상황에서는 지정하지 않은 코어로 이동 될 수도 있다.

 

 

 

 

 

NDK에서에 사용하기

header파일은 <sched.h>이다.

현재 쓰레드가 어떤 프로세서에 할당되어 있는 지 알아내기

int sched_getcpu();

이 함수는 현재 쓰레드가 어떤 코어에 할당 되어있는 지를 알아낸다. 첫번째 코어는 "0"이다.(두번째는 "1")

다른 쓰레드의 Processer번호를 알아내는 함수는 없다. 굳이 알고 싶다면 좀 어려운 코드를 작성해야한다. 단순히 함수호출만으로는 할 수가 없다. 솔직히 다른 쓰레드의 프로세서번호를 알아내는 것은 거의 쓸일이 없다.

 

쓰레드에 Affinity지정하기

다음의 예제는 현재 쓰레드에 사용할 프로세서[0번~3번]를 지정하는 코드이다.

cpu_set_t set;
CPU_ZERO( &set );      // 쓰레기 값을 깨끗이 지워주고
CPU_SET( 0, &set );    
CPU_SET( 1, &set );
CPU_SET( 2, &set );
CPU_SET( 3, &set );
// int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
sched_setaffinity( 0, sizeof( cpu_set_t ), &set );

sched_setaffinity함수

    pid_t는 쓰레드 값을 지정하면 되고, 0을 넣으면 현재 쓰레드를 사용하게 된다.

    리턴값은 0일 경우 성공을 나타난다.

    참고로 현재의 Thread번호를 알아내려면 "int gettid()"함수를 사용하면 된다.

 

쓰레드의 Affinity값 가져오기

다음의 코드는 현재 Thread의 Affinity값을 가지고 오는 코드다.

cpu_set_t set;
CPU_ZERO( &set );      // 쓰레기 값을 깨끗이 지워주고
// int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
sched_getaffinity( 0, sizeof( cpu_set_t ), &set );

sched_getaffinity함수의 두번째 파라미터인 cpusetsize는 꼭 sizeof(cpu_set_t)을 넘겨야 잘 동작한다.(주2)

 

 

API사용하기

CCpuAffinity.zip
0.01MB

 

 

 

 

위의 내용을 바탕으로 Api를 간단하게 만들어 보았다.

Big.Little 방식이 아닌 것은 그냥 Big코어로 처리되도록 작성하였다.

#define CPUA_BIGCORE 0
#define CPUA_LITCORE 1
#define CPUA_MIDCORE 2
struct PROCESSERINFO {
    int cpu;
    int frequency;
    int packageId;
    int coreType; // CPUA_* 
};

 

 

static int CCpuAffinity::GetProcesserCount();

전체 프로세서 개수를 돌려 준다.

static bool CCpuAffinity::GetProcesserInfo(PROCESSERINFO* p );

모든 프로세서의 정보를 가지고 온다. p의 필요한 크기는 GetProcesserCount()함수로 구할 수 있다.

static int CCpuAffinity::GetProcesserCountByCoreType( int type );

프로세서 중 CPUA_BIGCORE, CPUA_MIDCORE,  CPUA_LITCORE 세가지를 구분하여 개수를 가지고 온다.

static bool CCpuAffinity::GetProcesserInfoByCoreType( PROCESSERINFO* p, int type );

프로세서 중 CPUA_BIGCORE, CPUA_MIDCORE,  CPUA_LITCORE 세가지를 구분하여 가지고 온다. p의 크기는 GetProcesserCountByCoreType()함수로 구할 수 있다.

 

static int CCpuAffinity::SetAffinity( int tid, int n, ... );

쓰레드에 Cpu Affinity를 지정한다.

n은 ... 에 갯수이며, ...은 사용할 processer번호이다

 

static int CCpuAffinity::SetAffinity( int tid, int n, int* cpus );

쓰레드에 Cpu Affinity를 지정한다.

n은 cpus의 개수

 

static void CCpuAffinity::GetProcessersOrderByPerformance( PROCESSERINFO* p );

Cpu를 성능순으로 가지고 온다. 성능이 좋은 녀석이 낮은 인덱스에 배치되고 저전력이 마지막 부분에 배치된다.

만약 저전력 순으로 가지고 오려면, 그냥 GetProcesserInfo()함수를 사용하면 된다.

 

NDK 예제

전체 프로세스 정보 가지고 오기

LOGD( TAG, "All Processers" );
const int count = CCpuAffinity::GetProcesserCount();
PROCESSERINFO procInfo[count];
CCpuAffinity::GetProcesserInfo(procInfo);
for (int i = 0; i < count; i++) {
    PROCESSERINFO *info = procInfo + i;
    LOGI(TAG, "#%d freq=%.2fGHz package=%d type=%s",
        info->cpu,
        info->frequency / (float) 1000000.0F,
        info->packageId,
        CCpuAffinity::CoreTypeToString( info->coreType ) );
}

 

현재 쓰레드를 Little Processer(아무거나)에 할당

const int count = CCpuAffinity::GetProcesserCountByCoreType( CPUA_LITCORE );
if( count > 0 ) {
    int affinityBits[count];
    
    PROCESSERINFO procInfo[count];
    CCpuAffinity::GetProcesserInfoByCoreType(procInfo, CPUA_LITCORE );
    
    for (int i = 0; i < count; i++) {
        affinityBits[i] = procInfo[i].cpu;
    }
    
     CCpuAffinity::SetAffinity(0, count, affinityBits);
}

 

현재 쓰레드를 0,5,7 Processer에 할당

// 0 : tid : 현재 쓰레드
// 3 : 사용할 cpu 개수(0,5,7)3개
// 0, 5, 7 코어사용
CCpuAffinity::SetAffinity(0,     3,     0, 5, 7 );

 

Java에서 쓰기

CpuAffinity class

public static int GetCpu();

현재 쓰레드가 어떤 프로세스에 할당되어있는지를 리턴한다.

public static int GetProcesserCount();

전체 프로세서수를 반환한다.

 

public static ProcesserInfo[] GetProcesserInfos();

전체 프로세서정보를 가지고 온다.

public static int GetProcesserCountByCoreType( int coreType );

프로세서 중 CPUA_BIGCORE, CPUA_MIDCORE,  CPUA_LITCORE 중 구분하여 가지고 온다

public static ProcesserInfo[] GetProcesserInfosByCoreType( int coreType );

프로세서 중 CPUA_BIGCORE, CPUA_MIDCORE,  CPUA_LITCORE 세가지를 구분하여 가지고 온다

public static ProcesserInfo[] GetProcessersOrderByPerformance();

Cpu를 성능순으로 가지고 온다. 성능이 좋은 녀석이 낮은 인덱스에 배치되고 저전력이 마지막 부분에 배치된다.

만약 저전력 순으로 가지고 오려면, 그냥 GetProcesserInfo()함수를 사용하면 된다.



public static int[] SchedGetAffinity(int tid );

Thread의 Affinity를 가지고 온다. tid를 0으로 하면 현재 쓰레드를 말한다.

현재의 쓰레드 값을 구하려면 LOLLIPOP이상에서는 android.system.Os.gettid()함수로 구할 수 있다.

public static int SchedSetAffinity(int tid, int[] cpu_set );

Thread의 Affinity를 지정한다. tid를 0으로 하면 현재 쓰레드를 말한다.

현재의 쓰레드 값을 구하려면 LOLLIPOP이상에서는 android.system.Os.gettid()함수로 구할 수 있다.

cpu_set은 int[] NewCpuSet() 함수로 통하여 만들어야 한다.

 

 

 

Java 예제

전체 프로세스 정보 가지고 오기

StringBuilder sb = new StringBuilder();
CpuAffinity.ProcesserInfo[] infos = CpuAffinity.GetProcesserInfos();
if( infos != null ) {

    sb.append( "processer count(length)=");
    sb.append( infos.length );
    sb.append( '\n' );

    for( int i = 0 ; i< infos.length ; i++ ) {
        CpuAffinity.ProcesserInfo info = infos[i];
        sb.append("processer #").append(info.cpu );
        sb.append(" freq.=");
        sb.append( String.format( "%.2fGHz", info.frequency / (float)1000000.0F ) );
        sb.append(" package =").append(info.packageId);
        sb.append(" type=" ).append( CpuAffinity.CoreTypeToString( info.coreType ) );
        sb.append( '\n' );
    }
}
Log.d( TAG, sb.toString() );

 

현재 쓰레드를 Little Processer(아무거나)에 할당

CpuAffinity.ProcesserInfo[] info = CpuAffinity.GetProcesserInfosByCoreType( CpuAffinity.CORE_TYPE_LITTLE );
if( info != null ) {
    int[] set = CpuAffinity.NewCpuSet();
    for (int i = 0; i < info.length; i++) {
        CpuAffinity.CPU_SET( info[i].cpu, set );
    }
    CpuAffinity.SchedSetAffinity(0, set );
}

 

현재 쓰레드를 0,5,7 Processer에 할당

// 0 : tid : 현재 쓰레드
// 0, 5, 7 코어사용
CpuAffinity.SetAffinity(0,     0, 5, 7 );

 

 

 Package이름을 바꿔 사용하고 싶은 사람은 추가적으로 작업을 해야 한다.

CCpuAffinity.cpp파일의 마지막 부분에 

Java_com_tistory_jamssoft_lib_CpuAffinity_InitNative(JNIEnv *env, jclass clazz, jstring classNamef, jint javaCoreCount )

Java_[....................여기...................]_InitNative

여기 부분을 바꾼 Package이름으로 변경해 줘야 할 것이다.(당연히 테스트는 안해 봤다.)

 

 

 

HyperThreading Note

HyperThreading을 지원하는 cpu의 경우 core수 * 2로 만들어지게 된다. 따라서 이 경우 제대로된 속도를 내려면 1개씩 뛰어넘어서 셋팅을 해줘야 한다.

 

 

 

 

주1 : [Intel Atom Z3735F]칩을 이용하는 기기였는데, 이 칩에는 ARM 코어도 포함되어 있어, x86코드가 없어도 arm-v7a방식만 쓰는 앱도 동작이 가능하다. x86코드를 넣어 동작시킬 때는 cpuinfo에 processer가 4번 나오지만, arm-v7a로만 사용할 경우 전혀 프로세서를 찾지 못하는 문제가 발생했다.

 

주2 : 함수의 구조상 넘기는 포인터에 위치한 크기를 넘겨 줘도 잘 동작해야 하는 데, 실제 구현하는 사람들이 함수 사용법을 잘 못 해석한 듯하다. 다른 값을 넘기면 동작하지 않는 경우가 전부였다. cpusetsize파라미터를 받는 이유가 퇴색된다.

 

 

Reference.

github.com/fcarucci/peuck/blob/master/src/Peuck.cpp

  구글에서 링크된 Cpu갯수를 구하는 코드

Replies
Reply Write