2P by GN⁺ | ★ favorite | 댓글 1개
  • 1989년 Rob Pike의 프로그래밍 5가지 규칙에 대한 기사
  • 규칙 1: 프로그램이 대부분의 시간을 어디에서 보낼지 가정하지 마라, 병목 현상은 예기치 않게 발생할 수 있다. 병목 현상이 입증될 때까지 속도 해킹을 피하라.
  • 규칙 2: 속도를 위해 튜닝하기 전에 항상 측정하라. 코드의 일부가 나머지에 상당한 영향을 미치는 경우에만 최적화하라.
  • 규칙 3: n이 작을 때 복잡한 알고리즘은 느리다. 이는 대부분의 경우이다. n이 자주 큰 경우에만 복잡한 알고리즘을 사용하고, 그럴 때도 먼저 규칙 2를 적용하라.
  • 규칙 4: 간단한 알고리즘과 데이터 구조가 바람직하다. 복잡한 것들보다 버그에 덜 취약하고 구현하기 쉽다.
  • 규칙 5: 올바른 데이터 구조는 프로그래밍에 결정적이다. 데이터가 잘 구성되어 있으면, 알고리즘은 자명하게 될 것이다.
  • Pike의 규칙 1과 2는 Tony Hoare의 격언 "조기 최적화는 모든 악의 근원이다"를 반영한다.
  • Ken Thompson은 Pike의 규칙 3과 4를 "의심스러울 때는 무차별적인 힘을 사용하라"로 다시 표현했다.
  • 규칙 3과 4는 KISS (Keep It Simple, Stupid) 디자인 철학을 구현한다.
  • 규칙 5는 Fred Brooks의 'The Mythical Man-Month'에서의 발언과 일치하며, 종종 "스마트한 객체를 사용하는 멍청한 코드를 작성하라"로 줄여서 말한다.

댓글과 토론

Hacker News 의견들
  • 데이터가 지배한다”는 말에 완전히 동의함
    그래서 LeetCode 면접이 늘 이상하게 느껴졌음. 대개 알고리즘에 초점을 맞추는데, 실제로는 초반부터 그렇게 접근하면 안 되는 경우가 많고 자료구조가 더 중심이어야 함
    물론 알고리즘을 전혀 모르면 예외 상황이나 특정 이유로 특정 알고리즘에 기대야 하는 때를 알아차리지 못할 수는 있음. 그래도 알고리즘은 비교적 짧게 가르칠 수 있는 반면, 어떤 자료구조를 써야 하는지 감 잡는 쪽은 사람들이 더 어려워하는 듯함

    • 내 경험으로도 동의함. 면접에서 FizzBuzz 같은 알고리즘 확인을 넘어서 바로 자료구조, 아키텍처, 도메인과의 대응을 이야기하기 시작하면 면접관이 훨씬 더 존중하는 걸 봤음
      그 순간 “아, 진짜 시니어 엔지니어가 들어왔구나” 하는 분위기로 바뀌고, 기술적 문제를 더 열어놓고 이야기하며 “코딩은 할 줄 아는지” 증명하려는 태도도 줄어듦
      반대로 좋은 변화 만들기, 마일스톤 달성, 팀 협업에서 가장 힘들었던 팀들은 자료구조와 코드 아키텍처를 제대로 잡는 사람이 없던 곳이었음. 프레임워크가 다 해주고, 안 되면 더 똑똑한 누군가가 만든 플러그인이나 미들웨어가 해결해줄 거라는 식에 익숙한 사람이 많아진 듯함
      자료구조를 피하는 엔지니어는 자기 발등을 찍는 셈이고, 가장 유용한 도구 중 하나를 포기하는 거라 일상적으로 한계가 드러남
    • 경쟁 프로그래밍 대회를 준비하는 조카를 도와보니, 대부분의 문제에서 데이터를 적절한 자료구조로 변환하는 게 해결의 큰 부분이었음
      예를 들어 코드는 가중치가 있는 방향 비순환 그래프(DAG)에서 최장 경로를 찾는 데 많이 쓰일 수 있지만, 핵심은 문제를 가중 DAG로 표현할 수 있다는 걸 깨닫는 데 있었음. 그걸 못 보면 여전히 풀 수는 있어도 훨씬 느리고 복잡한 풀이가 됨
    • 일반적인 LeetCode 문제도 사실 자료구조에 초점을 둠. 지원자가 문제와 해법을 패턴 매칭할 때 머릿속에서 꺼낼 자료구조 목록을 갖고 있어야 하기 때문임
      면접관이 우선순위 큐, 인접 행렬, 트라이 같은 걸 써야 한다고 먼저 알려주지는 않음. 막히면 힌트를 줄 수는 있지만, 지나친 유도는 강한 채용 신호로 보이기 어렵다
    • “흐름도는 보여주되 표는 숨기면 나는 계속 헷갈릴 것이다. 표를 보여주면 흐름도는 볼 필요가 없다. 저절로 명확해질 테니까”
    • 둘 중 하나를 고르려면 다른 하나에 대한 대략적인 감은 필요하지 않나 싶음. 데이터를 어떻게 접근할지 전혀 모르면 어떤 자료구조를 써야 할지 알기 어렵다
  • “멋진 알고리즘은 n이 작을 때 느리고, n은 보통 작다”는 말과 관련해, 최근 프로젝트에서 체감한 건 큰 n은 생각보다 훨씬 클 수 있다는 점임
    “10만 번 연산해야 하니 반드시 최적화해야겠다”라고 생각하기 쉽지만, 컴퓨터는 빠르고 10만 번 곱셈 정도는 대개 너무 빨라 깊게 고민할 필요가 없을 수 있음
    아무 생각도 하지 말자는 건 아니지만, 현대 하드웨어가 얼마나 미친 듯이 빠른지는 자주 놀랍다

    • 이 말에는 강하게 동의하기 어려움. 이차 시간 알고리즘은 예상 못 한 순간에 물기 쉬운 종류임
      실수로 이차 시간이 된 코드 때문에 운영 장애가 난 걸 봤고, 99% 사용자는 항상 작은 n을 쓰더라도 일부 사용자는 자주 큰 n을 만나 매우 느린 앱을 겪을 수 있음
      대부분의 경우 일반적인 경우에 조금 느리고 구현이 조금 복잡하더라도 이차 시간보다 나은 알고리즘을 고르고 싶음. 흔한 느린 경로는 최적화되지만, 드문 느린 경로는 개발자가 직접 안 밟아 지나치거나 운영에서 터짐
      물론 너무 복잡한 알고리즘이면 단순한 이차 시간 구현을 고를 수도 있지만, 기본값은 가능하면 이차 미만으로 잡으려 함. 관련해서 쓴 글도 있음: https://kevincox.ca/2023/05/09/less-than-quadratic/
    • 메모리 계층도 여기에 영향을 줌. 많은 멋진 알고리즘은 참조 지역성이 나쁘고 분기가 추가됨
      그래서 CPU가 메모리보다 그렇게 빠르지 않았고, 소비자용 하드웨어에서 분기 예측 실패를 크게 신경 쓰지 않던 40년 전에는 더 잘 맞았을 수 있음
    • LeetCode 면접 문제에서 10만 개짜리 목록을 몇 번 순회하는 걸 계속 봄. 최적은 아닐 수 있지만, 실제 운영 시간 기준으로는 그 직후에 하는 네트워크 호출에 비하면 10만 개 순회는 아무것도 아님
      면접 때마다 채용 매니저는 원하지만, 운영의 상처를 아직 겪어보지 못한 LeetCode 초보가 결정을 거부하는 일이 생김
    • 이 주제의 대표 참고 자료는 Scalability! But at what COST?
      https://www.frankmcsherry.org/assets/COST.pdf
    • 2000년대 초 게임 회사에서 첫 제대로 된 프로그래밍 일을 시작했을 때, 기술 디렉터가 “다루는 항목 수가 1만 정도면 최적화하지 말라”고 조언했음
      지난 20년간 컴퓨터 성능 향상을 생각하면 그 기준을 10만으로 올리는 게 꽤 적절해 보임
  • “조기 최적화는 모든 악의 근원”이라는 유명한 격언은 Tony Hoare가 아니라 실제로는 Donald Knuth에서 온 말이고, 최적화 일반을 반대하는 말로 자주 문맥 없이 쓰임
    전체 문장은 “우리는 작은 효율, 말하자면 97%의 경우는 잊어야 한다. 조기 최적화는 모든 악의 근원이다. 그러나 중요한 3%에서 기회를 놓쳐서는 안 된다”임
    요지는 영향이 있는 곳에 시간을 써서 최적화하라는 것임

    • Knuth는 Hoare의 말이라고 하고, Hoare는 Knuth의 말이라고 해서 누구를 믿느냐의 문제임. 둘 다에게 돌리는 게 제일 나을 듯함
      아마 Tony가 먼저 말했고 Knuth가 다듬어 출판했을 가능성이 있어 보임. 필요한 문맥을 주는 긴 인용을 함께 두는 건 항상 좋다
    • 그 인용이 1970년대 말이라는 점도 자주 잊힘. 거의 50년 전임
      당시 프로그래밍은 지금과 많이 달랐음. 그때의 “조기 최적화”는 “확장성 좋은 인기 라이브러리를 그냥 쓰자”가 아니라, “이 하드웨어에서만 동작하는 이해 불가능한 비트 조작 알고리즘을 쓰자”에 가까웠음
    • 긴 인용이 의미 있는 추가 문맥을 준다고 보지는 않음. 중요한 3%를 측정해서 찾아냈다면 그 상태는 더 이상 조기가 아님
      “조기 최적화가 모든 악의 근원”이라는 말에 이미 포함된 뜻이고, 격언은 “최적화가 모든 악의 근원”이 아님
    • 이 말을 교리처럼 받아들여 효율적인 방법 자체를 배우지 않는 사람이 너무 많음
      회사의 자료구조·알고리즘 면접에서 버블 정렬이 최선이라고 말하는 프런트엔드 개발자를 셀 수 없이 봤음. 즉석에서 유도까지 할 필요는 없고, 몇 가지를 알고 문제에 맞는 좋은 선택을 말해주면 충분함
      “조기 최적화하지 말라”를 너무 극단적으로 살아서 효율적인 방법조차 모르면, 어디가 중요한지 어떻게 알 수 있나
    • 이 문맥에서는 최적화 일반을 반대하는 뜻으로 쓰인 건 아닌 듯함
  • “자료구조가 핵심”이라는 말은 데이터베이스에서는 두 배로 중요함
    DB를 멍청한 비트 저장통이나 객체 정의의 1:1 반영 정도로 쓰는 사람들은, DB가 그걸 개인적으로 받아들이고 성능을 망칠 때 놀라곤 함
    ORM이 생성한 DB 스키마를 또 보게 된다면 너무 이른 재회일 것 같음

    • 대부분의 ORM은 요청한 대로 스키마를 생성한다고 봄. ORM을 쓴다고 손으로 만드는 것보다 더 나쁜 데이터베이스 설계가 자동으로 생기지는 않음
      문제는 일부 또는 많은 개발자가 SQL을 모르고, ORM을 쓰는 데 필요한 DB 지식도 없다는 데 있음
      ORM은 아래에 무엇이 있는지 알아야 하는 꽤 새는 추상화임. 그걸 이해하면 대부분의 ORM으로도 괜찮은 스키마를 만들 수 있음
    • 여기에 Conway의 법칙도 더할 수 있음. “시스템을 설계하는 조직은 그 조직의 의사소통 구조를 복제한 설계를 만들게 된다”는 말임
      데이터 구조를 잘 조직하고, 설계가 바뀌어도 그렇게 유지하려면 데이터와 코드를 조직 수준에서 분리해야 함
      DB 스키마 설계, 사용 사례, 그 사이의 매핑을 나머지 구현과 분리하고, 이 그룹이 무결성 검사 등도 작성해야 함. 조직 구조가 데이터와 코드를 분리하지 않으면 코드와 데이터를 분리하기 어렵다
    • 저장 프로시저가 이김
  • 내 추가 규칙은, 작은 성능 낭비들이 쌓이면 각각은 별것 아니어도 결국 프로그램을 느리게 만든다는 것임
    복잡도, 가독성, 유지보수성, 구현 비용에 영향이 없다면 성능을 그냥 버려두지 말아야 함. 다른 조건이 거의 같다면 두 선택지 중 더 느린 걸 고르는 건 괜찮지 않음
    또 n이 작다고 가정하면 거의 무엇이든 통함. 하지만 n이 100 이하에서는 잘 동작하고 10000 이상에서는 망가지는 코드, 예를 들어 O(n²) 같은 걸 쓴다면 그냥 제한을 걸어야 함. 작은 n 가정이 깨지면 크게 에러를 내는 편이 AWS 청구서 폭탄이나 멈춘 프로그램보다 낫다

    • 여기에는 규칙 1과 2가 적용됨
  • 이 지침들 상당수는 결국 과잉 설계를 막는 전략으로 귀결됨
    내 경험상 조기 최적화는 가장 비싼 함정 중 하나임. 잠재적 문제를 너무 일찍 우회하면 그 가정이 검증되지 않고, 다음 팀이 불필요한 복잡성을 해결하려고 비싼 해법을 만들어야 함
    배운 접근법은 이렇다. 최적화는 추정에 기대고, 초반의 추정은 자주 틀림
    또한 사람들이 지나치게 복잡한 코드를 만들지 않도록 하려면 자존심 관리와 심리 이해가 꽤 중요하다는 것도 알게 됨

    • “가지고 있는 문제를 풀어라. 가지고 있다고 생각하는 문제를 풀지 말라”는 식으로 말하곤 함
    • 이 개념은 린과 식스 시그마의 낭비 식별과도 맞닿아 있음
      과잉 생산은 보통 최악의 낭비로 여겨지는데, 필요 없는 걸 만들 뿐 아니라 실제로 필요한 것에 쓸 수 있었던 노력을 써버리기 때문임. 과잉 설계도 비슷함
    • 한 단계 더 들어가면, 과잉 설계는 나중에 복잡성이 필요할지도 모르고 그때 시스템을 확장하는 게 더 어렵거나 위험할 거라고 생각해서 생김
      예를 들어 사용자가 100명뿐인데도, 언젠가 100만 명이 되면 모놀리스를 재설계하기 어려울 거라며 마이크로서비스 아키텍처로 시작하는 식임
      그래서 시간이 지날수록 코드가 왜 덜 말랑해지는지부터 다뤄야 함
    • 오류 처리는 멋 부리지 말고 일찍 단순하게 실패하는 게 좋음
  • 대체로 좋은 규칙들이지만, 실제로는 1번이 그대로 성립하지 않음
    시작할 때는 무엇이 병목이 될지에 대한 가설이 필요함. XYZ를 구현한 뒤 무엇이 느린지 측정하고 고치면 된다는 식이 항상 가능하지 않음. X, Y, Z가 연결돼 있어서 Y를 빠르게 만들기 위해 X와 Z를 특정 방식으로 만들어야 할 때가 있고, Y가 병목이 될 걸 이미 아는 경우가 있음
    나중에 측정해서 무엇이 느린지 알게 되더라도, 더 빠르게 만들 접근법에 베팅해야 함. 더 교육받은 베팅일수록 좋음
    좋은 프로그래머는 측정하지만, 무엇이 느리고 버그가 많고 메모리를 많이 쓸지 예측할 수 있어서 반복을 덜 함. 성능 동작을 예측할 수 없다는 걸 규칙처럼 말하면 좋은 프로그래머가 쌓은 경험과 기술을 무시하는 셈임

    • 1번 규칙은 믿지 않는 사람에게는 철칙이고, 믿는 사람에게는 느슨한 지침임
      1번을 지키는 과정이야말로 병목을 예상하는 좋은 직관에 필요한 경험과 경험적 배경을 얻는 가장 좋은 방법이기 때문임
    • 느릴 것 같은 알고리즘은 스파이크 구현으로 확인할 수 있음. 보통 느린 알고리즘은 구현과 테스트가 간단함
      속도 예측이 틀리면 프로젝트 수명 내내 불필요하게 복잡한 코드를 안고 가게 됨
      사람들은 알고리즘 속도를 자주 틀림. 컴퓨터가 시간의 99%를 DB 서버에서 n을 가져오는 데 쓰면 O(n)과 O(n²)이 실제 시간으로 같게 보이는 일도 많음
      C로 쓴 알고리즘이 동등한 Python 코드보다 느릴 때도 있는데, 바이트코드 컴파일러가 영리한 일을 했기 때문일 수 있음
      레거시 코드를 빠르게 만드는 일을 많이 해봤는데, 보통 생각보다 훨씬 쉽고, 원래 작성자에게는 명확하지 않았을 이유로 느림. 실제로는 코드베이스가 너무 복잡해져서 원 작성자가 더 이상 추론하지 못했기 때문에 느린 경우가 많음. 내게는 “너무 느린” 구체적 예가 있으니 실행하며 느린 지점을 관찰해 디버그하기 쉽다
    • 원문 전체에 대한 반응은 아닌 듯함. 거기서는 병목을 알기 전까지 속도 해킹을 넣지 말라고 함. 설명한 상황과는 다름
      물리 객체가 많은 비디오 게임을 만들고 있고, 경험상 충돌 감지가 큰 문제가 될 걸 확실히 안다면, 게임과 시스템을 그 중심으로 설계하는 건 속도 해킹이 아님
      성능이 큰 관심사가 될 걸 아는 작업이라면 당연히 측정해야 함. 관심사인지 확인하려는 게 아니라, 그 관심사를 얼마나 잘 다루고 있는지 확인하려는 것임
    • 구체적인 예가 궁금함. 대부분의 경우 실제로 차이가 나는지 의심스러움
      새 요구사항으로 새 시스템을 만든다면 일단 시작해도 괜찮은 경우가 많지 않나 싶음. 만들고, 테스트·측정하고, 버리거나 리팩터링하고, 반복하면 됨
      Rust를 예로 들면 초안 언어와 OCaml로 만든 컴파일러에서 시작해 반복했음. 언젠가 OCaml에서 자체 호스팅으로 옮길 수 있다는 걸 알았더라도 그게 큰 차이를 만들었는지는 잘 모르겠음
    • 개발자가 무엇이 느릴지 그렇게 잘 예측할 수 있다면, 개발자 주도 스타트업의 성공률이 100%여야 하지 않나 싶음
      사용자가 없다면 몇 시간 걸리는 함수도 최적화하면 밀리초가 되는 함수에 비해 여전히 충분히 빠름. 그런 예측을 정확히 할 수 있다는 걸 입증한 사람이 있는지는 잘 모르겠다
  • 5번에 대한 반론으로, 단순한 데이터 위의 복잡한 알고리즘이 큰 성능 이득을 주고 장애물을 없애며 오히려 단순화할 수도 있음
    예를 들어 BinaryTree 객체 대신 정렬된 배열에서 이진 탐색을 하면 병합은 concat 후 정렬이라 단순해지고, 포인터가 없어 직렬화가 쉬우며, 경우에 따라 직렬화 자체가 필요 없어짐. 배열은 디스크나 메모리 또는 mmap으로 둘 다에 있을 수 있고, RAM보다 큰 데이터도 다룰 수 있으며, 파일이나 매핑만 가리키고 바로 실행하는 콜드 스타트도 가능함. 캐시 무시(cache-oblivious) 특성도 있음
    Huffman 코딩도 예임. 대학에서는 보통 트리 기반 알고리즘과 O(n log n) 복잡도로 배웠겠지만, 제자리 배열 기반으로 Huffman 트리를 선형 시간에 구성하는 방법이 있다는 걸 몰랐음
    물론 99%의 시간에는 백엔드 마이크로서비스를 만들며 표준 컬렉션 자료구조를 씀. 하지만 직장에서 빅데이터 작업을 한다면, 당시 유행하는 MapReduce 계열을 도입하기보다 로컬의 큰 디스크가 달린 단일 머신에서 처리하는 쪽을 훨씬 선호함

    • 이진 탐색은 멋진 알고리즘이라고 보지 않음. 현대 정렬 함수가 멋진 쪽이고, 미묘한 버그가 있을 수 있어서 일반 개발자가 직접 만들면 안 됨. 퀵정렬조차 함정이 있음
      Rob Pike라면 먼저 코드를 프로파일링하고, 그다음 멋진 코드나 대체 자료구조가 실제로 더 빠른지 보라고 했을 듯함
    • 이건 반론으로 보이지 않음. Pike의 조언 관점에서는 “정렬된 배열에서 이진 탐색”과 “BinaryTree 객체”가 동일한 자료구조의 서로 다른 구현일 뿐임
    • 개발자가 99%의 시간 동안 가장 비싼 자원이라는 점을 잊으면 안 됨. 유지보수성과 시장 출시 속도가 보통 훨씬 더 중요함
  • 이 글을 10년 넘게 전 cat-v에서 처음 읽었고, 설계와 복잡성을 접근하고 생각하는 방식에 지워지지 않는 영향을 받았음
    http://doc.cat-v.org/bell_labs/pikestyle

  • “자료구조가 핵심”이라는 원래 규칙에서 어떻게 “똑똑한 객체를 쓰는 멍청한 코드를 작성하라”로 줄어드는지 모르겠음
    “smart objects”라는 표현은 너무 별로였고, 원래 규칙이 길어도 훨씬 낫다

    • Rob Pike도 “smart objects”는 잘못된 사고방식이라고 동의할 것 같음: https://commandcenter.blogspot.com/2012/06/less-is-exponenti...
    • “스마트” 로직을 더 높은 수준으로 올리는 편이 이해, 테스트, 변경이 더 쉬움. smart objects는 서로 응집력 있게 만들기가 훨씬 어렵다고 봄
    • 잘 구조화된 객체에서 자연스럽게 따라 나오는 코드를 쓰라는 뜻으로 보면 됨