IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스  >

OProfile로 퍼포먼스 병목현상 분석하기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.


제안 및 의견
피드백

난이도 : 초급

John Engel, 리눅스 기술 컨설턴트, IBM

2005 년 5 월 17 일

OProfile을 배우고 이것을 IBM® POWER™ 프로세서 기반 서버에서 실행되는 리눅스에서 활용해본다. OProfile을 소개하고 Linux on POWER 상에서의 구현을 설명한 다음, 코드를 프로파일링 하는 방법과 Linux on POWER 플랫폼 상에서 OProfile을 사용하여 결과를 분석하는 방법을 두 가지 예제를 통해 설명한다.

Introduction

개발자들이 코드 효율성을 높이려고 할 때, 퍼포먼스 병목현상을 분석하는 일은 가장 어려운 일 중 하나이다. 코드 프로파일링은 이 작업을 수월하게 하는 한 가지 방법이다. 코드 프로파일링에는 실행중인 시스템 상에서 특정 프로세서의 액티비티를 나타내고 있는 데이터 샘플들을 분석하는 일도 포함된다. OProfile은 이 솔루션을 Linux on POWER에도 제공한다. OProfile은 최신 Linux on POWER 배포판에 포함된다: Red Hat Enterprise Linux 4 (RHEL4)와 SUSE LINUX Enterprise Server 9 (SLES9). 이 글에서 Linux on POWER용 OProfile을 소개하고 이것을 퍼포먼스 병목현상을 찾는데 활용하는 방법을 설명한다.




위로


프로파일링 개요

Linux on POWER용 OProfile은 퍼포먼스 카운터 레지스터와 사용자 공간 데몬에 접근할 수 있는 커널 모듈을 사용하여 이 레지스트리에서 데이터들을 모은다. 데몬이 시작하기 전에, OProfile은 이벤트의 유형과 각 이벤트의 샘플 카운트를 설정한다. 어떤 이벤트도 설정되지 않으면, OProfile은 CYCLES 라고 하는 Linux on POWER용 디폴트 이벤트를 사용한다. CYCLES는 프로세서 사이클을 카운팅한다. 이벤트용 샘플 카운트는 이벤트 발생 때 마다 카운터가 얼마나 자주 증가하는지를 결정한다. OProfile은 낮은 오버헤드로 실행되도록 설계되었기 때문에 백그라운드에서 실행되는 데몬은 시스템 퍼포먼스에 영향을 주지 않는다.

OProfile은 POWER4™, POWER5™, PowerPC® 970 프로세서를 지원한다. PowerPC 970과 POWER4 프로세서는 8 개의 카운터 레지스터가 있지만, POWER5 프로세서는 6 개의 카운터 레지스터가 있다. 타이머 모드는 OProfile 지원이 되지 않는 아키텍쳐 상에서 사용된다. OProfile은 이 모드에서 타이머 인터럽트를 사용하고, 인터럽트를 사용할 수 없는 코드는 프로파일링 할 수 없다.




위로


OProfile 툴

OProfile의 커널 지원과 더불어, 커널과 인터랙팅하는 사용자 공간 툴과 모아진 데이터들을 분석하는 툴이 있다. 앞서 언급했지만, OProfile 데몬은 샘플 데이터를 모은다. 이 데몬을 제어하는 툴을 opcontrol 이라고 한다. 표 1은 opcontrol의 명령행 옵션들이다. 나중에 설명할 다른 툴들은 opreport과 opannotate. 이 두 개는 모아진 데이터를 분석하는데 사용된다. 모든 OProfile 툴 개요는 OProfile 매뉴얼의 Section 2.2를 참조하기 바란다. (참고자료)

RHEL4와 SLES9에서 지원되는 프로세서 이벤트 유형들은 다양하다. 다른 파워 프로세서 상에서 지원되는 이벤트 유형도 마찬가지로 다양하다. --list-events옵션과 opcontrol을 사용하여 자신의 플랫폼에서 지원되는 이벤트 리스트를 파악할 수 있다.

표 1. opcontrol 명령행 옵션

opcontrol 옵션 설명
--list-events프로세서 이벤트와 단위 마스크 리스팅
--vmlinux=<kernel image>프로파일링 될 커널 이미지 파일
--no-vmlinux커널 프로파일링 금지
--reset현재 세션에서 데이터 리셋(reset)
--setup실행 전 데몬 설정
--event=<processor event>해당 프로세서 이벤트 모니터링
--start샘플링 시작
--dump데이터를 데몬으로 플러시(flush)
--stop데이터 샘플링 중지
-h데몬 죽이기




위로


OProfile 예제

OProfile을 사용하여 프로세서 사이클, Translation Look-aside Buffer 버퍼 실패, 메모리 레퍼런스, branch mis-predictions, 캐시 실패, 인터럽트 핸들러 등의 이벤트를 분석한다. Opcontrol의 --list-events 옵션을 사용하여 특정 프로세스에 대해 모니터링 될 수 있는 이벤트 리스트를 제공한다.

다음 두 예제는 Linux on POWER용 OProfile을 사용하는 방법을 보여주고 있다. 첫 번째 예제에서는 프로세서 사이클을 감시하여 잠재적인 퍼포먼스 병목현상을 일으키는 엉성하게 짜인 알고리즘을 구별해낸다. 이 예제는 간단한 것 같지만, 애플리케이션을 프로파일링 할 때 이 방식을 사용하여 대부분의 프로세서 사이클이 어디에서 사용되는지를 구분할 수 있다. 그런 다음, 이 부분의 코드를 집중 분석하여 최적화 될 수 있는지를 확인한다.

두 번째 예제는 좀더 복잡하다. level 2 (L2) 데이터 미스를 구분하고 데이터 캐시 미스의 수를 줄일 수 있는 두 가지 솔루션을 제공한다.

예제 1: 엉성하게 작성된 코드 분석하기

이 예제의 목적은 엉성하게 작성된 코드 샘플을 컴파일 및 프로파일링하여 어떤 기능이 부족한 퍼포먼스를 보이는지를 분석하도록 하는 것이다. 이 예제는 간단하고 두 개의 함수-- slow_multiply()fast_multiply() --–를 사용하여 두 개의 숫자를 곱한다. (Listing 1)


Listing 1. 곱셈을 수행하는 두 개의 함수
  
int fast_multiply(x,  y) 
{
        return x * y;
}

int slow_multiply(x, y) 
{
        int i, j, z;
        for (i = 0, z = 0; i < x; i++) 
                z = z + y;
        return z;
}

int main()
{
        int i,j;
        int x,y;

        for (i = 0; i < 200; i ++) {
                for (j = 0; j " 30 ; j++) {
                        x = fast_multiply(i, j);
                        y = slow_multiply(i, j);
                }
        }
        return 0;
}

OProfile 주석과 함께 소스 코드를 볼 수 있도록 opannotate로 코드를 프로파일링하고 분석하라. 우선, 디버그 정보로 소스를 컴파일 해야 한다. opannotate는 이 주석을 추가해야 한다. 다음의 명령어를 실행하여 Gnu Compiler Collections C 컴파일러인 gcc를 사용하여 Listing 1의 예제를 컴파일한다. -g 플래그는 디버그 정보를 추가한다.

 gcc  -g multiply.c -o multiply   

그 다음, Listing 2의 명령어를 사용하여 코드를 프로파일링하고, 프로세서 사이클을 계산하는 CYCLES 이벤트를 사용하여 결과를 분석한다.


Listing 2. 프로파일 곱셈 예제용 명령어
# opcontrol --vmlinux=/boot/vmlinux-2.6.5-7.139-pseries64

# opcontrol --reset

# opcontrol --setup --event=CYCLES:1000

# opcontrol --start

Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.

# ./multiply

# opcontrol --dump

# opcontrol --stop
Stopping profiling.

# opcontrol -h
Stopping profiling.
Killing daemon.

마지막으로, opannotate 툴을 사용하여 --source 옵션을 가진 소스 코드 또는 --assembly 옵션을 가진 어셈블리 코드를 만든다. 한 개를 사용하든, 두 옵션 모두를 사용하는지는 프로파일링하고자 하는 상세 레벨에 달려있다. 예를 들어 --source 옵션을 사용하여 대부분의 프로세서 사이클이 발생한 곳을 구분한다.


Listing 3. 곱셈 예제의 opannotate 결과 분석
# opannotate --source ./multiply

/* 
 * Command line: opannotate --source ./multiply 
 * 
 * Interpretation of command line:
 * Output annotated source file with samples
 * Output all files
 * 
 * CPU: ppc64 POWER5, speed 1656.38 MHz (estimated)
 * Counted CYCLES events (Processor cycles) with a unit mask of
0x00 (No unit mask) count 1000
 */
/* 
 * Total samples for file : "/usr/local/src/badcode/multiply.c"
 * 
 *   6244 100.000
 */


               :int fast_multiply(x, y) 
    36  0.5766 :{ /* fast_multiply total:     79  1.2652 */
    26  0.4164 :        return x * y;
    17  0.2723 :}
               :
               :int slow_multiply(x, y) 
    50  0.8008 :{ /* slow_multiply total:   6065 97.1332 */
               :        int i, j, z;
  2305 36.9154 :        for (i = 0, z = 0; i " x; i++) 
  3684 59.0006 :                z = z + y;
    11  0.1762 :        return z;
    15  0.2402 :}
               :
               :int main()
               :{ /* main total:    100  1.6015 */
               :        int i,j;
               :        int x,y;
               :
     1  0.0160 :        for (i = 0; i " 200; i ++) {
     6  0.0961 :                for (j = 0; j " 30 ; j++) {
    75  1.2012 :                        x = fast_multiply(i, j);
    18  0.2883 :                        y = slow_multiply(i, j);
               :                }
               :        }
               :        return 0;
               :}
               

Listing 3의 다음 라인은 두 곱셈 함수에서 사용된 CYCLES 수이다:

36  0.5766 :{ /* fast_multiply total:     79  1.2652 */


50  0.8008 :{ /* slow_multiply total:   6065 97.1332 */

보다시피, fast_mulitply()는 단 79 샘플을 사용한 반면, slow_multiply()는 6065 샘플을 사용했다. 단순한 예제이고, 실제로 이런 일이 발생할 것 같지는 않지만, 코드를 프로파일링 하고 퍼포먼스 병목현상과 관련하여 이를 분석하는 방법을 보여주고 있다.

예제 2: level 2 데이터 캐시 미스 구분하기

이 예제는 첫 번째 것 보다 복잡하고 level 2 (L2) 데이터 캐시 미스를 구분하는 것이 포함된다. POWER 프로세서에는 on-chip L2 캐시를 포함하고 있는데 이는 프로세서 근처에 위치한 고속 메모리이다. 이 프로세서는 자주 변경된 데이터를 관찰할 때 L2 캐시를 본다. 두 개의 프로세서가 데이터 구조를 공유하고 있고 그렇게 공유된 데이터를 동시에 변경할 때 문제가 발생할 수 있다. CPU1에는 L2 캐시에 있는 데이터 카피가 포함되어 있는데 CPU2는 공유된 데이터 구조를 변경한다. CPU1의 L2 캐시의 카피는 이제 무효하기 때문에 업데이트 되어야 한다. CPU1은 추가 프로세서 사이클을 쓰는 메인 메모리에서 데이터를 검색해야 하는 힘든 과정을 수행해야 한다. 그림 1은 자신의 L2 캐시에 공유된 데이터 구조의 카피를 갖고 있는 두 개의 프로세서를 보여주고 있다.


그림 1. 데이터 구조를 공유하고 있는 두 개의 프로세서

이 예제에서, 데이터 구조를 보고(Listing 4) 두 개의 프로세서가 데이터 구조를 동시에 변경할 때 어떤 일이 발생하는지를 분석할 것이다. 그런 다음 데이터 캐시 미스를 관찰하고 이 문제를 해결 할 두 개의 솔루션을 검토해보고자 한다.


Listing 4. 공유된 데이터 구조
struct shared_data_struct {
   unsigned int data1;
   unsigned int data1;
}

Listing 5의 프로그램은 clone()시스템 호출을 사용하여 자식 프로세스를 만들면서 VM_CLONE 플래그로 이동된다. VM_CLONE플래그는 자식 프로세스가 부모와 같은 메모리 공간에서 실행되도록 한다. 부모 쓰레드는 데이터 구조의 첫 번째 엘리먼트를 변경하는 동안 자식 쓰레드는 두 번째 엘리먼트를 변경한다.


Listing 5. L2 데이터 캐시 미스를 나타내는 예제 코드
#include <stdlib.h>
#include <sched.h>

struct shared_data_struct {
        unsigned int data1;
        unsigned int data2;
};

struct shared_data_struct shared_data;

static int inc_second(struct shared_data_struct *);

int main(){

        int i, j, pid;
        void *child_stack;

        /* allocate memory for other process to execute in */
        if((child_stack = (void *) malloc(4096)) == NULL) {
                perror("Cannot allocate stack for child");
                exit(1);
        }

        /* clone process and run in the same memory space */
        if ((pid = clone((void *)&inc_second, child_stack,
CLONE_VM, &shared_data)) < 0) { perror("clone called failed."); exit(1); } /* increment first member of shared struct */ for (j = 0; j < 2000; j++) { for (i = 0; i < 100000; i++) { shared_data.data1++; } } return 0; } int inc_second(struct shared_data_struct *sd) { int i,j; /* increment second member of shared struct */ for (j = 1; j < 2000; j++) { for (i = 1; i < 100000; i++) { sd->data2++; } } }

gcc 컴파일러를 사용하여, Listing 6의 명령어를 실행하여 최적화 없이 예제 프로그램을 컴파일 한다.


Listing 6. Listing 5의 예제 코드를 컴파일하는 명령어
gcc -o cache-miss cache-miss.c

이제, OProfile을 사용하여 이 프로그램에 발생한 L2 데이터 캐시 미스를 분석할 수 있다.

이 예제의 경우, SLES9 Service Pack 1 (SLES9SP1)을 실행하는 두 개의 POWER5 프로세서 기반의 IBM eServer™ OpenPower™ 710에서 프로그램을 실행하고 프로파일링했다. --list-events 플래그는 opcontrol로 전달되어 어떤 이벤트가 L2 데이터 캐시 미스를 감시하는지를 결정한다. SLES9SP1을 실행하는 POWER5 프로세서 기반의 시스템의 경우 PM_LSU_LMQ_LHR_MERGE_GP9 이벤트는 L2 데이터 캐시 미스를 감시한다. 샘플 카운트를 1000으로 설정하면, 이 예제에서는, OProfile은 카운팅 되고 있는 1000 번째 하드웨어 이벤트용 샘플을 취한다. POWER4 프로세서 기반 서버 같은 다른 플랫폼을 사용한다면 이 이벤트는 달라질 것이다.

Listing 7의 명령어를 사용하여 다음과 같이 예제 코드를 프로파일링한다:


Listing 7. Listing 5 예제에서 L2 데이터 캐시 미스를 프로파일링 하는 명령어
# opcontrol --vmlinux=/boot/vmlinux-2.6.5-7.139-pseries64

# opcontrol --reset

# opcontrol --setup –event=PM_LSU_LMQ_LHR_MERGE_GP9:1000

# opcontrol --start
Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.

# ./cache-miss

# opcontrol --dump

# opcontrol -h
Stopping profiling.
Killing daemon.

# opreport -l ./cache-miss 
CPU: ppc64 POWER5, speed 1656.38 MHz (estimated)
Counted PM_LSU_LMQ_LHR_MERGE_GP9 events (Dcache miss occurred for
the same real cache line as earlier req, merged into LMQ) with a
unit mask of 0x00 (No unit mask) count 1000 samples % symbol name 47897 58.7470 main 33634 41.2530 inc_second

opreport 에서 결과를 분석할 때, main()inc_second()함수에 많은 캐시 미스가 있다는 것을 알게 될 것이다. opreport에 대한 -l 옵션은 바이너리 이미지 이름 대신 심볼 정보를 프린팅한다. 크기가 8 바이트이고 128 캐시 라인에 맞는 공유 데이터 구조를 변경하는 두 개의 프로세서가 있기 때문에 또 다시 캐시 미스가 발생한다.

데이터 캐시 미스를 줄이는 한 가지 방법은 데이터 구조를 덧대어(pad) 각 엘리먼트가 각자의 캐시 라인에 저장되도록 하는 것이다. Listing 8에는 124 byte pad를 가진 변경된 구조가 포함된다.


Listing 8. 공유된 데이터 구조와 패드
struct shared_data_struct {
   unsigned int data1;
   char pad[124];
   unsigned int data1;

Figure 2는 각 데이터 엘리먼트가 각 프로세서 상의 캐시 라인에 저장되는 방법을 보여준다.


그림 2. 패딩된 데이터 구조를 공유하는 두 개의 프로세서

전에 했던 것 처럼 프로그램을 재컴파일 하는데, 이번에는 변경된 데이터 구조를 사용한다. Listing 9의 명령어를 사용하여 결과를 다시 분석한다.


Listing 9. 패딩된 데이터 구조로 L2 데이터 캐시 미스를 프로파일링하는 명령어
# opcontrol --vmlinux=/boot/vmlinux-2.6.5-7.139-pseries64

# opcontrol --reset

# opcontrol --setup –event=PM_LSU_LMQ_LHR_MERGE_GP9:1000

# opcontrol --start
Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.

# ./cache-miss

# opcontrol --dump

# opcontrol -h
Stopping profiling.
Killing daemon.

# opreport -l ./cache-miss 
error: no sample files found: profile specification too strict ?

Opreport는 샘플링 된 데이터를 찾을 수 없기 때문에 에러가 있을 것이라고 하지만, 공유된 데이터 구조의 변경으로, 각 데이터 엘리먼트는 각자의 캐시 라인에 있기 때문에 L2 캐시 미스는 없을 것이다.

이제 L2 캐시 미스가 프로세서 사이클의 관점에서 얼마나 값비싼 것인지를 관찰 할 수 있다. 우선, 패딩 없이 원래의 공유된 데이터 구조로 코드를 프로파일링한다. (Listing 4) 샘플링 하게 될 이벤트는 CYCLES 이다. Listing 10에서 이 명령어를 사용하여 CYCLES 이벤트용 예제를 프로파일링 한다.


Listing 10. Listing 5 예제의 프로세스 사이클의 수를 프로파일링 하는 명령어
# opcontrol --vmlinux=/boot/vmlinux-2.6.5-7.139-pseries64

# opcontrol --reset

# opcontrol --setup –event=CYCLES:1000

# opcontrol --start
Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.

# ./cache-miss

# opcontrol --dump

# opcontrol -h
Stopping profiling.
Killing daemon.

# opreport -l ./cache-miss 
CPU: ppc64 POWER5, speed 1656.38 MHz (estimated)
Counted CYCLES events (Processor cycles) with a unit mask of 0x00
(No unit mask) count 1000 samples % symbol name 121166 53.3853 inc_second 105799 46.6147 main

이제 Listing 11의 명령어를 사용하여 패딩된 데이터 구조(Listing 8)로 예제 코드를 프로파일링한다.


Listing 11. 패딩된 데이터 구조로 예제의 프로세서 사이클의 수를 프로파일링하는 명령어
# opcontrol --vmlinux=/boot/vmlinux-2.6.5-7.139-pseries64

# opcontrol --reset

# opcontrol --setup –event=CYCLES:1000

# opcontrol --start
Using 2.6+ OProfile kernel interface.
Reading module info.
Using log file /var/lib/oprofile/oprofiled.log
Daemon started.
Profiler running.

# ./cache-miss

# opcontrol --dump

# opcontrol -h
Stopping profiling.
Killing daemon.

# opreport -l ./cache-miss 
CPU: ppc64 POWER5, speed 1656.38 MHz (estimated)
Counted CYCLES events (Processor cycles) with a unit mask of 0x00
(No unit mask) count 1000 samples % symbol name 104916 58.3872 inc_second 74774 41.6128 main

예상했겠지만 프로세서 사이클의 수는 L2 캐시 미스의 수와 함께 증가한다. 주요한 이유는 L2 캐시가 아닌 메인 메모리에서 데이터를 검색하는 연산 때문이다.

두 개의 프로세서들 간 캐시 미스를 피하는 또 다른 방법은 같은 프로세서 상에서 두 개의 쓰레드를 실행하는 것이다. 다음 예제는 Cpu affinity를 사용한다. 이것은 프로세스를 특정 프로세서에 바인딩한다. 리눅스 상에서 sched_setaffinity() 시스템 호출은 한 프로세서 상의 두 쓰레드를 실행한다. Listing 12sched_setaffinity() 호출을 사용하여 이 연산을 수행하는 원래 예제 프로그램의 또 다른 변이이다.


Listing 12. cpu affinity를 사용하여 L2 캐시 미스를 피하는 코드 예제
#include <stdlib.h>
#include <sched.h>

struct shared_data_struct {
        unsigned int data1;
        unsigned int data2;
};

struct shared_data_struct shared_data;

static int inc_second(struct shared_data_struct *);

int main(){

        int i, j, pid;

        cpu_set_t cmask;
        unsigned long len = sizeof(cmask);
        pid_t p = 0;
        void *child_stack;

        __CPU_ZERO(&cmask);
        __CPU_SET(0, &cmask);

        /* allocate memory for other process to execute in */
        if((child_stack = (void *) malloc(4096)) == NULL) {
                perror("Cannot allocate stack for child");
                exit(1);
        }

        /* clone process and run in the same memory space */
        if ((pid = clone((void *)&inc_second, child_stack,
CLONE_VM, &shared_data)) < 0) { perror("clone called failed"); exit(1); } if (!sched_setaffinity(0, len, &cmask)) { printf("Could not set cpu affinity for current
process.\n"); exit(1); } if (!sched_setaffinity(pid, len, &cmask)) { printf("Could not set cpu affinity for cloned
process.\n"); exit(1); } /* increment first member of shared struct */ for (j = 0; j < 2000; j++) { for (i = 0; i < 100000; i++) { shared_data.data1++; } } return 0; } int inc_second(struct shared_data_struct *sd) { int i,j; /* increment second member of shared struct */ for (j = 1; j < 2000; j++) { for (i = 1; i < 100000; i++) { sd->data2++; } } }

이 예제는 하나의 프로세서 상의 한 개의 L2 캐시 라인에서 공유된 데이터 구조로 같은 프로세서에서 두 개의 쓰레드를 실행한다. 결과는 0 캐시 미스이다. 앞에서 설명한 단계를 사용하여 캐시 미스를 프로파일링하여 한 프로세서 상에서 두 개의 프로세스를 실행할 때 어떤 L2 캐시 미스도 없다는 것을 확인하라. 데이터 캐시 미스 문제에 대한 제 3의 솔루션은 캐시 미스의 수를 줄이는 컴파일러 최적화를 사용하는 것이다. 하지만 이 옵션이 안되는 상황이 있기 때문에 코드를 프로파일링하고 퍼포먼스를 높여야 한다.




위로


요약

프로파일링은 개발 과정에서 가장 어려운 일이다. 자신이 만든 코드에서 최적의 퍼포먼스를 끌어내려면 좋은 툴을 갖추는 것이 필수적이다. OProfile은 Linux on POWER에 맞는 툴이라고 할 수 있다. 다른 많은 퍼포먼스 및 디버깅 툴들도 많이 있다. 다른 유형의 프로세서 이벤트를 제외하고는 POWER 프로세서 기반의 리눅스 플랫폼에서 OProfile을 실행하는 것은 다른 아키텍쳐에서 OProfile을 실행하는 것과 비슷하다. 따라서 다른 플랫폼 상에서 OProfile을 사용해봤다면 Linux on POWER에서 다루는데 어려움이 없을 것이다.




위로


감사의 말

Linda Kinnunen과 Maynard Johnson에게 특별한 감사를 전합니다.



참고자료



필자소개

John Engel IBM eServer Solutions Enablement organization의 리눅스 기술 컨설턴트이다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


The following terms are trademarks of International Business Machines Corporation in the United States, other countries, or both:

  • eServer
  • IBM®
  • PowerPC®
  • POWER
  • POWER4
  • POWER5
. UNIX is a registered trademark of The Open Group in the United States and other countries. Linux is a trademark of Linus Torvalds in the United States, other countries, or both. Other company, product or service names may be trademarks or service marks of others. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의