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





 

시작하며..

리눅스의 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 이슈 에 대한 분석을 좀 더 이해하려고 한다.


 

 

 

출처 : http://hopangbear.tistory.com/183

 

 

좀더 자세히는

오늘은 rsync 이야기...

 

제길.. 개발 서버를 한 대 새로 받았다... -_-;;

다음주까지 환경 세팅해야 하는데 무지 무지 무지 귀찮아서 죽을 것 같았는데...

가만 보니 예전에 테스트 서버로 받았던 서버와 똑같은 OS가 깔려 있었다...

옳커니! rsync로 땡겨가면 되겠구나 하고 rsync 명령을 쳐봤다...

옵션 넣으라고 뭐라 뭐라 줄줄이 내뱉는다..

이제 접근 권한 설정을 위해 /etc/rsyncd.conf 파일을 찾았다...

그런데 이게 웬일... 파일이 없다.... -_-;;

여기 저기 검색해봤더니... 없으면 그냥 만들어주면 된다고 한다... -_-;;

대충 아래와 같이 파일을 생성해주고~ (아참! root 권한으로~)

 

[R]

comment = root

path = /

uid = inho

gid = inho

read only = false

hosts allow = 127.0.0.1

hosts deny = *

 

대충 localhost에서만 접근 가능하고 잘 되는지 테스트를 해봤다.

 

rsync -localhost::R

 

이랬더만..

 

rsync: failed to connect to localhost: Connection refused (111)
rsync error: error in socket IO (code 10) at clientserver.c(107) [receiver=2.6.8]

 

위와 같은 오류가 발생했다..

 

여기 저기 검색해봤더니 rsync가 아직 안 떠 있다는 이야기라고 한다...

netstat -nlp 명령으로 873 포트가 없으면 rsync가 아직 동작하지 않는 상태이다...

그렇다면 rsync를 띄워보자..

대부분 /etc/init.d/xinetd restart 하면 실행된다고 하는데 이 서버는 어떻게 깔아 놓은건지 죽어도 실행이 안됐다... -_-;;

겁나 삽질하다가 rsync 도움말에 --daemon 이라는 부분이 눈에 들어와서 찾아봤더니...

 

rsync --daemon

 

요렇게 해주면 그냥 실행된다... -_-;; 에이씨...

아무튼 위 명령으로 일단 rsync를 대몬으로 실행해주고 netstat -nlp 로 확인해보면 아래와 같이 873 포트가 사용중인 것으로 나온다...

 

tcp        0      0 0.0.0.0:873                 0.0.0.0:*                   LISTEN      -

이제 잘 동작한다... -_-;;

이거 안되면 진짜 울 뻔 했는데... 다행이다...

 

 

 

대용량 소켓 서버 프로그래밍이라는 주제로 글 하나 올린 것이 화근이 되어서 해당 검색어를 통해서 제 블로그를 방문하시는 분들이 간혹 계시는 지라, 책임감을 느끼고 포스트를 이어 갑니다.  (이럴 틈이 없이 바쁜데 ㅠ.ㅠ)


오늘은 시리즈를 염두하면서 첫 번째 주제로 "Atomic Operation"에 대해서 이야기하고자 합니다.  "Atomic Operation"을 먼저 이야기하려는 이유는, 대용량 지원을 위해서는 멀티 스레드 프로그래밍이 필수이기 때문입니다.  그리고, 멀티 스레드 프로그래밍을 효율적으로 작성하기 위해서 가장 큰 적은 역시 "Context Switching 비용"입니다. 아!!  어려워 보이는 용어가 시작부터 난무하고 있습니다 ㅠ.ㅠ


우선, "Atomic Operation"을 쉽고 무식하게 설명하자면, "한 번에 실행 할 수 있는 명령어"라고 할 수 있습니다.


[소스 1]

1.var
2.a, b : integer;
3.begin
4.a := 1;
5.b := a + 1;

위의 소스 코드를 보면 4: 라인의 경우 한 줄의 코드이기 때문에 한 번에 실행 될 것이라고 착각할 수도 있습니다.  하지만, 이것이 실제 CPU가 처리 할 때에는 [그림 1]에서처럼 여러 명령어를 순차적으로 실행해야 같은 결과를 얻을 수가 있습니다.



[그림 1]


결국 멀티 스레드 상황에서 해당 코드를 실행하기 위해서는 임계영역(Critical Section 등)을 이용해서 락을 걸어두고 실행을 마친 후 락을 푸는 작업으로 한 번에 하나의 스레드만이 코드를 실행 할 수 있도록 해야 합니다.  이때, 스레드가 락에 걸리고 풀리는 과정 중에 수반되는 작업량이 무시 못할 정도로 크며, 이로 인해서 성능 저하가 발생합니다.  이것을 "Context Switching 비용"이라고 합니다.


"b := a + 1;"과 같은 코드가 간단하고 대수롭지 않게 보일지 모르지만, 멀티 스레드 상황에서는 아주 중요한 코드가 됩니다.  이때문에 각 플랫폼 마다 해당 코드를 한 번에 실행 할 수 있는 장치를 마련하고 있습니다.  윈도우즈에서는 Interlokced 함수들이 그런 기능을 제공합니다.


[소스 2]

01.var
02.Old : TObject;
03.iIndex, iMod : integer;
04.begin
05.FCurrent := AObj;
06. 
07.iIndex := InterlockedIncrement(FIndex);
08.if iIndex >= FRingSize then begin
09.iMod := iIndex mod FRingSize;
10.InterlockedCompareExchange(FIndex, iMod, iIndex);
11.end else begin
12.iMod := iIndex;
13.end;

[소스 2]는 대용량 소켓 서버를 작성하면서 패킷 풀을 만들기 위해서 작성한 코드의 일부분입니다.  링 버퍼에 새로운 객체를 추가하기 위해서 현재 위치(FIndex)를 한 칸 앞으로 전진하고 있습니다.  얼핏 보아도 여러 줄의 코드가 실행되고 있기 때문에 임계영역을 이용하여 락을 걸어야 할 것처럼 보입니다.  하지만, Interlocked 함수들을 이용해서 락을 사용하지 않고 현재 위치를 변경하고 있습니다.


7: InterlockedIncrement를 이용해서 FIndex에 1을 더하고 결과를 iIndex에도 저장합니다.  이 동작은 한 번에 이루어지기 때문에 멀티 스레드 상황에서 안전합니다.  이후 다른 스레드가 FIndex의 값을 변경하더라도 우리는 iIndex를 참조하고 있기 때문에 우리가 얻은 값이 중간에 변경 될 염려가 없습니다.


8-9: iIndex가 링 버퍼의 크기를 넘어서게 되면, 링 버퍼 크기로 나눠서 나머지를 취해야 합니다.  (크기가 정해진 큐를 작성하는 알고리즘 참고)


10: FIndex를 계속 증가시켜면 데이터 타입의 한계로 인해서 에러가 발생합니다.  따라서, iMod 값으로 바꿔야 합니다.  하지만, 지금 FIndex를 바꾼다면 다른 스레드가 이미 바꾼 값을 다시 되돌려 놓는 등의 문제가 발생합니다.  따라서, InterlockedCompareExchange를 이용해서 FIndex의 값이 iIndex와 같다면 아직 다른 스레드가 FIndex를 변경하지 않았으므로 iMod를 대입합니다.  이 과정 역시 한 번에 실행됩니다.


이후에는 iMod를 이용해서 "FRing[iMod]"과 같이 데이터를 접근 할 수가 있습니다.  또한 이 모든 과정이 락을 걸지 않고도 멀티 스레드에서 안전하게 동작함을 확신 할 수가 있습니다.


[소스 2]에서 생각 할 수 있는 의문점은 "링 버퍼가 한 바퀴 돌았을 때, 이전 메모리 해제를 어떻게 하는 가?"입니다.  우선 실제 코드는 [소스 3]과 같으며, 전체 코드는 http://bit.ly/XWBvBJ 를 참고하시기 바랍니다.


[소스 3]

1.Old := InterlockedExchangePointer(Pointer(FRing[iMod]), AObj);
2.if Old <> nil then Old.Free;

1: InterlockedExchangePointer를 이용해서 한 번에 "FRing[iMod] := AObj;"를 대입하고, 원래의 값은 Old에 대입하게 됩니다.


2: 만약, Old가 nil이 아니면 객체를 파괴합니다.


그런데 이 Old를 다른 스레드가 사용하고 있으면 어떻게 될까요?  큰 일 납니다 ㅠ.ㅠ  따라서, 저는 충분히 큰 크기의 링 버퍼를 구현하여 모든 객체의 라이프 사이클이 스레드의 참조 기간보다 길다는 것을 보장합니다.  그렇게 할 수 없는 곳에서는 사용 할 수 없지만, 일반적인 패킷 교환 등에서는 그런 일이 벌어지지 않습니다.


또한, 위의 코드는 혹시라도 그러한 일이 벌어지면 "Access Violation" 에러가 발생하게 되지만, 실제 코드에서는 좀 더 보강한 방법을 통해서 컨넥션을 종료하는 것으로 구현되어 있습니다.  그토록 오랫 동안 패킷을 처리 못했다면 분명 문제가 있는 접속이기 때문입니다.  해당 코드에 대해서는 다음 시리즈를 기약해 보도록 하겠습니다 ㅡ.ㅡ;;  (실제 패킷 풀에서는 메모리를 해제하지 않고 재사용하며, 잘못 된 재사용을 막을 수 있도록 코드가 추가되어 있습니다)



Lock-Free에 대한 참고 자료


 


 

통상 Critical Section을 처리하는데 Lock을 많이 사용한다.

그런데 성능이 굉장히 중요한 부분에 있어서 Lock을 사용하기에는 성능 저하가 우려 되는 경우가 있다.


이럴 때 활용할 수 있는 것이 Atomic 연산자를 이용한 Critical Section처리이다.

Atomic 연산이란 기본적으로 연산 수행 중 thread 전환이 되지 않는 것을 보장하는 연산을 일컫는다.


흔히 하는 실수로 성능 때문에 lock을 사용하지 않고 flag 변수를 하나 두고,

flag 변수를 이용하여 critical section을  처리할려 하는데,  이 경우 flag변수에 대한 읽고 쓰는 연산이 

atomic 하지 않으면 critical section이 제대로 보호 받지 못한다.


예를 들어 아래와 같이 코딩을 하여 critical section 처리를 한다면...


unsigned int semaphore  = 0;

while (semaphore) {

    // flag 풀릴 때 까지 yield하면서 기다림

}

semaphore  = 1;

// critical section 처리 함

semaphore  = 0;


위의 코드를 수행하는 여러 thread에서 semaphore flag변수가 0임을 동시에 확인 하면

여러 thread가 동시에 critical section에 진입하게 되어 문제를 일으키게 된다.


이를 해결하기 위해서는 semaphore변수가 0이면 바로 1로 바꿔 버리는 연산이 atomic하게 thread 전환없이

처리되어야 한다.


unsigned int swap(unsigned _new)

{

   unsigned int old = semaphore;

   semaphore = _new;

   return old;

}

while (swap(1)) {

       // flag 풀릴 때 까지 yield하면서 기다림

}

// critical 처리함

swap(0);


위의 코드에서 swap() 함수가 atomic 하게 처리되면 critical section 보호가 된다.


Windows의 경우는 <boost/detail/interlocked.hpp> 에서 이러한 atomic operation들이 지원되어 이를 활용하면 된다.

안타깝게 windows가 아닐 경우는 "Interlocked intrinsics not available" 이라는 에러 메세지가 출력된다.


Linux의 경우는 x86 계열 CPU에서는 gcc에서 제공하는 __exchanged_and_add를 사용할 수 있다.

이 __exchange_and_add의 경우 gcc 4.0.2 이전에는 

arm-none-linux-gnueabi/include/c++/4.0.2/bits/atomicity.h

이후에는 아래와 같이 있다.

arm-none-linux-gnueabi/include/c++/4.5.1/ext/atomicity.h


최근에 Android 폰등에서 ARM CPU를 많이 사용하는데 위의 __exchange_and_add() 이 컴파일은 되기는 하나,

정작 테스트해보면 atomic하지 않아 critical section보호가 제대로 되지 않거나,

아니면 일반적인 pthread mutex로 구현되어 성능이 좋지 않은 것을 확인하였다.


ARMv6 이상 CPU에서는 kernel쪽을 뒤져 보면 아래와 같은 atomic operation inline assembly함수를 찾을 수 있다.

inline unsigned long testAndSwap(volatile int *ptr, unsigned long old, unsigned long  _new)

{

    unsigned long oldval, res;

    do {

        __asm__ __volatile__("@ testAndSwap\n"

        "ldrex  %1, [%2]\n"

        "mov    %0, #0\n"

        "teq    %1, %3\n"

        "strexeq %0, %4, [%2]\n"

            : "=&r" (res), "=&r" (oldval)

            : "r" (ptr), "Ir" (old), "r" (_new)

            : "cc");

    } while (res);

    return oldval;

}


ARMv5te 이하의 CPU의 경우에는 multi-core를 지원하지 않는 CPU로 kernel쪽에서는 

단순히 interrupt를 disable시킨 상태에서 flag 변수 test and swap으로 구현되어 있는데,  

이러한 방식은 user space에서는 활용할 수 없다.


그런데 swap assembly command가 지원되어서 이를 이용하여 swap 함수를 만들어서 활용할 수 있다.

inline unsigned long swap(volatile int *ptr, unsigned long _new)

{

      unsigned long oldval;

        __asm__ __volatile__("@ swap\n"

        "swp %0, %1, [%2]\n"

            : "=&r" (oldval)

            : "r" (_new), "r" (ptr) );

        return oldval;

}


ARM Cortext-A8 CPU에서 간단히 구현해서 pthread_mutex_trylock()과 성능 비교를 해보면 

위의 atomic 연사자를 이용한 critical section 보호가 4배이상 빠른 것을 확인할 수 있었다.


그러나 성능 최적화에서 무엇보다도 중요한 것은 Profiling을 먼저 해보고 

어떤 SW 부분이 bottleneck인지 확인하고 성능 최적화에 들어가야 한다.


성능에 크게 문제가 되지 않을 경우에는 표준적인 pthread_mutex_trylock()등을 활용하면

critical section진입을 위해서 yield하면서 busy waiting하지 않고 suspend되고 resume되는 이점이 있어서

전체적으로 성능상에 오히려 이점이 될 수도 있다.


 


+ Recent posts