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

주제: 일반성을 최소화해 커플링을 최소화하고, 불변속성 측면에서 최적화를 바라보기

🔖 12.3 최적화 시기와 방법

책의 내용

최적화가 필요한 이유를 찾기 위해 항상 자동 성능 테스트를 설정하고 테스트가 실패할 때만 최적화하는 것이 좋습니다. 일반적인 테스트 유형은 아래와 같습니다.

  • 벤치마크 테스트: 제한된 기한이나 간격으로 답변을 제공해야 하는 임베디드나 실시간 시스템에서 일반적

  • 부하 테스트: 처리량을 검증하며 웹이나 클라우드 기반 시스템에서 일반적

  • 성능 승인 테스트: 성능이 갑자기 저하되지 않는지 확인하며, 실행 간의 일관성이 있는 한 외부 요인과 완전히 분리

리팩토링 목표 중 하나는 불변속성을 지역화해서 더 명확하게 만드는 것입니다. 최적화는 불변속성에 의존하기 때문에 이는 잘 분해된 코드를 최적화하는 것이 더 쉽다는 것을 의미합니다.

나의 생각

최적화가 불변속성에 의존한다는 문장이 잘 이해가 되지 않아서 아래와 같이 이해하며 적어보았다.

위의 내용은 소프트웨어 리팩토링과 최적화 과정에서 불변성의 중요성을 강조한다. 복습 차 다시 한번 떠올려 보자. 불변성을 지역화한다는 것은 코드 내에서 데이터가 변경되지 않도록 관리하며, 이를 통해 코드의 명확성을 높이고 오류를 줄이며, 성능 최적화를 쉽게 할 수 있다는 의미다.

예를 들어, 상태를 불변 객체로 관리해 사용자의 프로필 정보를 관리하는 기능을 구현한다 치자. readonly 키워드를 사용해 name, age 등의 속성은 생성 후 변경될 수 없게 하며, hobbies 배열은 ReadonlyArray 타입으로 선언되어 배열 내용이 변경되지 않도록 한다.

이러한 불변성의 이점으로는 함수 외부에서 상태가 변경되지 않으므로 코드 안정성과 유지보수성이 향상된다. 따라서 성능 최적화 작업도 효과적으로 진행할 수 있다. 즉, 불변성을 가진 데이터는 참조 투명성(프로그램의 표현식이 그 표현식을 평가한 결과로 대체될 수 있음)을 보장하므로 캐싱(자주 사용되는 데이터나 계산 결과를 미리 저장해 두었다가 필요할 때 빠르게 접근할 수 있도록 하는)과 같은 최적화 기술을 적용하기 용이할 것이다.

🔖 12.3.6 최적화된 코드 분리하기

책의 내용

알고리즘, 동시성 및 캐시로 성능 테스트를 만족시키는 데 불충분한 경우는 거의 없습니다. 불충분한 경우 마이크로 최적화라고도 하는 성능 튜닝으로 전환합니다. 이때 우리는 런타임과 원하는 동작 사이에 존재하는 작은 불변속성을 이용합니다.

튜닝의 예는 매직 비트 패턴을 사용하는 것입니다. 이것들은 마벅의 숫자로 보통 16진법으로 작성되어 읽기가 훨씬 더 어렵습니다. 매직 비트 패턴은 사용된 알고리즘이 가진 미묘한 성능 차이를 충족시키는 경우가 많습니다.

참고 지식

위의 내용은 소프트웨어 개발에서 알고리즘과 동시성 관리를 통해 성능 최적화를 달성한 후에도 성능 목표를 만족하지 못할 때 적용할 수 있는 추가적인 최적화 기법인 "마이크로 최적화"에 대해 설명하고 있다. 이러한 최적화는 특히 런타임에서의 작은 변화를 이용해 성능을 개선하는 데 초점을 맞춘다. "매직 비트 패턴"을 사용하는 것은 이러한 마이크로 최적화의 한 예다.

(엄청 중요한 내용은 아닌 것 같지만 그냥 궁금해서 정리해 보자면..) 매직 비트 패턴은 주로 비트 연산을 사용하여 수행되는 계산에서 사용된다. 예를 들어, 어떤 수가 2의 제곱인지 아닌지를 매우 빠르게 확인하는 것은 비디오 게임에서 흔히 필요한 연산 중 하나라고 한다.

🔖 12.3.2 제약 이론에 따른 최적화

책의 내용

병목 지점 작업자의 다운스트림(다음 작업자)을 최적화하는 것은 다운스트림 작업자가 충분히 빠르게 입력을 받을 수 없기에 전체 성능에 영향을 미치지 않습니다. 병목 지점의 작업자를 최적화해야만 시스템 성능에 영향을 미칩니다.

병목 현상을 최적화하면 새로운 병목 현상에 맞닥뜨립니다. 다행히도 소프트웨어에는 '리소스 풀링'이라는 해결책이 있습니다. 리소스 풀링은 사용 가능한 모든 리소스를 필요할 때 사용할 수 있는 공용 풀에 배치하는 것을 의미합니다. 따라서 가능한 최대 용량이 병목 지점에 제공됩니다. 부하 분산 장치를 통해 외부적인 서비스 수준에서 또는 스레드 풀링을 통해 애플리케이션 내에서 이 접근 방식을 구현할 수 있습니다.

나의 생각

리소스 풀링은 특히 고성능이 요구되는 환경에서 매우 유용한 전략이라고 한다. 이는 필요할 때마다 리소스를 효율적으로 사용하고 재사용함으로써 시스템 전체의 성능을 향상시키는 데 도움을 준다. 예를 들어, 서버가 많은 수의 동시 요청을 처리해야 하는 경우가 있을듯 하다. DB 연결이나 스레드 사용 등 리소스에 대한 요청이 폭증하는 이럴 때, 리소스 풀링을 사용하면 리소스를 미리 생성하고 풀에 저장하여, 요청이 발생할 때마다 빠르게 리소스를 할당하고 반환할 수 있다.

리소스 풀링과 맞닿아있는 '스레드 풀' 이라는 개념도 있다. 스레드 수가 많으면 스레드 간 전환에 따른 오버헤드가 증가한다. 따라서 스레드 여러 개를 미리 생성해 두고, 스레드가 처리할 작업이 생기면 해당 스레드에 처리를 요청하는 것이다. 이렇게 하면 스레드가 미리 생성되어 있기에 스레드의 생성과 종료 작업이 빈번하게 발생하지 않는다. 여기서 중요한 것 또한 '스레드를 재사용하는 것'이다.

알아보니, 실제로 스레드 풀은 리소스 풀링 전략의 한 예로 볼 수 있다고 한다. (리소스 풀링이라는 단어는 DB 연결, 메모리 버퍼 등 더 일반적으로 쓰이는 개념)

📚 참고 자료

Last updated