관리 메뉴

기억을 위한 기록들

[CPP] C++ 최적화에 관하여 (1-3) 본문

C & CPP

[CPP] C++ 최적화에 관하여 (1-3)

에드윈H 2021. 7. 25. 16:04

1. 최적화란

- 더 좋은 컴파일러 사용, 최적화 설정 사용

당연히 돼있을 수도 있지만 안 돼있을 수도 있다.

 

- 최적의 알고리즘 사용

사소한것부터 큰 것까지 최적의 알고리즘은 다양하다. 이 내용을 알기엔 모든 나의 커리어를 쏟을 만큼 쉽지 않다.

 

- 더 좋은 라이브러리 사용하기

표준 C++ 템플릿 및 런타임 라이브러리는 유지보수 가능해야 하고, 범용적이고 견고해야 한다.

보통 속도를 신경을 쓰지 않기 때문에 잘 확인해 보며 써야 한다.

 

- 메모리 할당 줄이기

메모리 관리자를 호출하는 횟수를 줄이는 방법 하나만 알고 있더라도 성공적으로 최적화를 할 수 있다고 말할 수 있다.

 

- 복사 줄이기

많은 복사가 메모리 할당과 관련해 발생하기 때문에 하나를 고치면 종종 나머지 하나의 문제도 사라진다.

 

- 계산 제거

할당과 함수 호출을 제외하면 단일 C++문의 비용은 일반적으로 중요하지 않다. 그러나 루프 내의 또는 프로그램이 이벤트를 처리할 때 같은 코드를 수백만 번 실행하면 문제가 된다.

 

- 최적의 자료구조를 사용

단순 자료구조를 선택하는 것만으로도 성능에 큰 영향을 미친다.

 

- 동시성을 증가시키기

천천히 하는 것보다 여러 가지 일을 프로세서에 나눠 분배하면 더 빨라지겠지?

 

- 메모리 관리 최적화

동적 메모리 할당을 하며 필요한 기법들이 있다.

 

 

2. 컴퓨터 하드웨어와 최적화

- 메모리 접근 비용은 프로세서의 다른 비용을 압도한다.

 

- 정렬되지 않은 메모리에 접근하는 시간은 모든 바이트가 동일한 워드에 있을 때보다 2배 오래 걸립니다.

C++ 컴파일러는 구조체 내 각 필드의 시작 주소가 필드 크기의 배수인 바이트 주소가 되도록 정렬한다. 그러나 이는 사용되지 않는 데이터가 구조체에 포함되어 구멍이 되는 문제를 초래. 구조체에서 데이터 필드의 크기와 순서에 주의를 기울이자.

 

- 많이 사용하는 메모리는 적게 사용하는 메모리 위치보다 빨리 접근할 수 있습니다.

그게 캐시 메모리(Cache Memory)

 

- 인접한 위치에 있는 메모리는 인접하지 않은 위치에 있는 메모리보다 더 빨리 접근할 수 있습니다.

 

-캐싱 때문에 전체 프로그램의 콘텍스트에서 실행되는 함수는 테스트 하네스(테스트용 소프웨어와 데이터의 집합)에서 실행되는 동일한 함수보다 느리게 실행될 수 있습니다.

 

-실행 스레드 간 공유하는 데이터에 접근하는 속도는 공유하지 않는 데이터에 접근하는 것보다 훨씬 느립니다.

 

- 계산은 의사 결정보다 빠르다.

 

- 모든 프로그램은 다른 프로그램들과 컴퓨터 자원을 놓고 경쟁한다.

 

- 프로그램이 시동 시간이나 최고 부하 시간에 실행되어야 한다면, 해당 프로그램의 성능은 해당 부하 상태에서 측정해야 한다.

 

- 모든 대입문, 함수 인수 초기화, 함수 반환 문은 생성자를 호출하며, 그 생성자가 얼마나 많은 코드를 가지고 있는지 알 수 없습니다.

 

- 일부 문장은 많은 양의 계산을 숨깁니다. 문장의 형태로는 비용이 얼마나 드는지 알 수 없다.

 

- 동기화 코드는 실행에 가능한 스레드가 데이터를 공유할 때 얻을 수 있는 동시성을 줄입니다.

 

 

3. 성능 측정

 

- 성능은 반드시 측정해야 한다.

- 테스트 가능한 예측을 만들고 적어두자.

- 코드 변경 사항을 기록해두자

- 각 테스트를 문서화했다면, 빠르게 반복 수행할 수 있다.

- 프로그램의 코드가 10%가 실행시간의 90%를 소비한다.

- 정확한 측정을 위해서는 정밀하고 동시에 충실해야 한다.

- 해상도(resolution)는 정확하지 않다.

- 개발자가 큰 성능 변화를 가져오는 부분만 받아들인다면, 방법론에 크게 신경 쓸 필요가 없어진다.

- C++ 문장의 비용이 얼마나 높은지 추정하려면, 문장이 메모리를 몇 번 읽고 썼는지 세면 된다.

 

 

4. 문자열 최적화

- 문자열은 동적으로 할당되기 때문에 사용하는데 비용이 많이 든다. 또한 표현식에서 값처럼 작동하며 구현 코드에서 복사를 많이 한다.

 

- 문자열을 값이 아닌 객체로 처리하면 할당 및 복사 횟수를 줄일 수 있습니다.

값처럼 작동하지만 복사하는 비용이 큰 것을 설명하는 프로그래밍 용어 : 카피 온 라이트(Copy On Write)(COW)

 

- 문자열 공간을 예약하면 오버헤드를 줄일 수 있습니다.

string사용 시 reserve 함수를 사용하는 게 좋다.

 

- 문자열을 가리키는 const 참조를 함수에 전달하는 방법은 값을 전달하는 방법과 비슷해 보이지만 더 효율적입니다.

void DoSomething(std::string const& s);

 

- 함수의 출력용 매개변수를 참조로 전달하면 인수의 저장 공간을 재사용하므로 새로운 저장 공간을 할당하는 방법보다 훨씬 효율적입니다.

void DoSomething(std::string& output); //string을 생성해서 return 하는것보단,output변수로 참조전달을 해준다.

 

- 할당 오버헤드를 제거하는 작업도 최적화라고 할 수 있습니다.

- 다른 알고리즘을 사용하는 것은 최적화하기 쉽고 더 효율적인 방법입니다.

- 표준 라이브러리 클래스는 범용적으로 간단하게 구현해야 합니다. 반드시 성능이 뛰어나야 한다거나 특정용도에 최적되어야 할 필요는 없습니다.

 

5. 알고리즘 최적화

- 상수 시간을 갖는 알고리즘을 홍보한다면 의심해야 봐야 한다. 시간 비용이 O(n)일 수도 있다.

- 효율적인 알고리즘을 여러 개 결합해서 사용하면 전체 실행시간이 O(n^2) 이상이 될 수도 있다.

- 이진 검색은 시간비용이 O(logn)이라 성능은 뛰어나지만 가장 빠른 검색 알고리즘은 아니다. 보간 검색의 시간비용은 O(loglogn)이고 해싱의 시간 비용은 상수 시간이다.

- 항목이 4개 미만인 작은 테이블에서는 모든 검색 알고리즘이 검사하는 항목의 수가 거의 같다.

 

6. 동적 할당 변수 최적화

- 성능 관점에 보면 new는 우리의 친구가 아니다.

- 메모리 관리자의 호출 횟수를 줄이는 방법을 알고 있다면 효과적으로 최적화할 수 있다.

 

- 프로그램은 ::operator new()와 ::operator delete()의 정의를 제공해 전역 범위에서의 메모리 할당 방법을 변경할 수 있다.

 

- 프로그램은 malloc()과 free()를 대체해 전역 범위에서의 메모리 관리 방법을 변경할 수 있다.

 

- 스마트 포인터는 동적 변수의 소유권을 자동화한다.

 

- 동적 변수의 소유권을 공유하면 비용이 더 크다.

공유 포인터 

 

- 클래스 인스턴스를 정적으로 만드세요.

MyClass* myInstance = new MyClass("Hello",123);

대신하여 정적으로 생성하는 방법

MyClass myInstance("Hello",123);

 

- 클래스 멤버 변수를 정적으로 만들고 필요하다면 2단계 초기화를 사용하세요.

https://stackoverflow.com/questions/12653724/use-cases-for-two-phase-initialization

 

Use cases for two-phase initialization

I've seen a lot of two-phase initialization used. The justification is to call virtual functions from the secondary constructor. However, I've never, ever, seen any use case in which this was neces...

stackoverflow.com

 

- 동적 변수를 소유하기 위한 소유 포인터를 사용하세요. 그리고 소유권을 공유하는 대신 소유하지 않는 포인터를 사용하세요.

 

- 출력용 매개변수에 데이터를 전달하는 복사 없는 함수를 만드세요

이전과 마찬가지로 함수 안에서 생성하여 return 하지 말고 output용 매개변수를 넣자.

 

- 이동 문법을 구현하세요

C++11에 추가된 이동 문법. (&&연산자)

http://sangsangworld.blogspot.com/2015/06/c11_37.html

 

[C++11] 이동 시맨틱

기존 C++의 성능(주로 STL)을 개선하기 위해 도입된 개념입니다. vector를 예로 들자면 기존 C++의 경우 계속 항목을 추가하다 보면 공간이 부족해지고 공간을 늘리기 위해 다음 절차를 수행합니다. 1

sangsangworld.blogspot.com

- 평평한 자료구조를 더 많이 사용하세요.

일반 배열이나 vector는 나란히 있는 평평한 자료구조, 그 외 list/map 등은 노드 기반이라 평평하지 않음.