항목 16 : 뼛속까지 잊지 말자, 80-20 법칙!

  • 80-20 법칙
    • 프로그램 리소스의 80%는 전체 실행 코드의 20%만 사용한다.
    • 실행 시간의 80%는 실행 코드의 20%만 소비한다.
    • 메모리의 80%는 실행 코드의 20%만 사용한다…. 등등
  • 80과 20은 숫자에 불과하다 -> 프로그램의 전체 수행 성능은 일부의 코드에 의해 좌지우지된다.

  • 실행 병목 현상을 일으키는 위치를 찾는 방법
    1. 대부분의 사람들이 하는 방법(어림짐작)
      • 어림짐작으로 코드 튜닝
      • 80-20법칙의 진짜 의미 : 아무 곳이나 골라잡고 효율을 향상시키는 것은 큰 도움이 되지 않는다.
      • 프로그램 수행 성능의 특징은 직관적으로 파악할 수 있는 성격의 것이 아니다.
    2. 20%의 코드를 판별하자(프로파일러)
      • 관심을 두고 있는 리소스를 직접 측정해주는 도구가 필요하다.
      • 최대한 많은 데이터를 사용하여 프로파일링하자!
      • 데이터는 사용자가 그 소프트웨어에 잘 입력하는 것으로 준비하자.

항목 17 : 효율 향상에 있어서 지연 평가는 충분히 고려해 볼 만하다

  • 지연 : 어떤 일을 하긴 하되 그 일을 하는 코드의 실행을 피하는 방법
  • 지연 평가를 적용할 수 있는 분야
  • ex 1) 참조 카운팅
    string s1 = "Hello"; 
    string s2 = s1; // string의 복사 생성자 호출 
    
  • 복사 생성자의 실행 비용
    1. s1값과 동일하게 데이터를 갖기 위해 new 연산자를 통한 힙 메모리를 할당한다.
    2. strcpy로 s1의 데이터를 s2의 힙 메모리로 복사한다.
  • 이렇게 복사 생성자가 호출되자마자 메모리 할당, 데이터 복사 등을 처리한다.(즉시 평가)
  • 하지만 s2가 굳이 자신의 메모리를 가질 필요가 없다. s2는 아직 사용되지 않았기 때문이다.
  • 지연 방법 적용 : s2가 s1의 값을 공유하도록 한다면
    • 이 경우 한쪽 문자열 값이 수정될 때 더 이상 지연시킬 수 없기 때문에 데이터 사본을 생성한다.
  • ex 2) 데이터 읽기와 쓰기 구분하기
    string s = "Homer's Iliad"; 
    cout << s[3]; // s[3]를 읽기 위해 operator[] 호출 
    s[3] = 'x'; // s[3]에 쓰기 위해 operator[] 호출 
    
  • operator[]는 하나인데 쓰기 / 읽기 시 다른 동작을 한다?
  • 불가능하다. 하지만(30)의 지연 평가와 프록시 클래스를 사용하면 가능

  • ex 3) 지연 방식의 데이터 가져오기
  • 어떤 오브젝트 인스턴스의 크기가 무척 클 때 데이터를 일일이 모두 읽어오지 않아도 별 상관이 없는 경우 지연 방식을 사용할 수 있다.
  • 인터페이스만 구현해 놓고 필요한 데이터만 데이터베이스에서 뽑아오게 한다.(요구 기반 방식의 객체 초기화)
  • 주의점
    • 필드에 대한 포인터는 어떤 멤버 함수에서도 실제 데이터를 가리키게 해야 한다.
    • const 멤버 함수는 보통의 클래스 데이터 멤버를 수정할 수 없다.
  • 해결 방법
    1. mutable로 선언 : 이 데이터는 어떤 멤버함수에서도 수정 가능
    2. this 흉내내기 : 비상수 객체에 대한 포인터에 this가 가리키는 걸 const_cast로 상수성을 제거하여 넣어서 사용한다.(fakeThis가 가리키는 객체는 상수객체가 아니다)
    3. 멤버 포인터로 스마트 포인터를 사용한다.
  • ex 4) 지연 방식의 표현식 평가
    Matrix<int> m1(1000, 1000); 
    Matrix<int> m2(1000, 1000); 
    Matrix<int> m3 = m1 + m2; 
    
  • operator+의 내부 동작은 즉시 평가 방식이다.
  • m3에 자료구조를 하나 마련한다. “m3의 값은 m1, m2의 합이다’라는 표시만 해주는 자료구조로, m1과 m2 요소의 포인터, 덧셈이 수행됨을 나타내는 나열자(enum)로 구성 -> 속도 및 메모리를 절약할 수 있다.
    cout<<m3[4]; // m3의 네번째 행만 계산한다. 
    m3 = m1 + m2; // m1과 m2의 합 
    m1 = m4; // 이제 m3은 이전 m1과 m2의 합 
    
  • m1 변경 전에 m3의 값을 계산해두거나 m1 이전 값을 복사해 두고 m3의 자료구조를 그 이전 값에 맞춰 업데이트한다.
  • 지연 평가는 APL(행렬 기반 계산을 대화식으로 할 수 있도록 개발된 1960년 언어)이란 언어에 뿌리를 두고 있다.
  • 계산을 바로 하지 않아도 되는 경우가 잦은 소프트웨어에 유용하다.
    • 그렇지 않으면 피하려면 계산 + 자료구조 조작 비용으로 손해
  • 인터페이스의 어느 부분에서도 즉시 평가 / 지연 평가 표시 흔적이 없다. -> 프로파일링 중 병목현상 발생 시 즉시 평가 -> 지연 평가로 수정이 쉽게 가능하다.