반응형

기존의 코드를 리펙토링 하면서 여러 가지 방법을 써보며 성능테스트를 하면서 

 

가독성과 간결성, 성능을 비교하며 테스트하였습니다.

 

1. example_1

# 첫번째 방법

start_time = time.time()
list_first_uid: list[int] = []
list_second_uid: list[int] = []
dict_number: dict[int, int] = {}
for i in range(10000000):
    list_first_uid.append(i)
    list_second_uid.append(i)
    dict_number[i] = i
end_time = time.time()
print(f"First method took {end_time - start_time} seconds")

 

2. example_2

# 두번째 방법
start_time = time.time()
list_first_uid, list_second_uid, tuple_dict_number = zip(*((i, i, (i, i)) for i in range(10000000)))
dict_number = dict(tuple_dict_number)
end_time = time.time()
print(f"Second method took {end_time - start_time} seconds")

이렇게 2가지 방식이 있습니다.

 

다른 방식도 있으나, 대표적으로 고민했던 로직이 이렇게 2가지였습니다.

 

비교를 하면 두 번째 방식(example_2)이 간결해 보이고 깔끔해 보이는(?) 느낌이 있습니다.

 

한 줄로 눈에 확 들어오기도 합니다.

 

예시이기 때문에 10,000,000의 숫자를 for문을 통하여 2개의 list와 1개의 dict에 삽입하는 테스트를 하였습니다.

 

개인적 생각으로는 두 번째 방식(example_2)이 더 깔끔하여 보이지만,

 

성능상에서는 큰 차이가 있습니다.

 

Result

First method took 3.508805751800537 seconds
Second method took 33.76016187667847 seconds

테스트 방식에 따라 다르지만, 동일한 상황에서 동일한 행동을 하였을 때,

 

성능 차이가 약 1.1~1.2배 정도 차이가 났습니다.

왜 이런 성능 차이가 나는가? 라는 것에 의문이 들 겁니다. (아마..?)


1. 튜플 생성 및 언패킹 오버헤드:

  • 두 번째 방법에서는 (i, i, (i, i)) 형태로 각 루프마다 튜플을 생성하고, zip 함수가 이를 언패킹하여 각 리스트에 분리하는 과정이 있습니다. 튜플 생성과 언패킹은 추가적인 메모리 할당과 연산을 필요로 하여 시간 소요가 큽니다.

2. 리스트 생성과 변환 오버헤드:

  • zip 함수는 튜플로 결과를 반환하므로, 마지막에 이를 리스트로 변환해야 합니다. list(list_first_uid)와 같은 변환은 1000만 개의 요소를 가진 튜플을 리스트로 변환하기 때문에 시간이 많이 소요됩니다.

3. 동시 처리의 비효율성:

  • 첫 번째 방법은 단순히 리스트와 사전을 동시에 업데이트합니다. 각 append와 사전 할당 연산이 독립적으로 일어나고, 이 과정은 파이썬 인터프리터에서 매우 최적화되어 있습니다.
  • 두 번째 방법은 제너레이터 표현식을 사용하여 1000만 개의 튜플을 생성하고, 이를 zip 함수로 한 번에 처리하려고 합니다. 이 과정에서 많은 메모리 사용과 CPU 오버헤드가 발생합니다.

zip이 병렬 처리가 가능하다는 점에서 굉장히 많은 강점이 있다고 생각할 수도 있으나,

 

막상 성능 테스트를 통하여 확인하였을 때,

 

그 값을 활용해야 한다면 오버헤드가 발생하여 성능상 떨어질 수도 있다는 단점이 있습니다.

 

상황에 따라 다 다르기 때문에 무조건 나쁘다고 이야기할 수는 없지만,

 

가독성과 간결성이냐 성능이냐에 따라 선택할 수 있을 것 같습니다.

반응형

+ Recent posts