HTC
- HPC 환경에서는 하나의 작업을 여러 작업으로 분리한다.
- 따라서 분리된 작업 처리 과정에서 상호 간 통신이 필요하다.
- MPI(Message Passing Interface) 방식으로 프로그램을 작성해야 한다.
- 상호 간에 의존성이 존재할 수밖에 없다.
- 의존성 때문에 가장 늦게 처리된 작업이 전체 속도에 영향을 줄 수밖에 없다.
 
- HTC는 기본적으로 서로 의존 관계가 없는 작업을 병렬로 처리하는 방식이다.
- 따라서 작업 하나에 발생한 오류가 전체 작업에 영향을 주지 않는다.
- 유휴한 컴퓨팅 자원을 활용하는 것에 중점을 두고 있다.
 
HTCondor (High Throughput Condor)
- 유휴한 컴퓨팅 자원을 클러스터링해 하나의 컴퓨터처럼 활용할 수 있다.
- Condor라는 이름으로 프로젝트를 시작했으나, 상표권 침해 소송에 걸려 HTCondor가 되었다.
- 작업(또는 태스크)을 큐에 보내는데, 이때 작업 제출 시스템을 submit server라고 한다.
- 작업이 큐에 제출되면 HTCondor는 워커 노드를 지정해 해당 작업을 처리한다.
HTCondor로 HTC를 구성할 때 자주 나오는 용어
- 작업(job) 또는 태스크(task)- HTCondor 큐에 제출될 수 있는 독립된 컴퓨팅 작업 단위
- 실행 파일: 컴퓨팅 노드에 할당되서 실제 실행 가능한 프로그램이나 스크립트(ex. bash)
- 인풋: 실행 파일이 컴퓨 노드에 실행될 때 필요한 인자나 파일과 같은 정보
- 아웃풋: 실행 파일이 컴퓨팅 노드에서 싫애되고 난 후 결과와 실행 중 발생한 정보
 
- 머신(machine)- 컴퓨팅 노드라고 불리는 실제 컴퓨터를 의미한다.
- 일반 데스크톱 컴퓨터일 수도 있고, 서버가 될 수도 있다.
- 컴퓨팅 노드는 일반적으로 멀티코어, CPU, 메모리, 디스크를 갖고 있다.
 
- 슬롯(slot)- 머신에서 작업을 처리할 하나의 단위를 말한다.
- 즉, 하나의 슬롯당 하나의 작업이 처리된다.
 
실행 과정 예시
- 
copy_check 프로그램 - 2개의 java 입력파일을 받아 similarity.out 파일로 생성한다.
- HTCondor가 작업에 대해 이해할 수 있는 작업명세서가 필요하다.
 // job.jds executable = copy_check arguments = program1.java program2.java similarity.out // copy_check가 컴퓨팅 노드로 전송될 때 // 어떤 파일들이 같이 전송되어야 하는지 알려준다. // arguments와 다르게 콤마(,)로 구분한다. transfer_input_files = program1.java, program2.java log = job.log output = job.out error = job.err // copy_check 프로그램이 동작하기 위한 // 컴퓨팅 자원의 최소 요건 request_cups = 1 request_disk = 20MB request_memory = 100MB // 몇 개의 작업을 HTCondor 클러스터로 보낼 것인지 지정 // 아래는 1개 작업을 HTCondor 클러스터에 서브미션하라는 것 queue 1
연습문제
- MPI 프로그래밍의 간단한 예를 찾아보고 설명해보라.
- C/C++에 있는 MPI를 사용한 원주율 계산:
- 적분 구간을 여러개로 나눈 다음, 프로세스들에게 할당하는 방식
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
// MPI 헤더 파일
#include<mpi.h>
int n_size_;  // 프로세스의 총 갯수
int n_rank_;  // 각 프로세스에 부여된 랭크
// 정밀도
double eps_precision = 1e-12;
// 적분 대상이 되는 함수
double func_integrand(double u);
int main(int argc, char *argv[]) {
    // MPI 초기화
    MPI_Init(&argc, &argv);
    // 프로세스 총 갯수 및 각 프로세스의 랭크
    MPI_Comm_size(MPI_COMM_WORLD, &n_size_);
    MPI_Comm_rank(MPI_COMM_WORLD, &n_rank_);
    if (n_rank_ == 0) {
        // 랭크가 0 인 프로세스
        fprintf(stdout,
            "We have %d processes.\n", n_size_);
        fprintf(stdout, "\n");
    }
    // 모든 프로세스가 여기에 도달할 때 까지 대기
    MPI_Barrier(MPI_COMM_WORLD);
    // MPI 통신을 위한 변수들
    int tag;
    MPI_Status status;
    // 현재 단계의 원주율 값
    double pi_now = 0.;
    // 이전 단계의 원주율 값
    double pi_prev;
    // 현재 단계의 각 프로세스의 기여분
    double pi_rank = 0.;
    // 이전 단계의 각 프로세스의 기여분
    double pi_rank_prev;
    // 수렴 여부를 체크하기 위한 제어 변수
    int converging = 0;
    /* 각 프로세스에서 수치적분을 위한 구간 및
     * 격자 간격 */
    unsigned long int nbin_u = 1;
    double u_min = (double)n_rank_ / (double)n_size_;
    double u_max = u_min + 1. / (double)n_size_;
    double delta_u = fabs(u_max - u_min);
    int istep = 1;
    /* 지정한 정밀도 이내에서 수렴할 때 까지
     * 반복문 실행 */
    while (converging == 0) {
        pi_prev = pi_now;
        pi_rank_prev = pi_rank;
        pi_rank = 0.;
        unsigned int iu;
        // 수치적분 계산
        if (istep == 1) {
            for (iu = 0; iu < nbin_u; iu++) {
                double u0 = u_min + delta_u * (double)iu;
                double u1 = u0 + delta_u;
                pi_rank +=
                    0.5 * delta_u * (func_integrand(u0) +
                                     func_integrand(u1));
            }
        } else {
            pi_rank = 0.5 * pi_rank_prev;
            for (iu = 0; iu <= nbin_u; iu++) {
                if (iu % 2 == 0) {
                    continue;
                }
                double u_now = u_min + delta_u * (double)iu;
                pi_rank +=
                    delta_u * func_integrand(u_now);
            }
        }
        pi_now = 0.;
        if (n_rank_ == 0) {
            // 랭크가 0 인 프로세스
            /* 모든 프로세스의 기여분들을 취합하여
             * 원주율의 값 계산 */
            pi_now = pi_rank;
            for (int irank = 1; irank < n_size_; irank++) {
                tag = 1000 + irank;
                double pi_add;
                MPI_Recv(&pi_add, 1, MPI_DOUBLE, irank,
                         tag, MPI_COMM_WORLD, &status);
                pi_now += pi_add;
            }
            fprintf(stdout,
                "    step %d : pi = %.12f\n", istep, pi_now);
        } else {
            // 랭크가 0 이 아닌 프로세스
            tag = 1000 + n_rank_;
            MPI_Send(&pi_rank, 1, MPI_DOUBLE, 0,
                     tag, MPI_COMM_WORLD);
        }
        if (n_rank_ == 0) {
            // 랭크가 0 인 프로세스
            // 수렴 체크
            if (fabs(pi_now - pi_prev) <
                    0.5 * eps_precision *
                    fabs(pi_now + pi_prev)) {
                converging = 1;
            }
            for (int irank = 1; irank < n_size_; irank++) {
                tag = 2000 + irank;
                MPI_Send(&converging, 1, MPI_INT, irank,
                         tag, MPI_COMM_WORLD);
            }
        } else {
            // 랭크가 0 이 아닌 프로세스
            tag = 2000 + n_rank_;
            MPI_Recv(&converging, 1, MPI_INT, 0,
                     tag, MPI_COMM_WORLD, &status);
        }
        istep += 1;
        // 적분 구간의 격자 갯수를 2배로 증가
        nbin_u = 2 * nbin_u;
        delta_u = 0.5 * delta_u;
    }
    // 모든 프로세스가 여기에 도달할 때 까지 대기
    MPI_Barrier(MPI_COMM_WORLD);
    if (n_rank_ == 0) {
        // 랭크가 0 인 프로세스
        fprintf(stdout, "\n");
        fprintf(stdout, "pi from numerical integration\n");
        fprintf(stdout, "  > pi = %.12f\n", pi_now);
        fprintf(stdout, "pi from C math library\n");
        fprintf(stdout, "  > pi = %.12f\n", M_PI);
    }
    // MPI 종료
    MPI_Finalize();
    return 0;
}
double func_integrand(double u) {
    return 2. / (fabs(u * u) + fabs((1. - u) * (1. - u)));
}
- HTCondor와 유사한 솔루션을 조사하여 설명하라
- slurm
- 리눅스에서 사용하는 클러스터 관리 및 작업 스케쥴링 시스템
- cluster server 상에서 작업을 관리하기 위한 프로그램
- 노드 간 통신을 통해 작업이 관리된다.
 
- kubernetes
- 컨테이너화된 애플리케이션의 자동 배포나 스케일링을 제공하는 관리 시스템
 즉, 도커는 컨테이너에 띄우고 실행하는 기술’이고 쿠버네티스는 도커를 관리하는 툴인 셈
 
- 컨테이너화된 애플리케이션의 자동 배포나 스케일링을 제공하는 관리 시스템