전체 글 269

[Modern C++] (8) 다듬기

시퀀스 컨테이너 (vector/list/string/deque)와 연관 컨테이너 (map/set/unordered_map/set)에서 원소를 삽입하는 방법에 대하여 고려할 사항을 알아본다. 1. 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려해라 Widget 인스턴스에 string 객체를 추가하는 addName 함수에 대하여 오른값과 왼값을 받는 중복 적재 버전 class Widget { private: std::vector names; public: // 왼값 참조를 받는 버전 void addName(const std::string& newName) { names.push_back(newName); } // 오른값 참조를 받는 버전 void addName(std::strin..

Advanced C++ 2021.05.04

[컴퓨터 구조] 12. 가상 메모리

가상 메모리 기술 2차 저장장치를 메인 메모리를 "캐시"로 사용하는 기술로 여러 프로그램을 동시에 실행하여 메모리를 효과적으로 공유하고 메인 메모리 장치의 크기 한계를 극복하여 더 큰 프로그램을 실행할 수 있게 해 준다. 프로그램 데이터 보호 - 프로그램마다 사용하는 주소 공간을 따로 두고 이를 보호한다 : 각 프로그램들이 자신만의 주소 공간을 가지고 가상 메모리 기술이 가상 주소 공간에 대해 실제 주소로 변환해준다. 페이징 기법 프로세서는 가상 주소를 만들어서 사용하는 반면, 메모리는 실제 주소를 사용하여 데이터에 접근한다. 가상 메모리와 주 메모리는 일정한 크기의 조각으로 나뉜 페이지로 구성되어 있으며 각 가상 페이지 하나는 주 메모리 페이지 하나에 사상된다. RISC-V에서 주소는 64비트를 가지지..

[컴퓨터 구조] 함수 호출 규약

함수 호출 규약이란 함수 호출 시 일어나는 행동에 대한 규칙을 의미하며 함수 인자들에 대해 어떤 순서로 스택에 쌓을 것 인지, 인자를 레지스터로 이용할 것 인지 그리고 함수 종료 후 스택을 누가 정리할 것 인지에 대해 정해놓은 규칙이다. 어떤 함수 규약이 호출되던, 다음과 같은 일이 발생한다. 1. 모든 인자들은 4 바이트(8 바이트)로 확장되고 적절한 메모리 위치로 삽입된다. 이 위치들은 주로 스택 상 메모리이지만 레지스터들을 사용할 수 도 있다. 이는 호출 규약에 따른다. 2. 프로그램 실행은 호출된 함수의 주소로 점프한다. 3. 함수 안에서 보존 레지스터들이 스택에 저장된다. 함수 프롤로그라고도 하며 컴파일러가 작성한다. 4. 함수에 해당하는 코드들이 실행되고 return 반환값이 eax 레지스터에..

[컴퓨터 구조] 스택 포인터와 프레임 포인터

함수 호출과정에서 스택 프레임에 대해 일어나는 일을 알아보도록 한다. 스택 포인터와 프레임 포인터 스택 프레임 (Stack Frame) 기본적으로 함수가 호출될 때마다 전달한 인자와 정의한 지역(자동) 변수가 높은 주소부터 낮은 주소의 방향으로 차례대로 저장되는 구조이다. 이외에도 다른 함수를 호출할 때 복귀할 주소(다음 실행할 명령어의 주소), 프레임 포인터 및 보존되는 레지스터들이 스택에 저장된다. 스택 포인터는 함수 호출 시작부터 피호출 프로그램이 실행되는 단계 차례대로 저장되는 값들을 저장하기 위해 현 시점에서 저장할 메모리의 위치를 가리킨다. 스택 프레임에 저장되는 값 - 복귀 주소 - 호출자 루틴의 프레임 포인터 - 사용하던 보존 레지스터 - 피호출자에 전달하는 인자 - 피호출자에서 사용되는 ..

[Modern C++] (7-2) 동시성 API

C++11부터 도입된 동시성 API관련 항목을 이어 공부한다. 4. 스레드 핸들 소멸자들의 다양한 행동 방식을 주의하라. std::thread, 미래 객체 std::future 모두 시스템 스레드에 대응되는 핸들이라고 할 수 있다. std::thread는 소멸자 호출시 바탕 스레드가 합류가능상태이면 프로그램이 종료되어버린다. 하지만 미래 객체는 암묵적 join/detach를 한 것처럼 프로그램이 종료되지는 않는다. 미래 객체는 스레드-미래 객체의 통신 채널의 끝 단자의 역할을 한다. 비동기적으로 실행하는 피호출자는 계산 결과를 통신 채널에 기록한다.(std::promise를 통해서) 호출자는 미래 객체를 통해 결과를 읽는다. 계산 결과가 저장되는 곳은? 피호출자에 담아둔다: std::promise가 피호..

Advanced C++ 2021.05.01

[Modern C++] (7-1) 동시성 API

C++11를 기점으로 도입된 동시성(Concurrency)을 언어와 표준 라이브러리에 도입하였다. 표준 라이브러리의 동시성 구성요소들 (과제, 미래, 스레드, 뮤 텍스, 조건 변수, 원자적 객체)에 대해 알아보도록 한다. 1. 스레드(Thread) 기반 프로그래밍보다 과제(Task) 기반 프로그래밍을 선호하라 스레드 기반 프로그래밍 이란 - 새로운 스레드를 실행하는 함수를 할당하여 생성하는 방식 int doAsyncWork(); std::thread t(doAsyncWork); 과제 기반 프로그래밍 auto fut = std::async(doAsyncWork); // fut은 future를 뜻한다. std::async에 전달된 doAsyncWork는 과제로 취급한다. 과제 기반 프로그래밍을 선호하는 이유..

Advanced C++ 2021.05.01

[C++ Thread] 쓰레드 풀 Thread Pool

다음을 참고하여 작성하였습니다. 모두의 코드, 쓰레드 풀: modoocode.com/285 쓰레드 풀이란 특정 함수를 실행하는 워커(Worker) 쓰레드를 관리하는 객체이다. 다음과 같은 역할을 수행한다. - 요청받은 작업을 작업 큐에 삽입한다. - 대기 중인 워커 쓰레드 중 하나를 wakeup하여 작업을 큐에서 꺼내고 실행한다. - 다수의 쓰레드에서 접근하는 작업 큐는 뮤텍스로 보호한다. 쓰레드 풀 생성 워커 쓰레드를 생성하는 것으로 시작한다. ThreadPool::ThreadPool(size_t num_threads) : num_threads(num_threads), stop_all(false) { worker_threads.reserve(num_threads); for (size_t i = 0; i..

[C++ Thread] 약속과 미래 객체, std::promise / std::future

C++11에 도입된 std::promise와 std::future 객체를 알아본다. 약속 객체 (std::promise) 약속 객체는 주로 비동기적으로 실행되는 다른 쓰레드로 부터 계산된 값이나 예외를 저장할 수 있는 공간을 가지고 있다. 약속 객체가 쓰레드의 완료로 저장한 값을 전달하기 위해 미래 객체가 필요하고 미래 객체는 약속 객체에 의해 생성된다. 또한 약속 객체는 결과 전달을 한번만 하도록 설계되었다. 템플릿 정의 template class promise; // 기본 템플릿 template class promise; // 쓰레드간 객체를 통신하기위해 사용한다. template class promise; // 상태없는 이벤트를 통신하기위해 사용한다. - 템플릿 인수에 전달받을 값의 타입을 명시하여..

[C++ Thread] memory order 와 atomic 객체

메모리 수정 순서와 동기화 방법을 제공하는 atomic 객체 ※ 다음 글들을 참고하였습니다. modoocode.com/271 en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering 메모리가 수정되는 순서에 관하여 - 단일 스레드에서 1) 동적 파이프라인 스케줄링에 의해 다음 함수를 컴파일하여 실행했을 시 a 대입과 b 대입의 순서에 대한 문제 #include int a = 0; int b = 1; int func(int* arr) { a = arr[1] + b; b = 5; return a; } int main() { int arr[5] = { 0,1,2,3,4 }; int result = func(arr); return 0; } 코드 흐름 상으로..

[컴퓨터 구조] 11. 캐시 성능 측정 및 향상

다단계 캐싱(multilevel caching) 여러 캐시를 계층적 구조로 사용하여 실패 손실을 줄이는 기술. 1 차 캐시에 데이터가 없다면 2차 캐시를 조회하게 되는데 2차 캐시에 데이터가 있다면 주 메모리 접근 시간보다 훨씬 작다. 보통 1차 캐시는 2차 캐시보다 작다. 1차 캐시는 CPU와 최대한 가깝도록 설계하여 적중 시간을 최소화하고 2차 캐시는 더 크게 설계하여 큰 블록에 대한 실패율을 감소시킨다. 적중된 캐시에 의한 정상적인 CPU 실행 시간은 다음과 같다. CPU 시간 = (CPU 클럭 사이클 + 메모리 지연 클럭 사이클) x 클럭 사이클 시간 메모리 지연 클럭 사이클은 읽기와 쓰기 행동에 의해 걸리는 시간의 합이다. 메모리 지연 클럭 사이클 = 읽기 지연 사이클 + 쓰기 지연 사이클 읽기..