brunch

You can make anything
by writing

C.S.Lewis

by crtlife noah Jul 14. 2023

복잡도는 실무에서 어떻게 쓰는 걸까?

복잡도는 중요하나 절대적이지 않다

복잡도가 중요하다는 말을 듣고 우리는 각종 알고리즘의 복잡도를 공부하게 된다. 그런데 복잡도는 실무에서 어떻게 참조하고 사용해야 할까? 나도 대학교 1~2학년 때 복잡도를 배웠지만 실제 복잡도를 이렇게 써야 하는구나라고 깨닫게 된 것은 5~6년 정도 후에 회사에서 성능과 관련된 실무를 진행했을 때였다. 내가 한참 후에 알게 되었다고 적어서 '엄청 어려워서 늦게 깨닫게 되나?'라고 겁을 먹은 사람도 있겠지만 사실 배울 때 어떻게 쓰는지를 안 알려줘서 나중에 알게 되었을 뿐이다.


복잡도를 참조하여 프로그래밍하는 법 : O(1)이 O(n)보다 항상 좋지는 않다

우리가 프로그래밍이 잘 되었는지 평가하기 위해서는 많은 기준들이 존재하고 각 프로그램마다 중요하게 봐야 하는 기준들이 다르다. 복잡도는 이런 많은 기준들 중에 하나이다. 복잡도를 기준으로 잡았을 때는 우리가 배운 것처럼 단순 복잡도의 크기만 비교하여 프로그래밍이 잘되었는지 확인하면 안 된다. 예를 들어 O(1)이 O(n) 보다 좋아라고 말할 수 없다. 복잡도를 배운 사람이라면 'O(1)이 O(n) 보다 당연히 좋은데 이게 무슨 소리야?'라고 생각할 것이다. 그런데 우리가 복잡도를 기준으로 평가를 하려면 현재 프로그래밍하고 있는 상황도 참조하여 분위기를 파악해야 한다. 전체적인 분위기를 모른다면 우리는 아마 오버스펙으로 프로그래밍을 하게 된다. 즉, 요구한 내용보다 초과해서 기능을 구현하게 되는 것이다. 예를 들어, 0.01초 만에 응답이 오게 하는 것을 0.001초로 구현하는 것이다. 이런 민감한 프로그래밍이 필요한 프로그램도 있지만 대부분은 프로그램에서는 큰 의미가 없을 것이다. 이런 큰 의미 없는 프로그래밍을 하게 되면 두 가지 문제가 발생하게 된다. 

첫째, 다른 기능을 구현하기 위해 사용해야 하는 비용(예. 시간)을 초과 기능 구현하는 데 사용하게 된다. 실무에서는 제한된 시간에 일정한 요구사항을 만족하는 프로그램을 만들어내야 한다. 그런데 이런 초과 기능을 구현하는데 시간을 쓰게 되면 시간이 부족해지면서 다른 기능을 구현을 미구현하거나 다른 기능에 충분한 시간을 줄 수 없어서 완성도가 떨어질 확률이 올라간다.

둘째, 서로 공유된 요구사항이 아니기 때문에 유지 보수가 어려워진다. 글을 쓰는 것으로 비유하면 평범한 설명문에 혼자만 시적인 표현을 작성하는 것과 같다. 혼자만 다르게 쓴 내용은 나중에 많은 사람과 협업할 때 문제를 일으키게 된다. 실제 겪었던 경험이지만 극단적인 예를 들으면 특정 사람이 오버스펙 코드를 참조하고 해당 스펙이 맞다고 생각해서 내부코드를 전부 오버스펙 코드로 바꿔버릴 수가 있다. 그러면 일단 해당 코드를 바꾸는 비용이 추가적으로 들게 되고 바꾸면서 호환이 안 되는 기존 기능들도 요구되지 않은 기능을 맞추기 위해 수정해야 하는 많은 작업이 생길 수 있다. 그런데 많은 비용을 들여서 바꾼 내용은 제품 관점에서는 아무 의미가 없을 것이다.


이러한 이유로 O(n)의 간단한 코드가 O(1)의 복잡한 코드보다 좋을 수 있다. 따라서 실무에서는 단순 복잡도만 보고 프로그래밍이 잘 되었는지 확인하면 안 된다. 그렇다고 다양한 것을 따지면서 프로그래밍하는 것은 어렵기 때문에 복잡도를 고려해서 쉽게 코드를 작성하는 방법을 알려주면 이미 작성된 근처 중요한 코드의 가장 높은 복잡도가 몇인지만 확인하는 것이다. 예를 들어 이중반복문이 있어서  O(n^2)이 일어나는 코드가 근처에 있다면 일반적으로 코드를 추가하는데 O(n) 정도는 크게 문제 되지 않을 것이다.


시간 복잡도가 높아도 간단한 코드가 좋을 수 있다

나는 자주 쓰는 함수들의 시간 복잡도에 의문을 가져본 적이 있다. 더 개선할 수 있을 것 같은데 왜 O(n)으로 제공할까? 그 이유는 간단하다. 더 개선하면 쓸 수 있는 상황이 제한적으로 변하거나 공간을 더 많이 쓰기 때문이다. 


문자열 길이를 구하는 함수를 예로 들어 생각해 보자. 문자열의 길이를 구하는 함수는 O(n)의 복잡도를 가졌다.  아마 아래와 같은 코드로 길이를 구하기 때문에 해당 복잡도를 가졌을 것이다.



def 문자열_길이구하기 (문자열):

  len = 0

  for 문자 in 문자열:

    len += 1

  return len;



그러면 이제 철수가 해당 문자열 길이 함수를 개선하기 위하여 아래와 같은 코드를 작성했다고 생각해 보자.



class 문자열길이최적화:

  def __init__(self, 문자열):

    self.문자열 = 문자열

    문자열길이 = 0

    for 문자 in 문자열:

      문자열길이 += 1

    self.문자열길이 =  문자열길이

 

  def 문자열_길이구하기:

    return self.문자열길이


철수는 문자열이 초기에 생성될 때 문자열의 길이를 구하는 방식으로 문자열 길이 구하는 함수의 복잡도를 O(1)로 개선하였다. 그러나 해당 코드를 사용하게 되면 문자열의 길이를 보관하는 비용이 추가로 항상 들게 되고 문자열 길이를 요청하는 함수를 쓰지 않아도 항상 문자열 길이를 구하는 비용을 문자열이 만들어질 때 지불하는 것을 알 수 있다. 무조건 좋아지는 복잡도 개선을 할 수 있다면 좋겠지만 보통은 특정함수의 복잡도 개선은 다른 특정 상황에 대한 추가 비용으로 바뀌게 된다. 정말 세상에 공짜는 없다. 따라서 정말 제한적인 상황에서 특별한 프로그램을 짜야하는 게 아니라면 일반적으로 사용성이 좋은 코드가 복잡도를 최대한 개선한 코드보다 더 좋을 수 있다. 실제로 유지보수도 훨씬 원활할 것이다.


복잡도는 중요하나 절대적이지 않다

쓰다 보니 실무에서 복잡도가 엄청 중요하지 않다는 느낌으로 오해해서 이해하는 사람이 생길 수 있다는 생각이 들었는데 복잡도는 중요하다. 복잡도가 굉장히 큰 코드를 넣어서 프로그램을 느리게 하거나 메모리를 너무 많이 써서 정상적으로 돌아가지 못하도록 만들면 안 된다. 그래서 우리는 우리가 작성하는 코드의 복잡도를 이해하고 작성을 해야 한다. 그러나 단순 복잡도의 크기가 절대적인 기준의 개선 요소가 아니기 때문에 너무 집착하면 안 된다는 말을 하고 싶었다. 

매거진의 이전글 파이썬 이상한 리스트 초기화 예제 분석
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari