'리눅스 서버에 대해서'에 해당되는 글 90건

출처 : http://www.mimul.com/pebble/default/2012/05/30/1338342349153.html

개요

OpenMP는 여러개의 프로세스가 공유된 메모리를 참조하는 환경에서 다중 스레드 병렬 프로그래밍을 위한 표준 스펙이다. 여기에서 제공하는 API를 통해 사용자들은 어플리케이션의 성능 향상을 얻을 수 있게 된다.



OpenMP의 주요 동기요인은 성능, 확장성, 이식성, 표준성을 목표로 한다. 그리고 C, C++(#으로 시작), Fortran(90에서는 !$로 시작)을 지원하고 있다.
역사로는 1997년에 Fortran 1.0가 최초로 나왔고 그 후 1998년 C/C++ 1.0이 출시, 2005년에 OpenMP 2.5가, 2008년에 OpenMP 3가 나와서 현재에 이르고 있다.
OpenMP를 주로 사용하는 방법은 프로파일링을 통해 시간을 많이 차지하는 부분을 식별하고 해당 프로그램에서 사용되는 의존성, 데이터의 유효 범위를 예측한 다음 지시어의 설정을 통해 병렬 처리를 하여 성능 향상을 얻는 방식으로 진행된다.

구성



1) Directives(컴파일러 지시자)
쓰레드 사이의 작업 분담, 통신, 동기화를 담당을 한다. 컴파일러가 지시자를 참고하여 다중 쓰레드 코드를 생성하게 된다.
Work-Sharing 지시자는 상세히 설명할 필요가 있어서 아래에 새로운 단락으로 빼서 설명하고 여기서는 동기화, 데이터 유효 범위 지시자를 설명한다. OpenMP 프로그래밍은 컴파일러가 모두 알아서 처리하는게 아니라 동기화 및 의존성 제거 등의 작업은 사용자가 직접 소스코드에 기술해 주어야 한다는 점에서 병렬프로그래밍이 어렵게 인식되는 부분중에 하나다.

- 상호 배제 동기화

  • critical : 병렬영역 안에서 critical section을 지정하면 그 영역은 하나의 스레드에서 실행 된다.
  • atomic : 하나의 스칼라 변수를 경신하는 지정된 단일 문장(single statement)에 대해 여러 스레드가 접근하는 것을 방지한다. mini-critical section의 역할과 비슷하다.
  • flush : 하나의 메모리에 여러 개의 스레드가 경합을 벌이는 경우 그 정합성을 유지시켜 주는 지시어

- 이벤트 동기화

  • barrier : 모든 스레드들이 barrier에 도달할 때까지 대기하게 된다.
  • ordered : ordered section 내부의 루프 실행을 순차적으로 진행한다.
  • master : master section을 마스터 쓰레드에서만 실행시킨다.
  • taskwait : 모든 task가 taskwait에 도달할 때까지 대기한다.

- 데이터의 유효 범위 지시자

  • private : 지정된 변수를 스레드끼리 공유하는 것 방지한다. 주로 스칼라값, 여러 쓰레드들이 동시에 접근해서 쓰기를 할경우 사용.
  • shared : 지정된 변수를 모든 스레드가 공유하도록 한다. 디폴트.
  • default : private 또는 shared로 선언되지 않은 변수의 기본적인 유효범위 지정한다.
  • firstprivate : private 변수처럼 각 쓰레드에 개별적으로 변수 생성하고 각 스레드 마다 순차 영역에서 가져온 값으로 초기화한다.
  • lastprivate : private 변수처럼 각 스레드에 개별적으로 변수 생성하고 순차 실행에서 마지막 계산에 해당되는 값. 즉, 마지막 반복실행의 값을 마스터 스레드에게 넘겨준다.
  • reduction : reduction 변수는 shared이고 다중 스레드에서 병렬로 수행된 계산 결과를 환산해 최종 결과를 마스터 스레드로 내 놓는다.
  • schedule : 작업의 균등 분배를 위해 schedule 을 사용한다.

2) Runtime Library
병렬화에 직접 참여는 하지 않지만 실행 환경에서 병렬 매개 변수(참여 스레드의 개수, 번호 등)의 설정과 조회를 통해 병렬화 정보를 변경 조회할 수 있다.

  • omp_set_num_threads : 병렬 영역에서 사용할 스레드 개수 설정
  • omp_get_num_threads() : 병렬 영역 안에서 호출되어 생성된 스레드의 개수를 리턴
  • omp_get_thread_num() : 병렬 영역 안에서 생성된 스레드들의 ID를 리턴
  • omp_get_max_threads() : 병렬 영역에서 사용 가능한 최대 스레드 개수 리턴
  • omp_set_nested() : nested 병렬성 지원 여부 결정
  • omp_set_dynamic() : 순차 영역에서 호출되어 이어지는 병렬 영역 들의 스레드 개수 할당을 동적으로 수행
  • omp_get_nested() : 호출되는 시점의 nested 병렬성 설정 여부 확인
  • omp_get_dynamic() : 스레드 할당 방식이 동적인지 확인
  • omp_in_parallel() : 호출된 지점이 순차 영역인지 병렬 영역인지 확인
  • omp_get_num_procs() : 프로그램에서 사용 가능한 물리적 프로세서의 총 개수 확인

3) Environment Variables
Runtime Library와 중복되는 부분이 많지만 우선순위는 동일한 지시자를 반복 사용했을 경우 Runtime Library가 우선된다. 주요한 일은 실행 시스템의 병렬 매개 변수(스레드 개수 등)를 설정해 실행시에 제어를 한다.

  • OMP_NUM_THREADS : 병렬영역에서 사용 가능한 최대 스레드 개수 지정
  • OMP_SCHEDULE : schedule type이 runtime으로 지정된 루프들에게 scheduling 방식 지정
  • OMP_DYNAMIC : 스레드 개수의 동적할당 여부 결정
  • OMP_NESTED : nested 병렬성 지원 여부 결정

 

프로그래밍 모델

1) Fork-Join 모델
쓰레드가 병렬 구문를 만나면 쓰레드는 그 자신을 포함한 추가적인 쓰레드(0개 이상의)로 이루어진 쓰레드 팀을 만든다(Fork). 병렬 구문를 만난 쓰레드는 팀의 마스터 쓰레드를 호출 하게 되고(Join) 다른 쓰레드들은 팀의 slave 쓰레드라고 불리어진다.



Work-Sharing 모델

같은 작업을 쓰레드별로 실행하는 것이 아니라 작업을 분할해서 쓰레드별로 나누어서 실행하는 것을 말한다.

1) for
바로 뒤에 오는 for 루프의 반복 실행을 쓰레드에 분배한다. 그리고 루프 끝에 암시적 장벽(동기화)이 존재한다. 이를 피할려면 nowait 사용하면 된다.
데이터 병렬화시에 활용된다.



#include <stdio.h>
#include <omp.h>

int main(int argc, char *argv[]) {
  int i;
#pragma omp parallel for
  for(i=0; i<10; i++) {
    printf("[%d-%d] Hello World\n", omp_get_thread_num(), i);
  }
   return 0;
}
> gcc -fopenmp -o for_hello for_hello.c
> ./for_hello
[0-0] Hello World
[0-1] Hello World
[0-2] Hello World
[2-6] Hello World
[2-7] Hello World
[2-8] Hello World
[3-9] Hello World
[1-3] Hello World
[1-4] Hello World
[1-5] Hello World


2) sections
독립된 여러 개 작업을 각 스레드에 분산 실행한다. 그리고 sections 구문 끝에 암시적 장벽(동기화)이 존재하고 이를 피할려면 nowait 사용하면 된다.
기능적 분할에 이용된다.



#include <stdio.h>
#include <omp.h>

int main(int argc, char *argv[]) {
#pragma omp parallel
#pragma omp sections
  {
#pragma omp section
    {
      printf("[%d] Hello \n", omp_get_thread_num());
    }
#pragma omp section
    {
      printf("[%d] World \n", omp_get_thread_num());
    }
  }
  return 0;
}
> gcc -fopenmp -o section_hello section_hello.c
> ./section_hello
[0] Hello 
[3] World 


3) single
병렬 영역 내부에서 하나의 스레드만을 이용해 작업 수행한다. single 지시어자 가장 먼저 접근한 스레드에 작업 할당된다. single 구문 끝에 암시적 장벽(동기화)이 존재한다.마찬가지로 이를 피하기 위해서는 nowait 사용하면 된다.
병렬 영역 내에서의 입/출력 수행에 주로 사용된다.



#include <stdio.h>
#include <omp.h>

int main(int argc, char *argv[]) {
#pragma omp parallel
 {
#pragma omp single
    {
      printf("[%d] Hello \n", omp_get_thread_num());
      
    }
    printf("[%d] World \n", omp_get_thread_num());
  }
  return 0;
}

> gcc -fopenmp -o single_hello single_hello.c
> ./single_hello
[3] Hello 
[3] World 
[2] World 
[1] World 
[0] World 


4) Task
OpenMP 버전 3.0 이상부터 추가된 지시자며, 수행할 작업을 한번의 호출로 진행되는 작업이나 한번의 구문으로 실행 가능한 작업 단위 나눈다. 태스트 실행은 작업 스케쥴링이 적용된다.

#include <stdio.h>
#include <omp.h>

int main(int argc, char *argv[]) {
#pragma omp parallel
  {
#pragma omp single
    {
#pragma omp task
      {
        printf("Hello ");
      }
#pragma omp task
      {
        printf("World ");
      }
#pragma omp taskwait
      printf("\nThanks! ");
    }
  }
  printf("\n");
  return 0;
}

> gcc -fopenmp -o task_hello task_hello.c
> ./task_hello 
Hello World 
Thanks! 


추후에는 mysql++ 라이브러리를 활용해서 MySQL Benchmark 클라이언트 성능 개선 예제를 올려볼까 합니다.

[참조 사이트]

Tags : 


블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

Tag OpenMP

댓글을 달아 주세요

- backtrace 출력 하는 방법.

http://jh4hj.tistory.com/355  


-make 의존성

http://jh4hj.tistory.com/entry/%EC%88%A8%EA%B2%A8%EC%A7%84-1%EC%9D%B8%EC%B9%98%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1%EC%9D%84-%EC%B0%BE%EC%95%84%EC%84%9C-make


- 메모리 디버깅

http://jh4hj.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%94%94%EB%B2%84%EA%B9%85%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%B9%9C%EA%B5%AC

'리눅스 서버에 대해서' 카테고리의 다른 글

병렬 프로그래밍 페러다임  (0) 2014.06.18
OpenMP를 이용한 병렬 프로그래밍  (0) 2014.06.18
리눅스 프로그래밍 팁  (0) 2014.06.10
GDBM을 이용한 프로그래밍..  (0) 2014.05.16
atomic swap ptr 처리  (0) 2014.01.17
리눅스 swap 메모리  (0) 2013.10.31
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요


출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/article/gdbm


프로그램 분석중 gdbm_exists 라는 함수를 발견 했습니다. 

뭔가 했더니 이런게 있군요... ㄷㄷㄷ



요즘 사이트에 있어서 mysql, oracle, postgresql 등 RDBMS 가 쓰이지 않는 곳이 거의 없을정도로 많은 인기를 끌고 있다. 
그 반면 NIS, BIND, sendmal, LDAP 등 많은 프로그램들이 각각의 자료를 관리하기 위해서 굳이 RDBMS 를 쓰지 않고 ndbm, dbm, gdbm 을 사용하고 있다. 왜 막강한 RDBMS를 쓰지 않고 이러한 간단한 dbm 을 쓰는걸까 ? 
그 이유는 간단한 일을 하기 위해서 RDBMS 는 너무 크고 너무 거추장 스럽다는 것이다. 소규모(1000 에서 10000 건) 정도의 전화번호부를 관리한다거나, sendmail 에서 수백건 미만의 hosts(relay 허용등) db를 관리하는데에는 실지로 RDBMS의 기능의 10%도 필요하지 않다. 이러한 간단한 DB를 유지하려고, 서버에 RDBMS 를 설치하고, 운용하고, 프로그래밍을 하고, 복잡한 SQL을 사용하는건 너무 소모적인 일이다. 그래서 이러한 간단한 소규모의 데이타를 관리하기 위해서 dbm 이 존재한다.

dbm 은 관계형 데이타 구조 모델을 가지지 않고, HASH 데이타 구조 모델을 가진다. HASH 는 Key(키), Value(값) 의 한쌍으로 이루어지는 데이타의 집합으로써 키를 이용해서 데이타를 저장하고, 검색하고, 삭제하는 작업을 한다.
어찌 보면 C 에서의 pointer 개념과 비슷하다고 할수 있는데, Value 를 Key 가 가르킨다고 보면 무난할듯 하다.
RDBMS 와는 달리 하나하나의 데이타가 다른 데이타와는 별개로 존재하므로 RDBMS 처럼 데이타간의 관계에 의한 질의 언어가 필요없이 간단하게 Key만을 호출하면, 그 키에 연결된 값을 가져올수 있게된다. 기능의 한계가 명확하므로, 배워서 구현하기가 매우 쉬우며, 작고 또한 빠르다라는 장점을 가진다. 

GDBM 은 GNU database mansger 로써 전통적으로 Unix 쪽에서 쓰이던 dbm 의 확장형이다.
아래의 예제는 간단한 주소록이다. 비록 간단하지만 세련된 코드는 아니지만 데이타의 입력, 검색, 삭제 등 dbm 으로써 가져야할 기본적인 기능을 살펴보는데 어려움은 없을것으로 생각된다. 

예제 : address.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <gdbm/gdbm.h>

const int FALSE = 0;
const int TRUE  = 1;


int main(int argc, char **argv)
{
    datum key_data;
    datum value_data;
    datum return_data;

    char cmd_char;
    int done = FALSE;

    GDBM_FILE dbf;


    int block_size = 0;

    int temp;

    char key_line[80];
    char value_line[255];

    key_data.dptr = NULL;
    value_data.dptr = value_line;

    if (access("/tmp/my_dic", F_OK) != 0)
    {
        printf( "사전 DB 파일이 존재 하지 않습니다"
                "영어 사전  DB 파일을 새로 만들겠습니까 (y/n)? ");
        cmd_char = getchar();

        while(1)
        {
            if (cmd_char != '')
            {
                char temp;
                do
                    temp=getchar();
                while (temp != '' && temp != EOF);
            }

            if (cmd_char == EOF) cmd_char = 'n';
            switch(cmd_char)
            {
                case 'y':
                    dbf = gdbm_open ("/tmp/my_dic", block_size, GDBM_WRCREAT|GDBM_FAST, 00664, NULL);
                    if (dbf == NULL)
                    {
                        perror("db file open error : ");
                        exit(0); 
                    }
                    break;
                case 'n':
                    return 1;
                    break;
            }

            if (cmd_char == 'y')
                break;
        }

    }
    else
    {
        dbf = gdbm_open ("/tmp/my_dic", block_size, GDBM_READER|GDBM_WRITER, 00664, NULL);
        if (dbf == NULL)
        {
            perror("db file open error : ");
            exit(0);
        }
    }


    printf("주소 관리 프로그램입니다. "
           "사용법이 궁금하시면 ? 를 입력하세요");
    // 사용자의 키보드 입력문자열을 받아서 필요한 
    // 행동을 취한다.
    while(!done)
    {
        printf("com -> ");
        cmd_char = getchar();

        if (cmd_char != '')
        {
            char temp;
            do
                temp = getchar();
            while (temp != '' && temp != EOF);
        }
        if (cmd_char == EOF) cmd_char = 'q';

        switch (cmd_char)
        {
            case 'q':
                done = TRUE;
                break;
            case '?':
                printf("i -- insert data"
                       "f -- fetch data"
                       "c -- data num"
                       "d -- data delete"
                       "q -- quit");
                break;

            case '':
                printf("");
                break;
            case 'i':
                if (key_data.dptr != NULL) free(key_data.dptr);
                printf("Key -> ");
                fgets (key_line, 80, stdin);
                key_line[strlen(key_line) - 1] = '0';

                key_data.dptr = key_line;
                key_data.dsize = strlen(key_line) + 1;

                printf("Value -> ");
                fgets(value_line, 255, stdin); 

                value_data.dsize = strlen(value_line) + 1;
                if (gdbm_store (dbf, key_data, value_data, GDBM_REPLACE) != 0)
                    printf("Item no inserted ");
                printf("");
                key_data.dptr = NULL;
                break;

            case 'f':
                if (key_data.dptr != NULL) free(key_data.dptr);
                printf("Key -> ");
                fgets (key_line, 80, stdin);
                key_line[strlen(key_line) -1] = 0;
                key_data.dptr = key_line;
                key_data.dsize = strlen(key_line) + 1;

                return_data = gdbm_fetch(dbf, key_data);
                if (return_data.dptr != NULL)
                {
                    printf("data -> %s", return_data.dptr);
                    free(return_data.dptr);
                }

                else
                    printf("No Such item found. ");
                key_data.dptr = NULL;
                break;
            case 'c':
                temp = 0;

                if (key_data.dptr != NULL) free (key_data.dptr);
                return_data =  gdbm_firstkey(dbf);
                while(return_data.dptr != NULL)
                {
                    temp ++;
                    key_data = return_data;
                    return_data = gdbm_nextkey(dbf, key_data);
                    free(key_data.dptr);
                }
                printf("%d 개의 자료가 있습니다", temp);
                key_data.dptr = NULL;
                break;

            case 'a':
                key_data = gdbm_firstkey(dbf);
                if (key_data.dptr == NULL)
                {
                    printf("No one tiem found");
                    break;
                }
                else
                {
                    printf("%s => ", key_data.dptr);
                    return_data = gdbm_fetch(dbf, key_data);
                    printf("%s", return_data.dptr);
                    free(return_data.dptr);
                }
                while(1)
                {
                    return_data = gdbm_nextkey(dbf, key_data);
                    if (return_data.dptr != NULL)
                    {
                        free(key_data.dptr);
                        key_data = return_data;
                        printf("%s => ", key_data.dptr);
                        return_data = gdbm_fetch(dbf, key_data);
                        printf("%s", return_data.dptr);
                        free(return_data.dptr);
                    }
                    else
                    {
                        printf("No such item found.");
                        break;
                    }
                }
                key_data.dptr = NULL;
                break;

            case 'd':
                if (key_data.dptr != NULL) free (key_data.dptr);

                printf("Key -> ");
                fgets (key_line, 80, stdin);
                key_line[strlen(key_line) -1] = 0;
                key_data.dptr = key_line;
                key_data.dsize = strlen(key_line) + 1;
                if(gdbm_delete (dbf, key_data) != 0)
                    printf("Item not found or deleted");
                key_data.dptr = NULL;
                break;
        }
    }

    printf("bye bye");
    return 0;
}

datum 의 dptr 멤버는 malloc 를 이용해서 메모리를 할당하고, 자동으로 해제시켜주지 않는다. 그러므로 반드시 필요없다고 생각되는 곳에서 free 를 해줘야 메모리 누수 및 오류를 방지할수 있다.
위 코드는 매우 명확해 보이므로 별도로 설명을하진 않을 생각이다. 아리송하더라도 한번 정도 컴파일 해서 사용해보면서 코드를 보면 쉽게 이해가 될것이다.
gdbm 에 대한 자세한 내용은 man 페이지를 참고하라
컴파일 방법은 아래와 같다.
[yundream@localhost test]# gcc -o address address.c -lgdbm



블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

Tag GDBM

댓글을 달아 주세요

OS단에서 쓰레드 안전을 보장해 주면서 포인터를 바꾸는 명령이라고 합니다.

대충, *ptrA -> *ptrB 로 이동하려면

*ptrOldB =  __sync_lock_test_and_set (&ptrB, ptrA)


ptrB의 원래 포인트가 결과로 리턴되고,
ptrA의 주소가 ptrB로 됩니다.

즉, 
*ptrA = __stnc_lock_test_and_set(&ptrB, ptrA) 하면
ptrA->ptrB로 이동하고 원래 ptrB는 ptrA로 가니 swap이 된다고 하네요.

솔라리스 gcc 에는 atomic_swap_ptr 이 있군요.

논란이 있는것 같지만, 스핀락 구현 같은거 할때 쓸만할 것 같네요
http://stackoverflow.com/questions/9003303/atomic-swap-function-using-gcc-atomic-builtins

__sync_lock_test_and_set

Purpose

This function atomically assigns the value of __v to the variable that __p points to.

An acquire memory barrier is created when this function is invoked.

Prototype

T __sync_lock_test_and_set (T__pT __v, ...);

where T is one of the data types listed in Supported data types.

Parameters

__p
The pointer of the variable that is to be set.
__v
The value to set to the variable that __p points to.

Return value

The function returns the initial value of the variable that __p points to.


Provide feedback ]



출처 : http://pic.dhe.ibm.com/infocenter/comphelp/v121v141/index.jsp?topic=%2Fcom.ibm.xlcpp121.aix.doc%2Fcompiler_ref%2Fbif_gcc_atomic_lock_test_set.html





블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

Tag C, Lock

댓글을 달아 주세요

 

시작하며..

리눅스의 swap 메모리에 대해서 이해한다.

 

본론

만약 어플리케이션의 RAM 용량이 차면, swap 메모리가 자동으로 늘어나도록 되어 있다. 하드디스크의 디스크를 swap 메모리로 만들기 때문에 속도가 느려진다. 또한 RAM의 용량보다 2배로 swap 메모리를 잡도록 되어 있다. (참조 : http://vr.org/docs/centos/4/html/rhel-sag-en-4/ch-swapspace.html) 시스템 엔지니어는 이런 이슈로 리눅스 서버를 셋팅할 때 swap 영역을 얼마나 잡을지 판단해야 한다. 때로는 개발자가 이 부분을 고쳐 성능을 향상 시킬 수 있다. (참고 : https://wiki.archlinux.org/index.php/Swap)


개인적으로는 메모리가 갑자기 부족한 경우를 제외하고는 swap 자체를 안쓰는 것을 선호한다. 성능상 swap 메모리를 쓰는 것보다 swap in , swap out이 성능을 무너뜨리는 것을 많이 봐왔다. 또한 너무 크게 사용하는 것도 경계한다. (난 보수적인 사람이 되었나 보다… 아직 적절히 활용한 대용량 처리 사례를 못해봐서 그런지도 모르겠다. ) 차라리 RAM 하나 더 꽂는게 더 나은 것 같다. 

하지만 DB나, 오픈 소스 대용량 처리 어플리케이션의 경우에서는 swap을 많이 활용하려고 하고 있다는 사실이다. 어떻게 하든 속도를 내기 위해서 메모리에서 처리하려다 보니 swap을 쓸 수 밖에 없는 구조로 가고, 너무 많이 써서 문제가 되니 다시 mlock으로 막는 구조로 가는 부분으로 처리하는 형태로 가고 있다.

 

1. swap 및 ram 메모리 상태 보기

free 명령어를 이용해서 간단히 살펴볼 수 있다.

# free 
             total       used       free     shared    buffers     cached 
Mem:       4150252    3991868     158384          0      78504    2265916 
-/+ buffers/cache:    1647448    2502804 
Swap:      2096472        208    2096264

 

2. Swap 메모리 설정하는 방법

리눅스 커널 2.6부터 swap 메모리를 설정할 수 있다. swap 영역은 dd 명령어와 mkswap 명령어를 이용해서 swap을 만들 수 있다. (http://www.artwork.com/gdsii/qckvu/faq/linux_swap.htm) 이렇게 만들어진 영역은 swap 메모리로 쓰고 있다는 뜻이다. 아래의 예는 swap을 2G 정도만 쓰고 있다는 것을 의미한다.

# cat /proc/swaps 
Filename                                Type            Size    Used    Priority 
/dev/cciss/c0d0p2                 partition       2096472    208        -1

 

3. Swap 메모리 사용 빈도 설정 방법

swap 메모리를 사용 빈도를 확인하는 방법은 vm.swappiness 정보를 보는 것이다. centos를 비롯한 일부 서버는 디폴트로 60으로 잡혀 있다.

]# sysctl vm.swappiness   (또는 cat /proc/sys/vm/swappiness 으로 확인 가능) 
vm.swappiness = 60

vm.swapiness 정보는 커널 파라미터이며 swap 영역을 얼마나 더 보겠냐 (또는 회피하겠냐)는 의미를 가지고 있다. (kernel's preference (or avoidance) of swap space) 여기에 0의 값을 넣으면 swap을 최대한 쓰지 않겠다는 의미이다. 만약 100은 하드디스크의 swap영역을 최대한 활용하겠다는 의미이다.

제일 이슈는 swap 메모리가 많이 잡혀 있는 것보다 얼마나 사용하고 있는지를 측정하는 것이 좋다. free나 cat /proc/swaps 명령어를 이용해서 해결할 수 있다.

만약 swap 메모리를 쓰지 않으려면, vm.swappiness를 0으로 셋팅하면 된다.

/etc/sysctl.conf 에 ‘vm.swappiness=0 ‘ 설정을 추가하고, ‘echo 0 > /proc/sys/vm/swappiness ‘ 명령어를 이용해서 적용한다.


* Reference

http://forums.gentoo.org/viewtopic.php?t=175419

5. Swappiness (2.6 kernels) 
Since 2.6, there has been a way to tune how much Linux favors swapping out to disk compared to shrinking the caches when memory gets full. 

ghoti adds: 
When an application needs memory and all the RAM is fully occupied, the kernel has two ways to free some memory at its disposal: it can either reduce the disk cache in the RAM by eliminating the oldest data or it may swap some less used portions (pages) of programs out to the swap partition on disk. 
It is not easy to predict which method would be more efficient. 
The kernel makes a choice by roughly guessing the effectiveness of the two methods at a given instant, based on the recent history of activity. 

Before the 2.6 kernels, the user had no possible means to influence the calculations and there could happen situations where the kernel often made the wrong choice, leading to thrashing and slow performance. The addition of swappiness in 2.6 changes this. 


 

또한 vfs_cache_pressure 라는 값을 0으로 잡아, 커널의 cache를 빨리 날려줄 수 있도록 한다. linux에서는 어플에서 처리한 남는 메모리를 바로 free되지 않고 buffer cache에 남게 한다. 이를 이용하면 free memory를 더 확보할 수 있다.


 


4. swap in, swap out 확인하는 방법

sar 명령어를 이용하면 메모리 상태를 확인할 수 있다. 

]# sar -r -s 11:41:00 
Linux 2.6.9-78.ELsmp        

11시 41분 kbmemfree kbmemused  %memused kbbuffers  kbcached kbswpfree kbswpused  %swpused  kbswpcad 
11시 42분   5932304  10700956     64.33    358820   1087040   2096280       192      0.01         0 
Average:    5932304  10700956     64.33    358820   1087040   2096280       192      0.01         0

 

sar 명령어의 –B 파라미터는 swapping 통계를 낸다.

]# sar -B 2 5 
Linux 2.6.9-78.ELsmp

11시 41분 28초  pgpgin/s pgpgout/s   fault/s  majflt/s 
11시 41분 30초      2.04     75.51     21.43      0.00 
11시 41분 32초      0.00      0.00    359.49      0.00 
11시 41분 34초      0.00    258.46     19.49      0.00

 

5. 이슈

jvm 영역에서 사용된 메모리는 언제나 swap 영역으로 이동될 수 있다.  
jvm 에서 nio를 사용하는 경우 (예, DirectBuffer) 에는 리눅스 운영체제가 언제든지 swap 영역으로 이동할 수 있다. 이를 방지하기 위해서는 mlock/mlockall 시스템 콜을 이용해서 특정 virtual address를 ram 에 묶어 둘 수 있게 한다. 

mlock을 쓰는 방식이 cassandra 0.6.5 에서 추가되었다.

 

마치며..

리눅스의 swap 메모리를 이해할 수 있었다. jvm을 사용하면서 나타날 수 있는 swap 메모리 이슈에 대해서 쉽게 처리할 수 있도록 공부하자.

cassandra의 swap 이슈 에 대한 분석을 좀 더 이해하려고 한다.


 

 

블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

댓글을 달아 주세요