스터디/Five Lines of Code

Five Lines of Code 12장. 최적화 및 일반화 회피

마디니 2023. 11. 17. 23:18
반응형
이번장에서 다룰 내용
- 일반성을 최소화해서 커플링 최소화 하기
- 불변속성 측면에서 최적화 바라보기
- 최적화를 통해 취약성 관리하기

 

(성능)최적화: 코드 처리량을 늘리거나 처리 시간을 줄이기

일반화: 코드가 더 일반적인 매개변수를 통해 더 많은 기능을 포함하도록 하는 것

 

요리사에게 칼을 건네야 한다면 군용칼이 좋을까 과일 칼이 좋을까?

일반화를 수용하는 설계는 일반화 하여 얻는 이점보다 부담스러울 수 있다. 일반화 해야하는 것은 컨텍스트 뿐이다.

단순성 추구

인간의 인지능력은 제한적이므로 단순함이 필수다.

결합된 컴포넌트와 기능을 이해하기 위해 추적해야 하는 일은 인지능력이 꽤 많이 요구된다. 

주로 아래 두가지 습관 때문에 단순함에서 멀어진다.

 

1. 일반화된 기능 제공

일반화된 기능을 제공하면 그 기능의 용도가 증가하고 더 많은 것이 결합되고, 기능을 이해하기에 더 많은 에너지가 소모된다

 

2. 불변속성을 활용하는 최적화

총 량을 매번 조회하는 로직을  -> total 필드를 만들어 활용

 

일반화나 최적화를 진행할 때는 그 근거가 이어야 한다. 

단순함을 희생할 때는 부작용을 최소화 하기 위한 예방 조치가 있어야 한다.

일반화의 시기와 방법

구현의 최소화로 일반화 지양하기

"하지 않는 일의 양을 최대화 하라"
                                  -  켄트 벡

 

- 무언가를 만들기 전에 먼저 컨텍스트 (= 구현하려는 동작의 범위)를 이해하기

- 소프트웨어가 발전함에 따라 요구사항이 변경되는 경향이 있기 때문에 불필요한 일반화를 구현해도 무효화 되기 쉽다.

 

안정성이 유사한 것 통합하기

- 새로운 것과 오래된 것을 바로 통합하지 않기 - 통합 대상이 비슷한 안정석에 도달할 때까지 기다리기

 

불필요한 일반화 제거

- 정기적으로 모니터링 후 불필요한 일반화 제거하기

- 메서드 전문화, 삭제 후 컴파일하기 패턴 사용

- 함수에 전달된 런타임 인자들을 모니터링 하면 불필요한 일반화를 효과적으로 찾을 수 있다. 

최적화 시기와 방법

최적화 역시 높은 인지부하를 유발하므로 최적화를 하기전 진짜 필요한가 타당한 근거가 필요하다.

자동 성능 테스트(벤치마크 테스트, 부하 테스트, 성능 승인 테스트) 를 이용해 테스트가 실패할 때 최석화를 적용시키는 것이 좋다.

 

최적화 전 리팩터링

- 최적화 전 적절하게 리팩터링 되었는지 확인한다

- 최적화는 불변속성에 의존하기 때문에 잘 분해된 코드를 최적화 하는 것이 쉽다. 

 

컴파일러에게 맡기기

- 과도한 기교는 컴파일러가 우리가 원하는 것을 인식할 수 없게 하여,  코드를 더 느리게 실행 할 수 있다.

- 컴파일러를 활용하자!

- 컴파일러가 개선된다는 것은 우리가 관용적 코드를 작성하면 시간이 지남에 따라 코드가 자동으로 빨라진다는 것을 의미한다.

- 복잡한 코드 관리나 특이한 패턴은 지양하자.

 

제약 이론에 따른 최적화

- 코드 리팩터링 후 테스트가 여전히 만족스럽지 못하다면 최적화 진행 필요

- 스레드나 프로세스, 서비스의 협업을 통해 동시 시스템에서 작업하는 경우 제약이론(theory of constraints)이 적용된다.

순차적으로 연결된 워크스테이션으로 구성된 시스템에서는 어떤 작업이 수행되면 그 결과를 버퍼를 통해 다른 작업자게에 전달되는데, 입력에서 출력까지의 흐름에서 한 명의 병목 지점 작업자가 있다면 병목 지점 작업자 입구의 버퍼에 작업이 쌓이기 시작한다.    
이 병목 지점의 작업자를 최적화 해야 시스템 성능이 좋아진다.

 

- 병목 현상을 최적화 하면 새로운 병목 현상 발생 -> 이전 병목 현상의 증가된 처리량을 따라가지 못하거나 출력 빠르게 생성하지 못함

- 리소스 풀링을 이용해 위와 같은 문제 해결 가능

리소스 풀링
모든 리소스를 필요할 때 사용할 수 있는 공용 풀에 배치하는 것을 의미한다. 가능한 최대 용량이 병목 지점에 제공된다. 로드밸런서를 통해 외부 서비스 수준이나 애플리케이션 내 스레드 풀링을 통해 이 접근 방식을 구현할 수 있다.

 

측정 지표를 사용한 최적화

- 리소스 풀링으로 시스템을 최적화 한 후에도 성능 요구사항을 충족하지 못하면 병목 지점 내에서 최적화해야한다.

- 코드에서 핫스팟(Hot spot: 스레드가 대부분의 시간을 보내는 지점)을 식별해야 한다.

- 프로파일링 도구를 활용해 전체 시간의 80%를 소비하는 코드 구간을 찾아내야 한다.

 

* 프로파일링: 메서드에서 얼마나 많은 누적시간을 소비했는지 추척하는것

 

좋은 알고리즘과 데이터 구조 선택하기

핫스팟을 찾은 후 최적화 방법을 생각해야 한다. 

동등한 인터페이스를 가진 다른 데이터 구조로 바꾸기

- 새로운 데이터 구조를 위해 도메인 관련 코드를 변경할 필요가 없기 때문에 안전하다.

 

캐시 사용하기

캐시: 계산을 여러 번 수행하는 대신 한 번 수행하고 결과를 저장한 후 재사용 하는 것

- 캐시는 멱등 불변속성에 적용할 때 가장 안전하다. => 동일한 인자로 호출하면 항상 동일한 결과가 제공 되므로 외부에서 캐시를 수행 할 수 있다. 

-  멱등성이 일시적인 경우 캐시는 취약해진다. 예) 상품 가격 캐싱중인데 변경될 때

- 멱등성이 없어도 캐시를 수행할 수 있으나 내부적으로만 사용해야 한다.

 

최적화된 코드 분리하기

알고리즘, 동시성 및 캐시로 성능 테스트를 충분히 만족 시킬 수 있지만,

불춘분한 경우 마이크로 최적화 라고 하는 성능튜닝을 활용할 수 있다.  => 주로 복잡하고 어려운,, 쉽게 변경 할 수 없는 작업이다

 

잠금 영역 최소화를 위한 메서드와 클래스 사용

- 일반적으로 까다롭게 튜닝된 함수를 이해하지 않고서는 변경할 수 없다. 

- 따라서 튜닝된 코드를 격리하여 (작업)코드의 양을 최소화 시킨다.

- 튜닝된 코드의 경우 그것을 추출 할 때보다 더 잘 이해할 수 있는 사람은 없기 때문에 네이밍이나 문서화가 잘 만들어져야 한다.

 

향 후 개발자들에게 알리기 위한 패키지 사용

-  미래의 개발자들에게 튜닝된 코드니 자세히 다루지 말라는 사실 전달하여 이익을 얻어야 한다.

-  튜닝된 코드를 위한 전용 패키지를 사용하는 것이 좋다

    > 임포트해서 사용할 패키지가 보이지 않게 할 수 있다.

    > 튜닝된 모든 코드를 함께 모으는 것은 그 영역에 대한 다른 품질 요구 사항이 있다는 것을 나타낸다.

튜닝된 영역 = 소수의 개발자들이 예외적으로 잘 이해하는 코드를 위해 각별히 관리된 성스러운 곳이 되어야 한다.

반응형