std::vector에서 capacity가 꽉 찬 상태로 push_back()을 하면 내부적으로 어떤 일이 일어나고, 시간복잡도는 어떻게 되나?
주소공간, 메모리 재할당 그리고 복사/이동 때문에 시간복잡도 O(N)까지는 맞는데 이터레이터, 포인터, 레퍼런스가 무효화된다는 이야기가 같이 들어가면 더 좋을거같다.
프로세스와 스레드의 차이를 메모리 공유 관점에서 설명
스레드는 코드, 힙, 전역 데이터는 공유하고, 각자 스택,레지스터는 별도라는 점 추가하기, 컨텍스트 스위칭은 다른 프로세스에 있는 스레드로 전환될때 프로세스 간 컨텍스트 스위칭도 발생한다.
보통 그래서 컨텍스트 스위칭이 발생하면 운영체제가 TCB(스레드 제어 블록) 같은 자료구조에 레지스터 값, 현재 실행중이던 명령어 위치(프로그램 카운터), 스택 포인터 같은 스레드 상태를 변경하는데 다른 프로세스에 있는 스레드로 스위칭되면 여기에 주소 공간(페이지 테이블등)도 바꿔야 되어서 비용이 더 크다
페이지 테이블
가상 주소를 실제 RAM주소로 변환하는 표, 프로그램은 실제 RAM 위치를 직접 다루지 않고, 자기만의 가상 주소 공간을 쓴다.
프로세스 A가 보는 주소 0x1000 → 실제 RAM의 위치 X
프로세스 B가 보는 주소 0x1000 → 실제 RAM의 위치 Y
두 프로세스가 똑같이 0x1000 주소를 사용해도 각자 페이지 테이블이 달라서 서로 다른 실제 메모리에 접근한다. 이 덕분에 프로세스가 서로의 메모리를 마음대로 침범하지 못함
프로세스간 컨텍스트 스위칭시에는 '이제 프로세스B의 페이지 테이블을 기준으로 주소를 해석해'라고 바꿔야 해서 이때 TLB(주소 변환 캐시)에도 영향이 생겨 같은 프로세스내에서 컨텍스트 스위칭이 발생했을때 보다 비용이 더 발생한다.
TLB
Translation Lookaside buffer, 주소 변환 캐시, 페이지 테이블을 매 메모리 접근마다 찾아보면 느리므로 최근에 사용한 가상주소 -> 물리주소 변환 결과를 TLB에 저장해둠
CPU의 MMU(Memory Management Unit)에서 다단계 페이지 테이블을 따라 주소를 변환하고 그 변환 결과를 TLB에 캐싱
데이터 레이스와 레이스 컨디션의 차이
데이터 레이스 : 여러 스레드가 같은 메모리에 동시 접근하고, 하나 이상이 쓰기인데 동기화가 없는경우
레이스 컨디션 : 스레드들의 실행 순서에 따라 결과가 달라져 로직이 잘못되는 더 넓은 문제
데이터 레이스는 mutex나 atomic같은 동기화로 메모리 접근을 안전하게 만들어서 해결가능하고\
레이스 컨디션은 여러 스레드가 공유 데이터를 동시에 처리하면서 실행 순서에 따라 결과가 달라지는 문제, 그래서 공유 데이터를 확인하고 수정하는 구간을 뮤텍스로 잠가 한 스레드씩만 처리하게 방지함
세마포어와 뮤텍스의 차이
뮤텍스는 공유 자원에 접근하는 코드 구간을 한번에 한 스레드만 실행가능하도록 함
세마포어는 자원에 동시에 접근을 허용할 개수를 말함 즉, 카운트가3이면 최대 3개의 스레드가 해당 자원에 접근해서 동시에 사용할 수 있다는것을 말한다. 카운트가 0이면 다음 스레드는 대기한다.
프러스텀, 오클루전 컬링
프러스텀 컬링 : 카메라 밖에있는 요소들은 렌더링 하지 않음
오클루전 컬링 : 카메라 안에 있더라도 불투명한 객체에 가려진부분들은 렌더링하지 않음 -> 대신에 해당 물체가 완전하게 전부다 가져져 있어야 렌더링 하지 않음 즉, 정확하게는 가려진 부분을 렌더링하지 않는게 아니고 전체가 가려진 객체는 렌더링하지 않음
DrawCall이 무엇이고 많이 호출하면 왜 느려지는가??
DrawCall은 CPU가 GPu에 특정 메시를 특정 머티리얼과 셰이더로 그리라고 제출하는 명령, DrawCall이 많으면 CPU가 렌더 상태 설정과 명령 제출을 반복하느라 병목이 생김, 배칭이나 GPU Instancing으로 호출 수를 줄임
GPU Instancing은 같은 메시, 머티리얼을 쓰는 여러 객체를 한번의 Draw Call로 그림, 각 객체의 위치,회전,크기 같은 값만 인스턴스 데이터로 따로 넘김
배칭은 조금 더 넓은 개념으로 여러 렌더링 작업을 묶어 Draw Call을 줄이는것 전체를 말함
map과 unordered_map의 차이와 각각의 탐색 시간복잡도를 설명
map은 레드블랙트리, 키값으로 정렬unordered_map은 해시테이블, 탐색의 경우 map은 키정렬이 들어가서 O(log n)이고, unordered_map은 평균 O(1), 최악은 O(N)이다. (해시충돌)
포인터와 레퍼런스의 차이 3가지
C++에서 다운캐스팅이 필요할 때 어떤 캐스팅을 사용하고, 그 이유는 무엇인가
dynamic_cast를 사용한다.
static_cast는 타입이 실제로 맞는지 런타임에 검사하지 않아서, 잘못된 다운캐스팅이어도 컴파일은 되고 이후 정의되지 않은 동작이 생길 수 있다.
dynamic_cast는 실제 타입을 런타임에 검사, 포인터 캐스팅이 실패하면 nullptr, 레퍼런스 캐스팅이 실패하면 예외가 발생 (원본이 포인터인 경우 레퍼런스인 경우 차이가 있다)
데드락이란 무엇이고 어떻게 예방하는가?
여러 스레드가 서로 가진 락을 기다리면서 아무도 진행하지 못하는 상황, 모든곳에서 락을 잡는 순서를 똑같이 정하는것이 대표적이다.
스택 메모리, 힙 메모리
Actor의 생명주기
생성자 -> OnConstruction -> BeginPlay -> Tick반복 -> EndPlay -> GC파괴
가상 함수가 뭔지, 그리고 부모 클래스의 소멸자를 virtual로 선언해야 하는 이유는 뭔지 설명
가상 함수는 부모포인터나 레퍼런스로 호출해도, 실제 객체가 자식이면 자식에서 오버라이딩한 함수가 호출되게 하는 함수
소멸자는 Base*로 Derived객체를 delete할때 중요하다. 부모 소멸자가 virtual이 아니면 부모 소멸자만 호출되고 자식 소멸자는 호출되지 않아서 자식이 가진 자원이 누수될 수 있다.
디퍼드 렌더링
포워드 렌더링 : 물체를 그릴 때마다 그 물체에 영향을 주는 조명을 같이 계산, 벽/상자/몬스터 각각에 조명 계산이 들어갈 수 있다.
디퍼드 렌더링 : 먼저 화면에 보이는 가장 앞표면의 정보만 G-Buffer에 기록한다. (벽의 색/방향/깊이(카메라에서 얼마나 먼지)) 그 뒤 화면의 벽 픽셀에만 조명을 계산한다. 조명 100개가 있어도 물체마다 조명100개를 계산하는 대신, 최종화면의 보이는 픽셀에만 필요한 조명을 계산해서 많은 동적 조명을 다루기 편하다.
TCP의 Nagle 알고리즘
TCP가 너무 작은 데이터를 자잘하게 계속 보내지 않도록, 작은 패킷들을 모아서 한번에 보내는 기능, 네트워크 효율은 좋아지지만, 게임 입력처럼 바로 보내야 하는 작은 데이터가 잠깐 대기해서 지연이 생길 수 있다. 그런 실시간 통신에서는 Nagle을 끄기도 함
버텍스 셰이더와 픽셀 셰이더
버텍스 셰이더 : 정점 하나씩 처리한다. 3D모델 정점을 카메라 기준의 화면 위치로 변환한다. 캐릭터 뼈대 애니메이션처럼 정점 위치를 변형하는 작업도 함
픽셀 셰이더 : 삼각형 내부가 픽셀로 채워진 뒤, 픽셀 하나씩 처리한다. 텍스처/조명등을 계산해서 그 픽셀의 최종색을 정한다.
소멸자에서 throw를 외부로 던지면 안되는 이유
C++에서 예외가 발생하면 C++런타임은 예외를 처리할 catch블록을 찾기 위해 스택 되돌리기를 수행하는데 이 과정에서 현재 스택 프레임에 생성되어있던 지역객체들의 소멸자가 순차적으로 호출됨 근데 C++은 기본적으로 이미 예외처리중에 또다른 예외가 소멸자 밖으로 터지게 되면 terminate함, 그리고 C++11부터 소멸자는 기본적으로 noexcept속성이 부여되어서 밖으로 던지면 무조건 종료됨
Iterator를 쓰는 이유
list나 map처럼 인덱스를 쓰지않는 컨테이너에서도 같은 방식으로 순회하기 위함, STL알고리즘들도 이터레이터 기준으로 동작함
C++에서 메모리를 해제한 뒤 그곳에 다시 접근해 생기는 댕글링 포인터를 방지하는 방법을 두 가지 설명
스마트 포인터를 사용 : unique_ptr로 소유권을 한곳에 두고 관찰하는쪽에서는 weak_ptr을 쓰면 방지가능
RAII잘 지키기, raw pointer를 해제해야 한다면 delete하고 꼭 nullptr로 초기화해주기
몬스터 만마리가 있다면 어떤 식으로 스레드 구조를 가져가야할까, 근데 만마리가 다 플레이어 값을 변수를 건드린다면?
CPU 코어수에 맞춘 고정 크기 스레드풀을 만듬
몬스터 1만 마리를 여러 묶음으로 나눠 워커 스레드에 맡김
워커들은 체력같은 값을 읽기만 하고, 직접 수정하지 않음
각 워커는 피해10, 중독 적용 같은 결과를 자기 버퍼에 모음
작업이 끝나면 메인 스레드가 결과를 모아 플레이어에게 한 번에 적용한다.
스레드 풀
스레드를 필요할때 마다 새로 만드는게 아니고 미리 정해진 개수만큼 만들어 두고 작업큐의 일을 가져가 처리하는 구조, CPU코어수보다 스레드가 훨씬 많으면 동시에 실행되지 못하고 전환 비용만 커짐
불편하지만 수동으로 메모리 관리를 하는 이유는 뭘까?
C++은 객체가 언제 생성되고 파괴되는지 개발자가 정확히 통제할 수 있음, 게임은 예측 불가능한 GC멈춤(GC가 메모리를 정리하는 동안 게임 실행 스레드들이 잠깐 멈추는 현상)을 피해야 하므로 필요한 시점에 자원을 확실히 해제하고 메모리 풀같은 최적화를 적용하기 위해 수동 관리 또는 RAII기반 관리를 사용한다.
임계영역(크리티컬 섹션)이란?
공유 자원에 접근하거나 수정하는 코드 구간, 예시로 gold -= 100 처럼 여러 스레드가 동시에 실행하면 문제가 생기는 코드가 임계영역이고 뮤텍스로 잠가서 한번에 한 스레드만 들어가게 한다.