코드 최적화: 최적화를 위한 최적의 방법
게시 됨: 2022-03-11성능 최적화는 코드에 대한 가장 큰 위협 중 하나입니다.
당신은 다른 사람이 아니라 생각 하고 있을지도 모릅니다. 이해 했어요. 어떤 종류의 최적화도 어원으로 볼 때 분명히 좋은 것이므로 자연스럽게 잘하고 싶어집니다.
더 나은 개발자로서 군중과 차별화되는 것이 아닙니다. Daily WTF 에서 "Dan"이 되는 것을 피하기 위해서가 아니라 코드 최적화가 올바른 일이라고 믿기 때문입니다. 당신은 당신의 일에 자부심을 가지고 있습니다.
컴퓨터 하드웨어는 계속해서 빨라지고 소프트웨어는 더 쉽게 만들 수 있지만, 당신이 할 수 있기를 원하는 단순한 일이라도 Dammit 은 항상 마지막 것보다 더 오래 걸립니다. 당신은 이 현상(부수적으로 Wirth의 법칙으로 알려짐)에 머리를 흔들고 그 추세를 거부하기로 결심합니다.
그것은 당신의 고귀한 하지만 그만.
그냥 멈춰!
프로그래밍 경험이 아무리 많아도 자신의 목표를 좌절시킬 가장 큰 위험에 처해 있습니다.
어때요? 백업합시다.
먼저 코드 최적화 란 무엇입니까?
종종 코드를 정의할 때 코드가 더 잘 수행 되기를 원한다고 가정합니다. 코드 최적화는 프로그램이 가능한 최소 메모리 또는 디스크 공간을 사용하고, CPU 시간 또는 네트워크 대역폭을 최소화하거나, 추가 코어를 최대한 활용하도록 코드를 작성하거나 다시 작성하는 것이라고 말합니다.
실제로 우리는 때때로 다른 정의를 기본값으로 사용합니다: 적은 코드 작성.
그러나 그 목표를 가지고 작성하는 선제적으로 나쁜 코드는 누군가의 편에 가시가 될 가능성이 훨씬 더 높습니다. 누구의? 당신의 코드를 이해해야 하는 다음으로 불행한 사람은 당신 자신일 수도 있습니다. 그리고 당신처럼 똑똑하고 유능한 사람은 자기 파괴 행위를 피할 수 있습니다. 목적을 고상하게 유지하되 의심할 여지 없이 직관적인 것처럼 보이지만 수단을 재평가하십시오.
따라서 코드 최적화는 다소 모호한 용어입니다. 그것은 우리가 아래에서 코드를 최적화할 수 있는 다른 방법을 고려하기 전에도 있습니다.
Jackson의 유명한 코드 최적화 규칙을 함께 탐색하면서 현자의 조언을 들어보는 것으로 시작하겠습니다.
- 하지마
- (전문가 전용!) 아직 하지 마세요.
1. Don't Do It: 완벽주의 채널링
나는 오래전에 SQL의 멋진 세계에 발을 담그고 있었을 때의 다소 당황스러울 정도로 극단적인 예부터 시작하겠습니다. 문제는 케이크를 밟았을 때 축축하고 발 냄새가 나기 시작했기 때문에 더 이상 먹고 싶지 않았다는 것입니다.
케이크도 먹고 너무 먹고 싶은 SQL의 멋진 세계에 발을 담그고 있었습니다. 문제는 내가 케이크를 밟았다는 것...
기다리다. 내가 방금 만들고 설명했던 은유의 이 자동차 사고에서 다시 돌아가겠습니다.
나는 인트라넷 앱을 위한 R&D를 하고 있었는데, 언젠가 내가 일하는 중소기업을 위한 완전히 통합된 관리 시스템이 되기를 바랐습니다. 모든 것을 추적하고 당시의 시스템과 달리 데이터를 잃지 않을 것입니다. 다른 개발자가 사용한 얇은 집에서 만든 플랫 파일이 아니라 RDBMS가 지원하기 때문입니다. 백지 상태였기 때문에 처음부터 모든 것을 최대한 스마트하게 디자인하고 싶었습니다. 이 시스템에 대한 아이디어가 내 머릿속에서 불꽃놀이처럼 폭발하고 있었고 CRM, 회계 모듈, 인벤토리, 구매, CMS 및 프로젝트 관리에 대한 연락처 및 컨텍스트 변형 테이블을 디자인하기 시작했습니다.
최적화 때문에 개발 및 성능 면에서 모든 것이 중단되었습니다.
나는 객체(테이블 행으로 표시됨)가 현실 세계에서 서로 많은 다른 관계를 가질 수 있고 이러한 관계를 추적함으로써 이점을 얻을 수 있음을 확인했습니다. 우리는 더 많은 정보를 보유하고 결국 모든 곳에서 비즈니스 분석을 자동화할 수 있습니다. 이것을 엔지니어링 문제로 보고 시스템 유연성을 최적화하는 것처럼 보이는 작업을 수행했습니다.
이때 손바닥에 상처가 나더라도 책임지지 않으니 얼굴을 잘 살펴보는 것이 중요합니다. 준비가 된? 나는 두 개의 테이블을 만들었습니다. relationship 와 하나는 relationship_type 에 대한 외래 키 참조가 있습니다. relationship 는 전체 데이터베이스의 임의의 두 행을 참조할 수 있으며 이들 사이의 관계 특성을 설명할 수 있습니다.
오, 이런. 그 유연성 을 너무 끔찍 하게 최적화 했습니다 .
너무 많이, 사실. 이제 새로운 문제가 생겼습니다. 주어진 relationship_type _유형은 주어진 모든 행 조합 간에 자연스럽게 의미가 없습니다. 어떤 person 이 company 에 employed by 관계가 있다는 것은 의미가 있을 수 있지만, 이는 두 document 사이의 관계와 의미상 동일할 수 없습니다.
알겠습니다. 문제 없습니다. 이 관계를 적용할 수 있는 테이블을 지정하는 두 개의 열을 relationship_type _유형에 추가하기만 하면 됩니다. (이 두 열을 relationship_type.id 를 참조하는 새 테이블로 이동하여 이를 정규화하는 것에 대해 생각했다면 여기에서 보너스가 주어집니다. 따라서 두 개 이상의 테이블 쌍에 의미론적으로 적용될 수 있는 관계는 테이블 이름이 중복되지 않도록 합니다. 결국, 테이블 이름을 변경해야 하고 적용 가능한 모든 행에서 업데이트하는 것을 잊었다면 버그가 생성될 수 있었습니다! 돌이켜보면 최소한 버그가 내 두개골에 서식하는 거미를 위한 음식을 제공했을 것입니다.)
고맙게도 나는 이 길을 너무 멀리 여행하기 전에 단서 스틱 폭풍에 의식을 잃었습니다. 내가 깨어났을 때 나는 RDBMS의 내부 외래 키 관련 테이블을 자체적으로 거의 다시 구현할 수 있었다는 것을 깨달았습니다. 보통 나는 “나는 너무 메타야.”라고 허겁지겁 선언하는 것으로 끝나는 순간을 즐깁니다. 하지만 불행히도 이것은 그 중 하나가 아닙니다. 확장 실패는 잊어버리세요. 이 디자인의 끔찍할 정도로 팽창은 아직 테스트 데이터로 DB가 거의 채워지지 않은 내 여전히 간단한 앱의 백엔드를 거의 사용할 수 없게 만들었습니다.
잠시 뒤로 물러나서 여기에서 사용되는 많은 측정항목 중 두 가지를 살펴보겠습니다. 하나는 내가 명시한 목표였던 유연성입니다. 이 경우 내 최적화는 본질적으로 아키텍처이기 때문에 시기상조롭지 않았습니다.
(최근에 출판된 기사인 조기 최적화의 저주를 피하는 방법에서 더 자세히 알아볼 것입니다.) 그럼에도 불구하고 내 솔루션은 너무 유연해서 눈에 띄게 실패했습니다. 다른 측정 기준인 확장성은 아직 고려조차 하지 않았지만 최소한 부수적인 피해로 극적 으로 파괴하는 데 성공했습니다.
바로 "오."
이것은 최적화가 어떻게 완전히 잘못될 수 있는지에 대한 강력한 교훈이었습니다. 나의 완벽주의는 완전히 무너졌다. 나의 영리함은 내가 지금까지 만든 것 중 가장 객관적으로 영리하지 못한 해결책 중 하나를 만들어내도록 이끌었다.
코드가 아닌 습관을 최적화하십시오
프로토타입과 테스트 스위트의 정확성을 입증하기도 전에 리팩토링하는 경향이 있으므로 이 충동을 전달할 수 있는 다른 곳을 고려하십시오. 스도쿠와 멘사는 훌륭하지만 실제로 프로젝트에 직접적으로 도움이 되는 것이 더 나을 수도 있습니다.
- 보안
- 런타임 안정성
- 선명도와 스타일
- 코딩 효율성
- 테스트 효과
- 프로파일링
- 귀하의 툴킷/DE
- DRY(반복하지마세요)
그러나 주의하십시오. 이 중 특정 항목을 최적화하면 다른 항목이 희생될 수 있습니다. 최소한 시간이 소요됩니다.
여기에서 코드 작성에 얼마나 많은 예술이 있는지 쉽게 알 수 있습니다. 위의 항목 중 하나에 대해 너무 많거나 너무 적은 것이 잘못된 선택으로 생각되었다는 이야기를 할 수 있습니다. 여기서 누가 생각을 하고 있는지도 맥락의 중요한 부분입니다.
예를 들어, DRY와 관련하여: 한 직업에서 나는 적어도 80%가 중복된 문장인 코드베이스를 상속받았습니다. 그 작성자가 함수를 작성하는 방법과 시기를 분명히 몰랐기 때문입니다. 코드의 나머지 20%는 혼란스러울 정도로 자기 유사했습니다.
나는 그것에 몇 가지 기능을 추가하는 임무를 맡았습니다. 이러한 기능 중 하나는 구현될 모든 코드에서 반복되어야 하며, 새로운 기능을 사용하려면 향후 코드를 주의 깊게 복사 해야 합니다.
분명히 내 정신(높은 가치)과 미래의 개발자를 위해 리팩토링해야 했습니다. 그러나 저는 코드베이스가 처음이었기 때문에 리팩토링으로 인해 회귀가 발생하지 않았는지 확인하기 위해 먼저 테스트를 작성했습니다. 사실, 그들은 그렇게 했습니다. 나는 스크립트가 생성한 모든 gobbledygook 출력 중에서 눈치채지 못했을 두 가지 버그를 발견했습니다.
결론적으로는 잘 했다고 생각했다. 리팩토링 후 나는 몇 줄의 간단한 코드로 어려운 기능으로 간주되었던 기능을 구현해 내 상사에게 깊은 인상을 남겼습니다. 게다가 코드는 전반적으로 훨씬 더 성능이 뛰어났습니다. 하지만 얼마 지나지 않아 같은 상사가 내가 너무 느리고 프로젝트가 이미 끝났어야 한다고 말했습니다. 번역: 코딩 효율성이 더 높은 우선 순위였습니다.
주의: 특정 [측면]을 최적화하는 것은 다른 측면을 희생하게 될 것입니다. 최소한 시간이 소요됩니다.
비록 그 당시 상사가 코드 최적화를 직접적으로 평가하지 않았더라도 나는 여전히 거기에서 올바른 길을 택했다고 생각합니다. 리팩토링과 테스트가 없었다면 실제로 수정하는 데 시간이 더 오래 걸렸을 것입니다. 즉, 코딩 속도에 초점을 맞추면 실제로 방해가 되었을 것입니다. (이봐, 그게 우리의 테마야!)
이것을 제가 소규모 프로젝트에서 수행한 일부 작업과 대조해 보십시오. 프로젝트에서 나는 새로운 템플릿 엔진을 시도하고 있었고 새로운 템플릿 엔진을 시도하는 것이 프로젝트의 최종 목표가 아니더라도 처음부터 좋은 습관을 들이고 싶었습니다.
내가 추가한 몇 개의 블록이 서로 매우 유사하다는 것을 알게 되자마자 각 블록은 동일한 변수를 세 번 참조해야 한다는 것을 알게 되자마자 머리에서 DRY 벨이 울렸고, 나는 오른쪽을 찾기 시작했습니다. 이 템플릿 엔진으로 하려고 했던 작업을 수행하는 방법입니다.
몇 시간 동안 아무 성과도 없는 디버깅을 한 후, 이것은 내가 상상했던 방식으로 템플릿 엔진으로 현재 불가능하다는 것이 밝혀졌습니다. 완벽한 DRY 솔루션이 없었을 뿐만 아니라; DRY 솔루션은 전혀 없었 습니다!
이 하나의 가치를 최적화하려고 노력하면서 코딩 효율성과 행복을 완전히 탈선시켰습니다. 왜냐하면 이 우회로 인해 그날 내가 가질 수 있었던 진행 상황이 내 프로젝트에 손실을 입혔기 때문입니다.
그때도 내가 완전히 틀렸습니까? 때로는 특히 새로운 기술 상황에서 나중에가 아니라 더 일찍 모범 사례를 알아보기 위해 약간의 투자 가치가 있습니다. 다시 작성해야 할 코드와 실행 취소해야 할 나쁜 습관이 적습니다.
아니요, 이전 일화에서 내 태도와 완전히 대조적으로 내 코드에서 반복을 줄이는 방법을 찾는 것조차 현명하지 못했다고 생각합니다. 그 이유는 맥락이 전부이기 때문입니다. 저는 장기적으로 안주하지 않고 소규모 놀이 프로젝트에서 새로운 기술을 탐구하고 있었습니다. 몇 줄의 추가 라인과 반복은 누구에게도 해를 끼치 지 않을 것이지만 초점 상실은 나와 내 프로젝트에 해를 끼쳤습니다.
잠깐, 모범 사례를 찾는 것이 나쁜 습관이 될 수 있습니까? 때때로. 내 주요 목표가 새 엔진을 배우는 것 또는 일반적으로 배우는 것이라면 Tinkering, 한계 찾기, 연구를 통해 관련 없는 기능 및 문제 발견하기와 같이 시간을 잘 보냈을 것입니다. 그러나 이것이 나의 주요 목표가 아니며 비용이 든다는 사실을 잊었습니다.
내가 말했듯이 예술입니다. 그리고 그 예술의 발전은 하지 마십시오 . 그것은 적어도 당신이 일할 때 어떤 가치가 작용하는지, 그리고 당신의 맥락에서 어떤 가치가 당신 에게 가장 중요한지를 고려하게 합니다.
그 두 번째 규칙은 어떻습니까? 언제 실제로 최적화할 수 있습니까?
2. Don't Do It yet : 누군가 이미 해본 적이 있다
자, 여러분이 하든 다른 사람이 하든 간에 아키텍처가 이미 설정되었고 데이터 흐름이 고려되고 문서화되었으며 코딩할 시간입니다.
Don't do it yet 한 단계 더 나아가 Don't even code it yet (아직 코딩하지 마십시오.
이것은 그 자체로 시기상조 최적화의 냄새가 날 수 있지만 중요한 예외입니다. 왜요? 두려운 NIHS 또는 "Not Invented Here" 신드롬을 피하려면 우선 순위에 코드 성능과 개발 시간 최소화가 포함된다고 가정합니다. 그렇지 않은 경우 목표가 완전히 학습 지향적이라면 이 다음 섹션을 건너뛸 수 있습니다.
사람들이 순전히 오만함에서 사각형 바퀴를 재발명하는 것은 가능하지만, 나는 당신과 나처럼 정직하고 겸손한 사람들이 우리가 사용할 수 있는 모든 옵션을 알지 못함으로써 이러한 실수를 저지를 수 있다고 믿습니다. 스택에 있는 모든 API 및 도구의 모든 옵션을 알고 성장 및 발전할 때 이를 파악하는 것은 확실히 많은 작업입니다.
그러나 이 시간을 투자하는 것은 당신을 전문가로 만들고 날짜-시간 계산기 또는 문자열 조작기에 대한 그들의 매혹적인 해석으로 인해 남겨진 황폐의 흔적에 대해 저주와 조롱을 받는 CodeSOD의 1000만 번째 사람이 되는 것을 방지합니다.
(이 일반적인 패턴에 대한 좋은 반대는 이전 Java Calendar API이지만 이후 수정되었습니다.)
표준 라이브러리 확인, 프레임워크의 생태계 확인, 이미 문제를 해결하는 FOSS 확인
당신이 다루고 있는 개념은 꽤 표준적이고 잘 알려진 이름을 가지고 있기 때문에 빠른 인터넷 검색으로 많은 시간을 절약할 수 있습니다.
예를 들어, 저는 최근 보드 게임의 AI 전략에 대한 분석을 준비하고 있었습니다. 어느 날 아침 나는 내가 기억하고 있는 특정 조합 개념을 사용하기만 하면 계획하고 있던 분석을 훨씬 더 효율적으로 수행할 수 있다는 것을 깨달았습니다. 지금은 이 개념에 대한 알고리즘을 스스로 알아내는 데 관심이 없었기 때문에 검색할 올바른 이름을 알고 있는 것으로 이미 앞서 있었습니다. 그러나 약 50분 동안 조사하고 일부 예비 코드를 시도한 후에도 발견한 반쯤 완성된 의사 코드를 올바른 구현으로 바꾸지 못했다는 것을 알게 되었습니다. (저자가 잘못된 알고리즘 출력을 가정하고 가정과 일치하도록 알고리즘을 잘못 구현하고 댓글 작성자가 이것을 지적한 다음 몇 년이 지난 후에도 여전히 수정되지 않은 블로그 게시물이 있다는 것을 믿을 수 있습니까?) 그 시점에서 내 모닝 티 시작하고 [name of concept] [my programming language] 를 검색했습니다. 30초 후, 나는 GitHub에서 증명할 수 있는 올바른 코드를 얻었고 내가 실제로 하고 싶었던 일을 하고 있었습니다. 내가 직접 구현해야 한다고 가정하는 대신 특정 언어를 포함하고 포함하는 것이 모든 것을 의미했습니다.
데이터 구조를 설계하고 알고리즘을 구현할 시간
...다시 말하지만 코드 골프를 치지 마십시오. 실제 프로젝트에서 정확성과 명확성을 우선시하십시오.
자, 살펴보았고 이미 문제를 해결하는 도구는 도구 체인에 내장되어 있거나 웹에서 자유롭게 사용이 허가된 것이 없습니다. 당신은 당신의 자신을 롤아웃합니다.
문제 없어요. 조언은 다음과 같은 순서로 간단합니다.
- 초보 프로그래머에게 설명하기 쉽도록 설계하십시오.
- 해당 설계에 의해 생성된 기대에 맞는 테스트를 작성하십시오.
- 초보 프로그래머가 코드에서 디자인을 쉽게 얻을 수 있도록 코드를 작성하십시오.
간단하지만 따라하기 어려울 수 있습니다. 여기에서 코딩 습관과 코드 냄새, 예술과 공예, 우아함이 작용합니다. 이 시점에서 하는 일에는 분명히 공학적 측면이 있지만 다시 한 번 코드 골프를 하지 마십시오. 실제 프로젝트에서 정확성과 명확성을 우선시하십시오.
비디오가 마음에 든다면 위의 단계를 어느 정도 수행하는 사람 중 한 명입니다. 비디오를 싫어하는 사람들을 위해 요약하자면 다음과 같습니다. Google 면접에서 알고리즘 코딩 테스트입니다. 인터뷰 대상자는 먼저 의사소통하기 쉬운 방식으로 알고리즘을 설계합니다. 코드를 작성하기 전에 작업 설계에서 예상되는 출력의 예가 있습니다. 그러면 코드가 자연스럽게 따라옵니다.
테스트 자체에 관해서는 일부 서클에서 테스트 주도 개발이 논쟁의 여지가 있다는 것을 알고 있습니다. 개발 시간을 희생할 정도로 종교적으로 과도하게 추구할 수 있기 때문이라고 생각합니다. (다시 말하지만, 처음부터 변수 하나라도 너무 많이 최적화하려고 하다 보면 발이 묶인다.) Kent Beck조차도 TDD를 그렇게 극단적으로 받아들이지 않았고, 그는 익스트림 프로그래밍을 발명하고 TDD에 관한 책을 썼다. 따라서 출력이 올바른지 확인하기 위해 간단한 것으로 시작하십시오. 어쨌든 코딩 후에 수동으로 하게 되겠죠? (당신이 록스타 프로그래머로서 코드를 처음 작성한 후 실행조차 하지 않는다면 사과드립니다. 그런 경우 에는 코드의 미래 유지 관리자에게 테스트를 맡기는 것이 좋습니다. 멋진 구현을 중단하십시오.) 따라서 테스트를 통해 수동으로 시각적 비교를 수행하는 대신 이미 컴퓨터가 해당 작업을 수행하도록 하고 있습니다.
알고리즘과 데이터 구조를 구현하는 다소 기계적인 프로세스 중에는 라인별 최적화를 피하고 사용자 정의 저수준 언어 extern을 사용할 생각 조차 하지 마십시오(C로 코딩하는 경우 어셈블리, 이 시점에서 Perl 등으로 코딩하고 있습니다. 이유는 간단합니다. 알고리즘이 완전히 대체되고 나중에 필요한지 여부를 프로세스에서 알 수 없는 경우 낮은 수준의 최적화 노력은 결국 효과가 없습니다.
ECMAScript 예제
우수한 커뮤니티 코드 리뷰 사이트 exercism.io에서 저는 최근에 중복 제거 또는 명확성을 위해 최적화를 시도하도록 명시적으로 제안한 연습을 찾았습니다. 중복 제거를 위해 최적화했는데, 위에서 언급한 것처럼 DRY(그렇지 않으면 유익한 코딩 사고 방식)를 너무 멀리 가져갈 경우 일이 얼마나 터무니없게 될 수 있는지 보여주기 위한 것입니다. 내 코드는 다음과 같습니다.
const zeroPhrase = "No more"; const wallPhrase = " on the wall"; const standardizeNumber = number => { if (number === 0) { return zeroPhrase; } return '' + number; } const bottlePhrase = number => { const possibleS = (number === 1) ? '' : 's'; return standardizeNumber(number) + " bottle" + possibleS + " of beer"; } export default class Beer { static verse(number) { const nextNumber = (number === 0) ? 99 : (number - 1); const thisBottlePhrase = bottlePhrase(number); const nextBottlePhrase = bottlePhrase(nextNumber); let phrase = thisBottlePhrase + wallPhrase + ", " + thisBottlePhrase.toLowerCase() + ".\n"; if (number === 0) { phrase += "Go to the store and buy some more"; } else { const bottleReference = (number === 1) ? "it" : "one"; phrase += "Take " + bottleReference + " down and pass it around"; } return phrase + ", " + nextBottlePhrase.toLowerCase() + wallPhrase + ".\n"; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('\n'); } } 문자열이 거의 중복되지 않습니다! 이런 식으로 작성하여 맥주 노래에 대한 텍스트 압축 형식을 수동으로 구현했습니다(그러나 맥주 노래 에만 해당 ). 정확히 어떤 이점이 있었나요? 음, 병 대신 캔으로 맥주를 마시는 것에 대해 노래하고 싶다고 가정해 봅시다. bottle 의 단일 인스턴스 를 can 으로 변경하여 이 작업을 수행할 수 있습니다.
멋진!
…오른쪽?
아니요. 그러면 모든 테스트가 중단되기 때문입니다. 좋아요, 수정하기 쉽습니다. 단위 테스트 사양에서 bottle 을 검색하고 교체하기만 하면 됩니다. 그리고 그것은 처음에 코드 자체에 그렇게 하는 것만큼 쉽고 의도치 않게 문제를 깨뜨릴 위험이 있습니다.
한편, 내 변수는 나중에 병과 전혀 관련이 없는 bottlePhrase 와 같은 것으로 이상하게 이름이 지정됩니다. 이것을 피하는 유일한 방법은 변경 유형을 정확히 예측하고 변수 이름에서 bottle 대신 vessel 또는 container 와 같은 보다 일반적인 용어를 사용하는 것입니다.
이런 식으로 미래를 대비하는 지혜는 꽤 의심스럽습니다. 당신이 무언가를 바꾸고 싶어할 확률은 얼마입니까? 그리고 그렇게 하면 변경한 것이 그렇게 편리하게 작동할까요? bottlePhrase 예에서 복수형이 2개 이상인 언어로 현지화하려면 어떻게 해야 합니까? 맞습니다. 시간을 리팩터링하면 나중에 코드가 더 나빠 보일 수 있습니다.
그러나 요구 사항 이 변경되고 단지 예상하려고 하는 것이 아닌 경우 리팩토링해야 할 때일 수 있습니다. 아니면 계속 미룰 수도 있습니다. 현실적으로 얼마나 많은 선박 유형이나 현지화를 추가할 예정입니까? 어쨌든 중복 제거와 명확성의 균형을 맞춰야 할 때 Katrina Owen의 이 데모를 시청하는 것이 좋습니다.
내 자신의 추한 예로 돌아가서: 말할 필요도 없이 중복 제거의 이점은 여기에서 그다지 실현되지 않고 있습니다. 한편 비용은 얼마였습니까?
처음에 작성하는 데 시간이 더 오래 걸리는 것 외에도 이제 읽기, 디버그 및 유지 관리가 훨씬 덜 간단합니다. 적당한 양의 복제가 허용되는 가독성 수준을 상상해 보십시오. 예를 들어, 4개의 절 변형을 각각 철자하는 것입니다.
하지만 아직 최적화되지 않았습니다!
이제 알고리즘이 구현되고 출력이 올바른 것으로 입증되었습니다. 축하합니다! 기준선이 있습니다!
마지막으로...최적화할 시간입니다. 맞죠? 아니오, 아직 하지 마십시오 . 기준선을 잡고 좋은 벤치마크 를 수행할 때입니다. 이에 대한 기대치에 대한 임계값을 설정하고 테스트 스위트에 고정하십시오. 그런 다음 무언가가 갑자기 이 코드를 느리게 만드는 경우(여전히 작동하더라도) 밖으로 나가기 전에 알 수 있습니다.
관련 사용자 경험 전체가 구현될 때까지 최적화를 보류하세요. 그 시점까지는 필요와 완전히 다른 코드 부분을 대상으로 할 수 있습니다.
앱(또는 구성요소)을 완료하세요. 아직 완료하지 않았다면 진행하면서 모든 알고리즘 벤치마크 기준을 설정하세요.
이 작업이 완료되면 시스템의 가장 일반적인 실제 사용 시나리오를 다루는 종단 간 테스트를 만들고 벤치마킹할 좋은 시간입니다.
아마도 모든 것이 괜찮다는 것을 알게 될 것입니다.
또는 실제 상황에서 무언가가 너무 느리거나 너무 많은 메모리를 사용한다고 판단했을 수도 있습니다.
이제 최적화할 수 있습니다.
그것에 대해 객관적인 방법은 하나뿐입니다. 이제 플레임 그래프 및 기타 프로파일링 도구를 분리할 때입니다. 숙련된 엔지니어는 초보자보다 더 자주 추측할 수도 있고 그렇지 않을 수도 있지만 그게 요점이 아닙니다. 확실히 알 수 있는 유일한 방법은 프로파일링하는 것입니다. 이것은 성능을 위해 코드를 최적화하는 과정에서 항상 가장 먼저 해야 할 일입니다.
주어진 종단 간 테스트 중에 프로필을 작성하여 실제로 가장 큰 영향을 미치는 항목을 파악할 수 있습니다. (그리고 나중에 배포한 후 사용 패턴을 모니터링하는 것은 시스템의 어떤 측면이 미래에 측정할 가장 관련성이 높은지 파악하는 좋은 방법입니다.)
프로파일러를 최대한 사용하려고 하는 것이 아닙니다. 일반적으로 명령문 수준의 프로파일링보다 기능 수준의 프로파일링을 더 찾고 있습니다. 왜냐하면 이 시점에서 목표는 단지 병목 현상이 있는 알고리즘 을 찾는 것뿐이기 때문입니다. .
프로파일링을 사용하여 시스템의 병목 현상을 식별했으므로 이제 최적화를 수행할 가치가 있다는 확신을 갖고 실제로 최적화를 시도할 수 있습니다. 또한 그 과정에서 수행한 기준 벤치마크 덕분에 시도가 얼마나 효과적(또는 비효율적)이었는지 증명할 수 있습니다.
전반적인 기술
첫째, 가능한 오래 높은 수준을 유지하는 것을 기억하십시오.
알고 계셨나요? 궁극적인 범용 최적화 트릭은 모든 경우에 적용됩니다.
— Lars Doucet(@larsiusprime) 2017년 3월 30일
- 더 적게 그리기
- 더 적은 항목 업데이트
전체 알고리즘 수준에서 한 가지 기술은 강도 감소입니다. 그러나 루프를 수식으로 줄이는 경우 주석을 남기는 데 유의하십시오. 모든 사람이 모든 조합 공식을 알고 기억하는 것은 아닙니다. 또한 수학 사용에 주의하십시오. 때로는 근력 감소라고 생각하는 것이 결국에는 그렇지 않습니다. 예를 들어, x * (y + z) 에 명확한 알고리즘 의미가 있다고 가정해 보겠습니다. 어떤 이유에서든 비슷한 용어를 자동으로 그룹 해제하도록 두뇌가 훈련된 경우 이를 x * y + x * z 로 다시 쓰고 싶을 수 있습니다. 우선, 이것은 독자와 거기에 있었던 명확한 알고리즘 의미 사이에 장벽을 만듭니다. (더 나쁜 것은, 이제 추가 곱셈 연산이 필요하기 때문에 실제로 덜 효율적입니다. 루프를 풀면 바지가 뭉쳐지는 것과 같습니다.) 어쨌든, 당신의 의도에 대한 빠른 메모는 먼 길을 갈 것이며 당신이 당신의 생각을 보는 데 도움이 될 수도 있습니다. 당신이 그것을 커밋하기 전에 자신의 오류.
공식을 사용하거나 루프 기반 알고리즘을 다른 루프 기반 알고리즘으로 교체하는 경우 차이를 측정할 준비가 된 것입니다.
그러나 단순히 데이터 구조를 변경하면 더 나은 성능을 얻을 수 있습니다. 사용 중인 구조 및 대안에 대해 수행해야 하는 다양한 작업 간의 성능 차이에 대해 교육합니다. 해시가 컨텍스트 내에서 작동하기에는 조금 더 지저분해 보일 수 있지만 어레이보다 우수한 검색 시간이 그만한 가치가 있습니까? 이는 귀하가 결정해야 할 절충안의 유형입니다.
편의 기능을 호출할 때 어떤 알고리즘이 사용자를 대신하여 실행되고 있는지 아는 것으로 요약된다는 것을 알 수 있습니다. 결국 힘 감소와 같은 것입니다. 공급업체의 라이브러리가 배후에서 무엇을 하고 있는지 아는 것은 성능뿐만 아니라 의도하지 않은 버그를 방지하는 데도 중요합니다.
미세 최적화
네, 시스템의 기능은 끝났지만 UX의 관점에서 보면 성능이 조금 더 미세하게 조정될 수 있습니다. 더 높은 곳에서 할 수 있는 모든 작업을 수행했다고 가정하고 지금까지 전체 시간 동안 피했던 최적화를 고려할 때입니다. 이 수준의 최적화는 여전히 명확성과 유지 관리 가능성에 대한 절충안이기 때문에 고려하십시오. 그러나 때가 되었다고 결정했으므로 이제 실제로 중요한 전체 시스템 컨텍스트 내에 있으므로 명령문 수준 프로파일링을 진행하십시오.
사용하는 라이브러리와 마찬가지로 컴파일러 또는 인터프리터 수준에서 사용자의 이익을 위해 수많은 엔지니어링 시간을 투자했습니다. (결국 컴파일러 최적화와 코드 생성은 그 자체로 엄청난 주제입니다.) 이것은 프로세서 수준에서도 마찬가지입니다. 가장 낮은 수준에서 무슨 일이 일어나고 있는지 알지 못한 채 코드를 최적화하려고 하는 것은 4륜구동이 차량이 더 쉽게 멈출 수 있다는 것을 의미한다고 생각하는 것과 같습니다.
기술 스택과 프로파일러가 가리키는 대상에 따라 다르기 때문에 그 이상의 일반적인 조언을 제공하는 것은 어렵습니다. 그러나 측정하고 있기 때문에 솔루션이 문제 컨텍스트에서 유기적이고 직관적으로 제시되지 않는 경우 이미 도움을 요청할 수 있는 훌륭한 위치에 있습니다. (수면과 다른 것에 대해 생각하는 시간도 도움이 될 수 있습니다.)
이 시점에서 컨텍스트 및 확장 요구 사항에 따라 Jeff Atwood는 개발자 시간보다 저렴할 수 있는 단순히 하드웨어 추가를 제안할 것입니다.
아마도 당신은 그 길을 가지 않을 것입니다. 이 경우 다양한 범주의 코드 최적화 기술을 탐색하는 것이 도움이 될 수 있습니다.
- 캐싱
- 비트 핵 및 64비트 환경에만 해당되는 해킹
- 루프 최적화
- 메모리 계층 최적화
더 구체적으로:
- C 및 C++의 코드 최적화 팁
- Java의 코드 최적화 팁
- .NET에서 CPU 사용량 최적화
- ASP.NET 웹 팜 캐싱
- SQL 데이터베이스 튜닝 또는 특히 Microsoft SQL Server 튜닝
- 스케일링 스칼라의 플레이! 뼈대
- 고급 WordPress 성능 최적화
- JavaScript 프로토타입 및 범위 체인을 사용한 코드 최적화
- React 성능 최적화
- iOS 애니메이션 효율성
- Android 성능 팁
어쨌든, 나는 당신 을 위해 몇 가지 더 하지 말아야 할 것이 있습니다:
다양한 목적을 위해 변수를 재사용하지 마십시오. 유지 보수 측면에서 이것은 오일 없이 자동차를 달리는 것과 같습니다. 가장 극단적인 임베디드 상황에서만 이것이 의미가 있었고 그러한 경우에도 더 이상 의미가 없다고 주장합니다. 이것은 구성하는 컴파일러의 작업입니다. 직접 한 다음 코드 한 줄만 이동하면 버그가 발생합니다. 기억을 저장한다는 환상이 당신에게 그만한 가치가 있습니까?
이유를 모르고 매크로와 인라인 함수를 사용하지 마십시오. 예, 함수 호출 오버헤드는 비용입니다. 그러나 이를 피하면 코드를 디버그하기가 더 어려워지고 때로는 실제로 속도가 느려집니다. 때때로 좋은 생각이라는 이유만으로 모든 곳에서 이 기술을 사용하는 것은 황금 망치의 예입니다.
루프를 손으로 풀지 마십시오. 다시 말하지만, 이러한 형태의 루프 최적화는 코드의 가독성을 희생하는 것이 아니라 컴파일과 같은 자동화된 프로세스에 의해 거의 항상 더 잘 최적화되는 것입니다.
마지막 두 코드 최적화 예제의 아이러니는 실제로 성능이 저하될 수 있다는 것입니다. 물론 벤치마크를 수행하고 있으므로 특정 코드에 대해 이를 증명하거나 반증할 수 있습니다. 그러나 성능 향상이 보이더라도 예술적인 측면으로 돌아가서 가독성과 유지 관리성을 잃는 데 이득이 가치가 있는지 확인하십시오.
It's Yours: 최적화된 최적화
성능 최적화를 시도하면 도움이 될 수 있습니다. 그러나 종종 너무 일찍 수행되고 많은 나쁜 부작용을 수반하며 가장 아이러니하게도 성능 저하로 이어집니다. 나는 당신이 최적화의 예술과 과학, 그리고 가장 중요한 것은 적절한 맥락에 대한 이해를 넓혔기를 바랍니다.
이것이 우리가 처음부터 완벽한 코드를 작성한다는 개념을 버리고 올바른 코드를 작성하는 데 도움이 된다면 기쁩니다. 위에서 아래로 최적화하고 병목 현상이 있는 위치를 증명하고 수정 전후를 측정하는 것을 기억해야 합니다. 최적화를 최적화하기 위한 최적의 최적의 전략입니다. 행운을 빕니다.

