본문 바로가기
GD's IT Lectures : 기초부터 시리즈/파이썬(Python) 기초부터 ~

[파이썬(PYTHON) : 고급] 성능 최적화

by GDNGY 2023. 5. 13.

3. 성능 최적화

3.1. 프로파일링 및 벤치마킹

3.1.1.1. 프로파일링이란?

프로파일링은 코드의 성능을 분석하고 최적화하는 과정에서 중요한 단계입니다. 이는 우리의 코드가 어느 부분에서 가장 많은 시간을 소비하고 있는지, 어느 부분이 가장 많은 메모리를 사용하고 있는지를 알려줍니다. 이 정보를 통해 우리는 성능을 개선할 수 있는 부분을 파악하고 최적화 작업에 착수할 수 있습니다.

 

3.1.1.2. 파이썬에서의 프로파일

파이썬에서는 cProfile이라는 모듈을 통해 프로파일링을 할 수 있습니다. cProfile은 파이썬 코드의 실행 시간을 측정하고 분석하는 데 사용됩니다.

import cProfile
import re

def example_function():
    re.compile("foo|bar")

cProfile.run('example_function()')

 

이렇게 실행하면 example_function 함수 내에서 실행 시간이 얼마나 걸리는지에 대한 세부 정보를 볼 수 있습니다.

 

반응형

 

3.2. 코드 최적화 기법

3.2.1. 알고리즘 및 자료구조 최적화

3.2.1.1. 알고리즘 복잡도 이해

알고리즘 복잡도는 알고리즘의 성능을 분석하는 데 사용되는 척도입니다. 주로 시간 복잡도와 공간 복잡도를 고려합니다. 시간 복잡도는 알고리즘이 문제를 해결하는 데 걸리는 시간을 나타내며, 공간 복잡도는 알고리즘이 문제를 해결하는 데 필요한 메모리 공간을 나타냅니다. 복잡도가 낮을수록 알고리즘이 효율적입니다. 

 

3.2.1.2. 자료구조 선택의 중요성

효율적인 자료구조 선택은 프로그램의 성능을 크게 향상할 수 있습니다. 예를 들어, 검색을 자주 수행한다면 해시 테이블을 사용하는 것이 좋습니다. 이는 O(1)의 시간 복잡도를 가집니다. 반면에 리스트에서 검색을 수행하는 경우 O(n)의 시간 복잡도를 가집니다. 

 

3.2.2. 병렬 및 비동기 프로그래밍

병렬 프로그래밍과 비동기 프로그래밍은 코드의 실행 속도를 높이는 데 도움이 됩니다.

 

3.2.2.1. 병렬 프로그래밍 소개

병렬 프로그래밍은 여러 개의 처리 유닛(CPU 등)이 동시에 작업을 수행하도록 하는 프로그래밍 방식입니다. 파이썬에서는 multiprocessing 라이브러리를 통해 병렬 프로그래밍을 구현할 수 있습니다. 

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3, 4, 5]))

 

이 코드는 함수 f를 병렬로 실행하여 입력 리스트의 각 요소에 대해 f 함수를 실행하고 결과를 반환합니다.

 

3.2.2.2. 비동기 프로그래밍 소개

비동기 프로그래밍은 동시에 여러 작업을 수행하는 방식입니다. 하지만 병렬 프로그래밍과는 다르게, 하나의 처리 유닛에서 여러 작업이 '동시에' 실행됩니다. 파이썬에서는 asyncio 모듈을 이용해 비동기 프로그래밍을 구현할 수 있습니다.

import asyncio

async def main():
    print('Hello')
    await asyncio.sleep(1)
    print('World')

asyncio.run(main())

 

이 코드는 "Hello"를 출력하고, 1초 동안 대기한 후에 "World"를 출력합니다.

 

3.3. 메모이제이션 및 캐싱

메모이제이션과 캐싱은 비슷한 개념이며, 양쪽 모두 이전에 계산한 값을 저장하고 재사용함으로써 코드의 성능을 향상하는 기법입니다. 

 

3.3.1. 메모이제이션 기법

3.3.1.1. 메모이제이션 이해

아래는 피보나치 수열을 계산하는 간단한 재귀 함수입니다. 피보나치수열은 이전 두 수의 합으로 다음 수를 계산하는 수열입니다.

def fibonacci(n):
    if n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

 

하지만, 이 함수는 중복 계산이 많습니다. 예를 들어, fibonacci(5)를 계산하기 위해 fibonacci(4)와 fibonacci(3)을 계산해야 하는데, 이 두 함수는 각각 fibonacci(3)과 fibonacci(2)를 다시 계산합니다. 이런 중복 계산은 시간을 낭비하게 됩니다. 

 

이런 문제를 해결하기 위해 파이썬은 functools 모듈의 lru_cache 데코레이터를 제공합니다. 이 데코레이터를 사용하면 이전에 계산한 결과를 저장하고 필요할 때 재활용할 수 있습니다. 

 

3.3.1.2. 메모이제이션 활용 예제

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

 

이렇게 하면, fibonacci(n)이 처음 호출될 때의 결과는 캐시에 저장되고, 같은 n에 대해 함수가 다시 호출되면 캐시에서 결과를 가져와서 중복 계산을 피하게 됩니다. 

 

3.3.2. 캐싱 기법

3.3.2.1. 캐싱 이해

캐싱은 메모이제이션과 유사하게, 이전에 계산한 값을 재사용함으로써 성능을 향상하는 기법입니다. 하지만 캐싱은 일반적으로 외부 리소스에 대한 요청 결과를 저장하는 데 사용됩니다. 예를 들어, 데이터베이스 쿼리 결과나 HTTP 요청 결과 등을 캐시에 저장할 수 있습니다.

 

3.3.2.2. 캐싱 활용 예제

파이썬에서는 requests-cache라는 라이브러리를 사용하여 HTTP 요청의 결과를 캐싱할 수 있습니다.

import requests_cache

requests_cache.install_cache('demo_cache')

# 첫 번째 요청은 실제 HTTP 요청을 하고 그 결과를 캐시에 저장합니다.
response = requests.get('https://www.example.com')
print(response.text)

# 두 번째 요청은 캐시에서 결과를 가져옵니다.
response = requests.get('https://www.example.com')
print(response.text)

 

위의 코드에서, requests_cache.install_cache('demo_cache')는 'demo_cache'라는 이름의 캐시를 생성합니다. 이후 requests.get 함수를 통해 웹 페이지를 요청하면, 첫 번째 요청의 결과는 캐시에 저장되고, 두 번째 요청부터는 캐시에 저장된 결과를 사용합니다. 이렇게 함으로써 네트워크 요청을 줄이고 프로그램의 성능을 향상할 수 있습니다.

 

3.4. Just-In-Time 컴파일

Just-In-Time(JIT) 컴파일은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법입니다.

 

3.4.1. JIT 컴파일 이해

3.4.1.1. JIT 컴파일 소개

JIT 컴파일러는 프로그램 실행 중에 특정 코드를 기계어로 변환하고, 동일한 코드가 다시 실행될 때 변환된 기계어 코드를 재사용합니다. 이렇게 함으로써 인터프리터 언어의 유연성과 컴파일 언어의 성능을 모두 얻을 수 있습니다.

 

3.4.1.2. JIT 컴파일의 장점 및 한계

JIT 컴파일의 주요 장점은 프로그램 실행 시간을 크게 줄일 수 있다는 것입니다. 또한, 실행 시점에 최적화할 수 있기 때문에 정적 컴파일보다 더 효율적인 코드를 생성할 수 있습니다.

 

그러나 JIT 컴파일에는 몇 가지 한계점도 있습니다. 첫째, 컴파일러 자체가 프로그램 실행 시간에 포함되므로, JIT 컴파일러의 성능이 중요합니다. 둘째, 메모리 사용량이 늘어날 수 있습니다. JIT 컴파일러는 컴파일된 코드를 메모리에 저장해야 하므로, 이로 인해 메모리 사용량이 증가할 수 있습니다.

 

3.4.2. JIT 컴파일 활용

3.4.2.1. PyPy 소개

PyPy는 파이썬의 대체 구현체 중 하나로, JIT 컴파일 기능을 내장하고 있습니다. 이로 인해 PyPy는 대부분의 작업에서 CPython보다 빠르게 실행됩니다.

 

3.4.2.2. PyPy 사용 예제

PyPy는 별도의 프로그래밍 기법 없이도 파이썬 코드를 빠르게 실행할 수 있습니다. 예를 들어, 아래의 피보나치 수열 계산 코드는 CPython과 PyPy에서 모두 실행할 수 있습니다.

def fibonacci(n):
    if n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))

 

하지만 이 코드를 PyPy에서 실행하면, CPython에서 실행할 때보다 훨씬 빠르게 결과를 얻을 수 있습니다. 이는 PyPy의 JIT 컴파일 기능 덕분입니다.

 

 

 

2023.05.12 - [GD's IT Lectures : 기초부터 시리즈/파이썬(Python) 기초부터 ~] - [파이썬(PYTHON) : 고급] 디자인 패턴

 

[파이썬(PYTHON) : 고급] 디자인 패턴

2. 디자인 패턴 디자인 패턴은 특정 문제를 해결하는 데에 있어 재사용 가능한 해결책입니다. 이는 코드의 효율성, 이해성, 유지 보수성을 향상할 수 있습니다. 2.1. 싱글턴 패턴 2.1.1. 싱글턴 패턴

gdngy.tistory.com

 

반응형

댓글