github source code
https://github.com/sg20180546/CSAPP/tree/main/CSE4100/stockserver
GitHub - sg20180546/CSAPP: study Computer Systems : A Programmer's Perpective
study Computer Systems : A Programmer's Perpective - GitHub - sg20180546/CSAPP: study Computer Systems : A Programmer's Perpective
github.com
1.개발 목표
2.개발 범위 및 내용
A.개발 범위
아래 항목을 구현했을 때의 결과를 간략히 서술
1)Task 1: Event-driven Approach
event-driven 방식은 모든 client 와의 통신을 한가지 프로세스에서 순차적으로 수행한다.
동작 (1) server booting
server를 ./stockserver {port} PRODUCTION으로 배포 버전으로 구동한다. 단 ./stockserver {port}만으로 구동해도 default는 PRODUCTION으로 되어 실행된다.(BENCHMARK version은 아래에서 설명 예정) Server running on {port} 를 STDOUT에 출력하며 connection을 기다린다
동작 (2) client connection
client와 연결을 성공하면 Connected to ({clienthost}:{clientport})를 출력한다
동작 (3) client interactive
client가 데이터를 전송하면 입력받은 데이터의 바이트 수를 서버의 stdout에 Server received n bytes 포맷으로 출력하고, 데이터를 파싱해 알맞은 동작을 수행한다
동작 (3-1) show
클라이언트가 "show\n" 를 전송하면 현재 서버에 적재된 주식 정보를
"{ID} {COUNT} {PRICE}\n" 으로 모두 출력한다
동작 (3-2) buy
클라이언트가 "buy {ID} {COUNT}\n" 을 전송하면 해당 ID를 가진 주식이 존재하는지 파악한다. 없으면 NO SUCH STOCK을 전송한다
존재한다면 남은 주식 수량이 살 주식보다 부족하다면 NOT ENOUGH LEFT STOCK을 전송한다
해당 주식의 수량을 감소시키고 "[buy]success\n"를 전송한다
동작 (3-3) sell
클라이언트가 "sell {ID} {COUNT}\n" 을 전송하면 해당 ID를 가진 주식이 존재하는지 파악한다. 없으면 NO SUCH STOCK을 전송한다
해당 주식의 수량을 증가시키고 "[sell]success\n"를 전송한다
동작 (3-4) exit
클라이언트가 "exit\n"을 전송하면 Connection Closed를 서버의 stdout에 출력하고 해당 소켓을 닫는다
동작 (3-5) enter
클라이언트가 "\n"을 전송하면 "Please Type Command\n"를 전송한다
동작 (3-5) no cmd
클라이언트가 존재하지 않는 명령어를 전송하면 "No Such Command\n"를 전송한다
동작 (3-6) invalid argument
클라이언트가 존재하는 명령어를 전송했으나 잘못된 argument를 포함했을 경우 "Invalid Argument\n"를 전송한다
동작 (4) read stock.txt file
서버를 처음 부팅하면 디스크의 stock.txt 에 저장된 "{ID} {COUNT} {PRICE}\n" 포맷을 binary tree형태로 메모리에 적재한다
동작 (5) file synchronization
모든 클라이언트와의 연결이 종료되었거나 / 마지막 파일과 메모리 동기화 시간이 16초를 초과했을 경우 메모리 상의binary tree를 stock.txt에 동기화해준다
2)Task 2: Thread-based Approach
thread_based approach는 두가지 유형의 thread가 각각의 context를 실행한다
하나의 parent thread는 client의 연결 요청을 받으면 global scope(sbuf)에 connfd,client ip,client 를 저장한다
N개의 child thread(worker thread)는 parent thread가 global scope에 저장한 정보를 바탕으로 각자의 문맥을 실행한다
동작 (1) server booting
server를 ./stockserver {port} PRODUCTION {thread_n}으로 실행할 수 있다. 단 ./stockserver {port}만으로 실행해도 default는 PRODUCTION, child 쓰레드의 갯수는 4개다.
Server running on {port} 를 STDOUT에 출력하며 parent thread는 connection을 기다리고, worker thread들은 parent thread가 전역공간에 connection에 대한 정보를 저장하기를 기다린다
동작 (2) client connection
client와 연결을 성공하면 Connected to ({clienthost}:{clientport})를 출력한다
동작 (3) client interactive
client가 데이터를 전송하면 입력받은 데이터의 바이트 수를 서버의 stdout에 Server received n bytes 포맷으로 출력하고, 데이터를 파싱해 알맞은 동작을 수행한다
동작 (3-1) show
클라이언트가 "show\n" 를 전송하면 현재 서버에 적재된 주식 정보를
"[show]success\n"와 함께
"{ID} {COUNT} {PRICE}\n" 으로 모두 출력한다
동작 (3-2) buy
클라이언트가 "buy {ID} {COUNT}\n" 을 전송하면 해당 ID를 가진 주식이 존재하는지 파악한다. 없으면 NO SUCH STOCK을 전송한다
존재한다면 남은 주식 수량이 살 주식보다 부족하다면 NOT ENOUGH LEFT STOCK을 전송한다
해당 주식의 수량을 감소시키고 "[buy]success\n"를 전송한다
동작 (3-3) sell
클라이언트가 "sell {ID} {COUNT}\n" 을 전송하면 해당 ID를 가진 주식이 존재하는지 파악한다. 없으면 NO SUCH STOCK을 전송한다
해당 주식의 수량을 증가시키고 "[sell]success\n"를 전송한다
동작 (3-4) exit
클라이언트가 "exit\n"을 전송하면 Connection Closed ({clienthost}:{clientport})를 서버의 stdout에 출력하고 해당 소켓을 닫는다
동작 (3-5) enter
클라이언트가 "\n"을 전송하면 "Please Type Command\n"를 전송한다
동작 (3-5) no cmd
클라이언트가 존재하지 않는 명령어를 전송하면 "No Such Command\n"를 전송한다
동작 (3-6) invalid argument
클라이언트가 존재하는 명령어를 전송했으나 잘못된 argument를 포함했을 경우 "Invalid Argument\n"를 전송한다
동작 (4) read stock.txt file
서버를 처음 부팅하면 디스크의 stock.txt 에 저장된 "{ID} {COUNT} {PRICE}\n" 포맷을 binary tree형태로 메모리에 적재한다
동작 (5) file synchronization
모든 클라이언트와의 연결이 종료되어 모든 worker thread가 context를 실행하지않고 잠들어있는 경우/ 마지막 파일과 메모리 동기화 시간이 16초를 초과했을 경우 메모리 상의 binary tree를 stock.txt에 동기화해준다
3)Task 3: Performance Evaluation
./benchmark <port> <client_number> <total_query> <-soption>
port : 서버가 올라갈 port numberclient_number : 연결을 시도할 client process의 총 갯수total_query : 전체 client가 서버에 요청할 모든 쿼리의 갯수, 만약 total_query=10000 client_number=4라면 각 client에서 2500개의 쿼리를 서버에 보낸다-soption : -e 는 event_driven 방식의 서버 benchmarking, -t는 thread_based 방식의 서버 benchmakring-t뒤에 숫자를 붙여주면 총 worker thread의 갯수를 조정할 수 있다. default는 4개 worker thread다.
example) ./benchmark 1024 64 600000 -t8 : 1024 포트에서 thread_based 서버가 부팅된다. worker thread는 총 8개이다. 64개의 클라이언트가 총 600,000개의 쿼리를 나누어서 서버에 요청한다
./benchmark 1024 64 600000 -e 위와 동일한 조건에 event_driven 서버가 부팅된다
총 4가지 척도를 측정한다
서버부팅시간 / 랜덤쿼리 / show 쿼리 / modify 쿼리
실행시간(execution time)과 , Query Throughput을 출력한다.
B.개발 내용
C.B.의 개발 내용을 구현하기 위해 어느 소스코드에 어떤 요소를 추가 또는 수정할 것인지 설명. (함수, 구조체 등의 구 현이나 수정을 서술)
event/thread 공통 :
stockfile_handle.c : 실행파일 stockserver와 동일한 경로에 위치한 stock.txt를 메모리에 binary tree형태로 적재시키고(read_stockfile) 메모리의 binary tree를 stock.txt에 fsync하는 (fsync_stockfile) 함수가 있다
util.c : csapp.c의 open_listenfd, rio 패키지 를 커스텀한 함수가 있고 socket_close는 close의 사용자 친화적인 래퍼함수다(연결 종료메시지, 에러메시지)
parser.c : 클라이언트의 command line을 파싱해서 command / argument를 찾거나 잘못된 요청의 경우 예외처리를 한다
binary_tree.c : 삽입(insert), 찾기(find), 수정(modify), string에 binary tree를 stock.txt format으로 출력하기(print_to_buf) 가 있다.
impl.c : binary_tree.c의 함수들을 client command에 대응하도록 만든 wrapper함수다. buy/sell의 경우 주식 노드를 찾고 (find) 수정(modify)을 한다
common.c : 전역에서 사용되는 변수 및 구조체 / 환경변수 / 매크로 함수 및 변수 / 사용하는 standard library 를 담고 있다
event driven
network.c FD POOL을 초기화하는 init_pool, connection들을 관찰하고 있는 see_pool, connection의 command를 파싱/실행/결과를 전송하는 write_pool 함수가 있다
thread based
binary_tree.c
공통된 부분에서 binary_tree를 modify하기 전 획득해야하는 semaphore가 추가된다(modify_lock)
sem_wait(&stock->modify_mutex);
stock->count+=count;
sem_post(&stock->modify_mutex);
threading.c
typedef struct _waiting_connfd {
int connfd;
struct sockaddr_storage clientaddr;
}waiting_connfd;
typedef struct{
waiting_connfd *waiting_connfd;
int n;
int front,rear;
sem_t mutex;
sem_t empty_slots;
sem_t items;
} sbuf_t;
공통된 부분에서 connection에 관한 정보를 관리하기 위한 sbuf_t가 추가되었다. 변수 N은 최대로 pending 가능한 connection의 수를 의마한다 worker thread가 connection을 획득하기 위해선 sem_t mutex에 락을 획득해야한다.
network_worker 함수에서는 conenction을 획득하고 놀고 있는 쓰레드의 갯수를 의미하는 변수인 idle_threads를 감소시키고, service라는 함수를 호출한다. 이후 연결이 종료되어 service함수가 리턴되면 idle_threads를 다시 증가시키고, 만약 모든 쓰레드가 idle 한 상태라면 sbuf에 락을 걸어놓고 자신의 thread group id, 즉 자기 자신에게 SIGSYNC 시그널을 보낸다 (SIGUSR1로 매크로처리되어있음).
SIGSYNC 핸들러는 fsync_stockfile을 실행한뒤 다시 sbuf에 락을 해제하며 핸들러가 종료된다
network.c
service 함수는 무한루프를 돌며 쓰레드가 잡은 connection이 종료되기 전까지 커맨드를 파싱/실행/결과를 전송한다
impl.c
impl.c의 sell과 buy 에서는 현재 접속중인 reader(writer) 수 인 reader_n과 writer_n을 스핀루프로 확인함으로써 race condition을 처리하고있다
util.c
thread_safe_printf : printf는 thread_unsafe한 함수이기때문에 thread_safe한 vprintf로 래핑했다.
benchmark
benchmark.c의 경우 컴파일하여 실행파일에 위에서 언급한 parameter를 argv로 넣어주면 하나의 서버에서 많은 클라이언트들이 접속/쿼리를 요청하며 성능을 평가한다
다만 비교 평가를 하기에는 반복 작업이 많아지므로 macro.c 파일에 loop에서 파라미터를 바꿔가며 실험이 가능하도록 코드를 짜놓았다
다만 꼭 필요한 부분만 주석처리를 해서 사용하기를 추천하는데, 너무 시간이 많이 걸릴 수 있기 때문이다
3.구현 결과
4.성능 평가 결과 (Task 3)
Benchmark environment
모두 OS : 64 bit Linux(Ubuntu 20.04) ,5.13.0-4 인텔® 코어™ i5-1035G4 1.10Ghz 프로세서, 16GB Memory 상에서 벤치마킹하였다
1. x=thread_n y=execution time
total query = 2^22(4194304) , client_n=64
-> -t8일때 가장 throughput이 peak인 것을 확인할 수 있는데 , 이는 benchmark envirnoment의 하이퍼쓰레딩에 의해 CPU갯수가 8개이기 때문이다.
2. x=client number y=execution time
1)thread_based
thread_n=32 , total query= 2^20
->thread based server의 경우 client의 갯수가 256~8개일때까지 큰 성능차이가 없음을 확인할 수 있다. 왜냐하면 최대 쓰레드가 동시에 8개까지만 돌아갈 수 있음으로 1번 실험과 같은 이유라고 추측한다.
1)event_driven
->event driven server의 경우 client_n 이 감소할수록 급격하게 성능이 나빠지는 것을 볼 수 있다. 왜냐하면 한번의 select 루프에 처리할 수 있는 쿼리의 갯수가 작아지기때문에, client n이 감소할수록 select의 오버헤드가 급격히 증가하기 때문이다.
비교 결과 Thread based가 절대적으로 우수하며, client의 병렬적 처리에도 큰 영향을 받지 않는 모습을 확인할 수 있다
3. thread vs event
-> event based server의 경우 생성해야할 쓰레드가 없기 때문에 부팅속도는 thread based server보다 빠르다
-> 그러나 전체적인 Thoughput은 많게는 12배, 최소 3배 이상 thread_Based server가 높은것을 확인할 수 있다.
4.query type
event driven server의 경우 쿼리 타입에 따른 로드워크 차이가 적음을 확인할 수 있다.
thread based server의 경우 lock에 관한 이슈와 thread 간 context switch에 관한 이슈가 있기때문에 때에 따라선 어느 한쪽이 더 우수하다.
Memory Allocation (0) | 2022.03.05 |
---|---|
Memory Virtualization (0) | 2022.02.12 |
Exceptional Control Flow (0) | 2022.02.09 |
Structure of Cache Memory (0) | 2022.02.07 |
디스크 저장장치 (0) | 2022.01.28 |
댓글 영역