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
- 컨테이너화된 애플리케이션의 자동 배포나 스케일링을 제공하는 관리 시스템
즉, 도커는 컨테이너에 띄우고 실행하는 기술’이고 쿠버네티스는 도커를 관리하는 툴인 셈
- 컨테이너화된 애플리케이션의 자동 배포나 스케일링을 제공하는 관리 시스템