P.9: Don’t waste time or space
이유
C++ 프로그래머시잖아요...
알아두기
목표 (예: 개발 속도, 리소스 안전성, 테스트 단순화 등) 를 달성하기 위해서 시간과 공간을 낭비하면 안됩니다.
"효율성을 추구하는 것의 또 다른 장점은 그 과정을 통해 문제를 더 깊이 이해할 수 있다는 것입니다." - Alex Stepanov
예시 (나쁜 예)
struct X {
char ch;
int i;
string s;
char ch2;
X& operator=(const X& a);
X(const X&);
};
X waste(const char* p)
{
if (!p) throw Nullptr_error{};
int n = strlen(p);
auto buf = new char[n];
if (!buf) throw Allocation_error{};
for (int i = 0; i < n; ++i) buf[i] = p[i];
// ... 이곳에 버퍼를 가공하는 코드가 위치한다고 가정합니다 ...
X x;
x.ch = 'a';
x.s = string(n); // 문자 배열 p 만큼 x.s 공간이 추가로 필요합니다
for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i]; // buf 를 x.s 로 복사합니다
delete[] buf;
return x;
}
void driver()
{
X x = waste("Typical argument");
// ...
}
다소 작위적이게 느껴지시겠지만 실제 프로덕션 코드에서 심심치 않게(?) 볼 수 있습니다.
X 의 레이아웃은 바이트 패딩을 전혀 고려하지 않았기 때문에 개체 생성시마다 메모리가 최소 6바이트 이상 낭비될 것이며, 메모리 소유권 이전으로 충분히 데이터를 처리 가능한 상황임에도 operator=() 함수 때문에 복사를 하고 있어서 성능 저하가 발생하고 있으며, RVO (Return Value Optimization) 에 의한 이동도 보장할 수 없다는 것 역시 큰 문제입니다.
위 상황에선 p 를 그냥 쓰면 되기에 buf 는 애초에 필요가 없었고, std::string 문자열이 진짜로 필요했다면 처음부터 C스타일 문자 배열보다는 std::string 을 써서 문자열을 표현하는 것이 훨씬 나아 보입니다.
예시 (나쁜 예)
void lower(zstring s)
{
for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}
이것은 실제 프로덕션 코드에서 본 예입니다.
위 조건에서 i < strlen(s) 를 보면, 이 표현식은 루프가 반복될 때마다 매번 평가해야 하므로 성능이 낭비되고 있습니다.
tolower() 는 문자열 길이를 변경시키는 함수가 아니기 때문에 for 문 바깥에 문자열 길이를 한번만 캐싱해두고 반복할 때마다 비용이 발생하지 않게 하는 것이 충분히 가능한 상황입니다.
알아두기
사실 소소한 자원 낭비를 일일히 고려하는 것은 그다지 효율적이지 않을때가 많고, 낭비가 심각했다면 시니어 프로그래머가 쉽게 알아보고 고칠 것입니다.
그러나 코드 전체에 이런 쓰레기들이 자유롭게 쌓여 나가는 이상, 가볍게 볼수만은 없는 문제입니다.
중요한 것은 C++ 사용과 관련된 대부분의 낭비를 발생하기 전에 미리미리 제거하는 것입니다.
정리
1. 반환값을 사용하지 않는다면 애초에 함수에 넣지 않음으로써 임시개체 생성을 줄일 수 있습니다.
2. operator++ 연산자 오버로딩 같은 경우 전위형(앞에 붙이는 형태, ++num) 을 사용하면 기존 값을 보관하기 위한 임시 변수 생성을 피할 수 있습니다.
3. copy semantics (복사 생성자, 복사 연산자) 를 직접 정의하면 move semantics (이동 생성자, 이동 연산자) 의 영향력이 줄어듭니다. 이는 컴파일러가 move semantics 를 사용하지 않고 비용이 비싼 copy semantics 를 사용하게 만듭니다. 이를 피하려면 move semantics 를 항상 같이 정의하거나, 컴파일러가 자동으로 생성해주는 멤버 함수들에 어떤 것들이 있는지 정확히 이해하고 적절하게 copy 와 move semantics 를 정의해야 합니다.
참고자료
'C++' 카테고리의 다른 글
[C++ 20] std::source_location 사용하기 (1) | 2024.04.22 |
---|---|
[C++ Core Guidelines] P.10 가급적 const를 사용하자 (2) | 2024.04.21 |
[C++ Core Guidelines] P.8 자원이 새지 않도록 하자 (0) | 2024.04.17 |
[C++ 23] std::print, std::println 사용하기 (0) | 2024.04.15 |
[C++ 20] 모듈 (Module) 사용하기 (0) | 2024.04.15 |