KR_Optimization - somaz94/python-study GitHub Wiki

Python ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ ์ฝ”๋“œ ์ตœ์ ํ™” ๊ธฐ๋ณธ

ํŒŒ์ด์ฌ ์ฝ”๋“œ ์ตœ์ ํ™”์˜ ๊ธฐ๋ณธ ์›์น™๊ณผ ๊ฐ„๋‹จํ•œ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์ด๋‹ค.

from typing import List, Dict, Callable, Any
import time
from functools import lru_cache
import timeit
import dis

def measure_time(func: Callable, *args, **kwargs) -> float:
    """ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
    
    Args:
        func: ์ธก์ •ํ•  ํ•จ์ˆ˜
        args: ํ•จ์ˆ˜ ์ธ์ž
        kwargs: ํ•จ์ˆ˜ ํ‚ค์›Œ๋“œ ์ธ์ž
        
    Returns:
        float: ์‹คํ–‰ ์‹œ๊ฐ„(์ดˆ)
    """
    start_time = time.time()
    result = func(*args, **kwargs)
    end_time = time.time()
    print(f"{func.__name__} ์‹คํ–‰ ์‹œ๊ฐ„: {end_time - start_time:.6f}์ดˆ")
    return end_time - start_time

# 1. ๋ฆฌ์ŠคํŠธ ์ปดํ”„๋ฆฌํ—จ์…˜ ์‚ฌ์šฉ
def create_list_loop(n: int) -> List[int]:
    """๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•œ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ (๋น„ํšจ์œจ์ )"""
    numbers = []
    for i in range(n):
        if i % 2 == 0:
            numbers.append(i)
    return numbers

def create_list_comprehension(n: int) -> List[int]:
    """๋ฆฌ์ŠคํŠธ ์ปดํ”„๋ฆฌํ—จ์…˜ ์‚ฌ์šฉ (์ตœ์ ํ™”)"""
    return [i for i in range(n) if i % 2 == 0]

# 2. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ (๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ)
def number_list(n: int) -> List[int]:
    """๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜ (๋ฉ”๋ชจ๋ฆฌ ์ง‘์•ฝ์ )"""
    return [i for i in range(n)]

def number_generator(n: int):
    """์ œ๋„ˆ๋ ˆ์ดํ„ฐ ๋ฐ˜ํ™˜ (๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ )"""
    for i in range(n):
        yield i

# 3. ์บ์‹ฑ ์‚ฌ์šฉ
def fibonacci_naive(n: int) -> int:
    """์žฌ๊ท€์  ํ”ผ๋ณด๋‚˜์น˜ (๋น„ํšจ์œจ์ )"""
    if n < 2:
        return n
    return fibonacci_naive(n-1) + fibonacci_naive(n-2)

@lru_cache(maxsize=128)
def fibonacci_cached(n: int) -> int:
    """์บ์‹ฑ๋œ ํ”ผ๋ณด๋‚˜์น˜ (์ตœ์ ํ™”)"""
    if n < 2:
        return n
    return fibonacci_cached(n-1) + fibonacci_cached(n-2)

# 4. ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ตœ์†Œํ™”
def process_items_inefficient(items: List[int]) -> List[int]:
    """ํ•จ์ˆ˜ ํ˜ธ์ถœ์ด ๋งŽ์€ ๋น„ํšจ์œจ์  ๋ฐฉ์‹"""
    result = []
    for item in items:
        result.append(item * 2)
    return result

def process_items_efficient(items: List[int]) -> List[int]:
    """ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์ตœ์†Œํ™”ํ•œ ํšจ์œจ์  ๋ฐฉ์‹"""
    append = result.append  # ์ง€์—ญ ๋ณ€์ˆ˜์— ๋ฉ”์†Œ๋“œ ๋ฐ”์ธ๋”ฉ
    result = []
    for item in items:
        append(item * 2)
    return result

# 5. ๋ณ€์ˆ˜ ์ง€์—ญํ™”
x = 0  # ์ „์—ญ ๋ณ€์ˆ˜
def increment_global():
    """์ „์—ญ ๋ณ€์ˆ˜ ์‚ฌ์šฉ (๋А๋ฆผ)"""
    global x
    for i in range(1000000):
        x += 1
    return x

def increment_local():
    """์ง€์—ญ ๋ณ€์ˆ˜ ์‚ฌ์šฉ (๋น ๋ฆ„)"""
    x = 0
    for i in range(1000000):
        x += 1
    return x

# ํšจ์œจ์„ฑ ๋น„๊ต ์˜ˆ์‹œ
if __name__ == "__main__":
    # ๋ฆฌ์ŠคํŠธ ์ปดํ”„๋ฆฌํ—จ์…˜ vs ๋ฃจํ”„
    n = 1000000
    measure_time(create_list_loop, n)
    measure_time(create_list_comprehension, n)
    
    # ์ผ๋ฐ˜ ๋ฆฌ์ŠคํŠธ vs ์ œ๋„ˆ๋ ˆ์ดํ„ฐ
    print("\n๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ๋น„๊ต:")
    print("๋ฆฌ์ŠคํŠธ: ๋ชจ๋“  ํ•ญ๋ชฉ์„ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ")
    numbers_list = number_list(1000000)
    print(f"์ œ๋„ˆ๋ ˆ์ดํ„ฐ: ๊ฐ’์„ ํ•„์š”ํ•  ๋•Œ๋งŒ ์ƒ์„ฑ")
    numbers_gen = number_generator(1000000)
    
    # ์บ์‹ฑ ํšจ๊ณผ
    print("\n์บ์‹ฑ ํšจ๊ณผ:")
    n = 35
    measure_time(fibonacci_naive, n)
    measure_time(fibonacci_cached, n)
    # ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ์€ ์บ์‹œ์—์„œ ๋ฐ”๋กœ ๊ฐ€์ ธ์˜ด
    measure_time(fibonacci_cached, n)
    
    # ๋ฐ”์ดํŠธ์ฝ”๋“œ ๋ถ„์„
    print("\n๋ฐ”์ดํŠธ์ฝ”๋“œ ๋น„๊ต:")
    dis.dis(lambda: [i for i in range(10)])
    print("---")
    dis.dis(lambda: list(range(10)))

โœ… ํŠน์ง•:

  • ๋ฆฌ์ŠคํŠธ ์ปดํ”„๋ฆฌํ—จ์…˜
  • ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ™œ์šฉ
  • ์บ์‹ฑ ๊ตฌํ˜„
  • ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ตœ์†Œํ™”
  • ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ
  • ๋ณ€์ˆ˜ ์ง€์—ญํ™”
  • ๋ฐ”์ดํŠธ์ฝ”๋“œ ๋ถ„์„

์ผ๋ฐ˜ ์ฝ”๋“œ vs ์ตœ์ ํ™” ์ฝ”๋“œ ์„ฑ๋Šฅ ๋น„๊ต

๋‹ค์–‘ํ•œ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์˜ ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ์ด๋‹ค.

๊ธฐ๋ฒ• ์ผ๋ฐ˜ ์ฝ”๋“œ ์ตœ์ ํ™” ์ฝ”๋“œ ์„ฑ๋Šฅ ๊ฐœ์„  ์ ํ•ฉํ•œ ์ƒํ™ฉ
๋ฆฌ์ŠคํŠธ ์ปดํ”„๋ฆฌํ—จ์…˜ for ๋ฃจํ”„์™€ append [x for x in range(n)] 30~50% ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ ์ž‘์—…
์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์ „์ฒด ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ yield ์‚ฌ์šฉ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๊ฐ์†Œ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
์บ์‹ฑ ์ค‘๋ณต ๊ณ„์‚ฐ @lru_cache ์ง€์ˆ˜์  ๊ฐœ์„  ๋ฐ˜๋ณต ๊ณ„์‚ฐ์ด ๋งŽ์€ ์ž‘์—…
์ง€์—ญ ๋ณ€์ˆ˜ ์ „์—ญ ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์ง€์—ญ ๋ณ€์ˆ˜ ์‚ฌ์šฉ 20~30% ๋ฃจํ”„ ๋‚ด๋ถ€ ์ž‘์—…
๋‚ด์žฅ ํ•จ์ˆ˜ ์‚ฌ์šฉ์ž ์ •์˜ ํ•จ์ˆ˜ map, filter ๋“ฑ 10~20% ๋ฐ˜๋ณต ์ฒ˜๋ฆฌ ์ž‘์—…


2๏ธโƒฃ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ตœ์ ํ™”

์ž‘์—…์˜ ํŠน์„ฑ์— ๋งž๊ฒŒ ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

from collections import defaultdict, Counter, deque, namedtuple
import array
import time
import sys
import numpy as np

def measure_operation(setup_code: str, operation_code: str, repeat: int = 3) -> float:
    """์ž‘์—… ์‹œ๊ฐ„ ์ธก์ •
    
    Args:
        setup_code: ์„ค์ • ์ฝ”๋“œ
        operation_code: ์ธก์ •ํ•  ์ž‘์—… ์ฝ”๋“œ
        repeat: ๋ฐ˜๋ณต ํšŸ์ˆ˜
        
    Returns:
        float: ํ‰๊ท  ์‹คํ–‰ ์‹œ๊ฐ„(์ดˆ)
    """
    times = timeit.repeat(operation_code, setup_code, number=100000, repeat=repeat)
    return min(times)

# 1. ์ ์ ˆํ•œ ์ž๋ฃŒ๊ตฌ์กฐ ์„ ํƒ
def membership_test():
    """์ž๋ฃŒ๊ตฌ์กฐ๋ณ„ ๋ฉค๋ฒ„์‹ญ ํ…Œ์ŠคํŠธ ์„ฑ๋Šฅ ๋น„๊ต"""
    # ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ
    n = 10000
    data = list(range(n))
    lookup_value = n // 2
    
    # ๋ฆฌ์ŠคํŠธ ๊ฒ€์ƒ‰ (O(n))
    list_time = measure_time(lambda: lookup_value in data)
    
    # ์„ธํŠธ ๊ฒ€์ƒ‰ (O(1))
    data_set = set(data)
    set_time = measure_time(lambda: lookup_value in data_set)
    
    # ๋”•์…”๋„ˆ๋ฆฌ ๊ฒ€์ƒ‰ (O(1))
    data_dict = {i: True for i in data}
    dict_time = measure_time(lambda: lookup_value in data_dict)
    
    print(f"๋ฆฌ์ŠคํŠธ ๊ฒ€์ƒ‰: {list_time:.6f}์ดˆ")
    print(f"์„ธํŠธ ๊ฒ€์ƒ‰: {set_time:.6f}์ดˆ")
    print(f"๋”•์…”๋„ˆ๋ฆฌ ๊ฒ€์ƒ‰: {dict_time:.6f}์ดˆ")
    print(f"์„ธํŠธ/๋ฆฌ์ŠคํŠธ ์†๋„ ๋น„์œจ: {list_time/set_time:.2f}x")

# 2. Defaultdict ์‚ฌ์šฉ
def word_count_inefficient(words: List[str]) -> Dict[str, int]:
    """์ผ๋ฐ˜ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋‹จ์–ด ์นด์šดํŠธ (๋น„ํšจ์œจ์ )"""
    word_count = {}
    for word in words:
        if word not in word_count:
            word_count[word] = 0
        word_count[word] += 1
    return word_count

def word_count_defaultdict(words: List[str]) -> Dict[str, int]:
    """defaultdict๋กœ ๋‹จ์–ด ์นด์šดํŠธ (ํšจ์œจ์ )"""
    word_count = defaultdict(int)
    for word in words:
        word_count[word] += 1
    return word_count

def word_count_counter(words: List[str]) -> Dict[str, int]:
    """Counter๋กœ ๋‹จ์–ด ์นด์šดํŠธ (๊ฐ€์žฅ ํšจ์œจ์ )"""
    return Counter(words)

# 3. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ๋ฐฐ์—ด
def compare_memory_usage():
    """์ž๋ฃŒ๊ตฌ์กฐ๋ณ„ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋น„๊ต"""
    n = 1000000
    
    # ์ผ๋ฐ˜ ๋ฆฌ์ŠคํŠธ
    lst = list(range(n))
    list_size = sys.getsizeof(lst) + sum(sys.getsizeof(i) for i in lst[:100]) / 100 * n
    
    # array ๋ชจ๋“ˆ (ํƒ€์ž… ์ง€์ •)
    arr = array.array('i', range(n))
    array_size = sys.getsizeof(arr)
    
    # NumPy ๋ฐฐ์—ด
    np_arr = np.arange(n, dtype=np.int32)
    numpy_size = np_arr.nbytes
    
    print(f"๋ฆฌ์ŠคํŠธ ๋ฉ”๋ชจ๋ฆฌ: {list_size/1024/1024:.2f} MB")
    print(f"Array ๋ฉ”๋ชจ๋ฆฌ: {array_size/1024/1024:.2f} MB")
    print(f"NumPy ๋ฉ”๋ชจ๋ฆฌ: {numpy_size/1024/1024:.2f} MB")
    print(f"Array/๋ฆฌ์ŠคํŠธ ๋ฉ”๋ชจ๋ฆฌ ๋น„์œจ: {array_size/list_size:.2f}x")
    print(f"NumPy/๋ฆฌ์ŠคํŠธ ๋ฉ”๋ชจ๋ฆฌ ๋น„์œจ: {numpy_size/list_size:.2f}x")

# 4. ํ ์ตœ์ ํ™”
def queue_operations():
    """ํ ๊ตฌํ˜„๋ณ„ ์„ฑ๋Šฅ ๋น„๊ต"""
    # ๋ฆฌ์ŠคํŠธ๋ฅผ ํ๋กœ ์‚ฌ์šฉ (๋น„ํšจ์œจ์ )
    queue_list = []
    list_time = measure_time(lambda: [queue_list.append(i) for i in range(10000)] + 
                                    [queue_list.pop(0) for _ in range(10000)])
    
    # deque ์‚ฌ์šฉ (ํšจ์œจ์ )
    queue_deque = deque()
    deque_time = measure_time(lambda: [queue_deque.append(i) for i in range(10000)] + 
                                     [queue_deque.popleft() for _ in range(10000)])
    
    print(f"๋ฆฌ์ŠคํŠธ ํ: {list_time:.6f}์ดˆ")
    print(f"Deque ํ: {deque_time:.6f}์ดˆ")
    print(f"๋ฆฌ์ŠคํŠธ/Deque ์†๋„ ๋น„์œจ: {list_time/deque_time:.2f}x")

# 5. namedtuple vs ํด๋ž˜์Šค
Point = namedtuple('Point', ['x', 'y'])

class PointClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def compare_namedtuple_class():
    """namedtuple๊ณผ ํด๋ž˜์Šค ์„ฑ๋Šฅ ๋น„๊ต"""
    # namedtuple ์ƒ์„ฑ ๋ฐ ์ ‘๊ทผ
    namedtuple_time = measure_time(lambda: [Point(i, i*2).x for i in range(10000)])
    
    # ํด๋ž˜์Šค ์ƒ์„ฑ ๋ฐ ์ ‘๊ทผ
    class_time = measure_time(lambda: [PointClass(i, i*2).x for i in range(10000)])
    
    print(f"Namedtuple: {namedtuple_time:.6f}์ดˆ")
    print(f"Class: {class_time:.6f}์ดˆ")
    print(f"์ƒ๋Œ€์  ์„ฑ๋Šฅ: {class_time/namedtuple_time:.2f}x")

# ์ž๋ฃŒ๊ตฌ์กฐ ์ตœ์ ํ™” ๋ฐ๋ชจ
if __name__ == "__main__":
    print("===== ์ž๋ฃŒ๊ตฌ์กฐ๋ณ„ ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ =====")
    membership_test()
    
    print("\n===== ๋‹จ์–ด ์นด์šดํŒ… ๋ฐฉ๋ฒ•๋ณ„ ์„ฑ๋Šฅ =====")
    words = ["apple", "banana", "cherry", "apple", "banana", "apple", "date", "fig"]
    measure_time(word_count_inefficient, words)
    measure_time(word_count_defaultdict, words)
    measure_time(word_count_counter, words)
    
    print("\n===== ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋น„๊ต =====")
    compare_memory_usage()
    
    print("\n===== ํ ๊ตฌํ˜„๋ณ„ ์„ฑ๋Šฅ =====")
    queue_operations()
    
    print("\n===== namedtuple vs ํด๋ž˜์Šค =====")
    compare_namedtuple_class()

โœ… ํŠน์ง•:

  • ํšจ์œจ์ ์ธ ์ž๋ฃŒ๊ตฌ์กฐ
  • ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
  • ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ์‹œ๊ฐ„ ๋ณต์žก๋„ ๊ณ ๋ ค
  • ๊ณต๊ฐ„ ๋ณต์žก๋„ ๊ณ ๋ ค
  • ํŠนํ™”๋œ ์ปฌ๋ ‰์…˜ ํ™œ์šฉ
  • ํƒ€์ž…๋ณ„ ์ตœ์ ํ™”

์ž๋ฃŒ๊ตฌ์กฐ ์„ฑ๋Šฅ ๋น„๊ต

์ฃผ์š” ์ž‘์—…๋ณ„ ์ž๋ฃŒ๊ตฌ์กฐ ์„ฑ๋Šฅ ๋น„๊ต์ด๋‹ค.

์ž‘์—… ๋ฆฌ์ŠคํŠธ ์„ธํŠธ ๋”•์…”๋„ˆ๋ฆฌ Array Deque
๊ฒ€์ƒ‰ O(n) O(1) O(1) O(n) O(n)
์‚ฝ์ž…(๋) O(1) O(1) O(1) O(1) O(1)
์‚ฝ์ž…(์‹œ์ž‘) O(n) N/A N/A O(n) O(1)
์‚ญ์ œ(๋) O(1) O(1) O(1) O(1) O(1)
์‚ญ์ œ(์‹œ์ž‘) O(n) N/A N/A O(n) O(1)
์ •๋ ฌ O(n log n) N/A N/A O(n log n) N/A
๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ ๋‚ฎ์Œ ์ค‘๊ฐ„ ์ค‘๊ฐ„ ๋†’์Œ ์ค‘๊ฐ„
๊ฐ€๋ณ€์„ฑ ๊ฐ€๋ณ€ ๊ฐ€๋ณ€ ๊ฐ€๋ณ€ ๊ฐ€๋ณ€ ๊ฐ€๋ณ€


3๏ธโƒฃ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ตœ์ ํ™”

์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์„ ํƒ๊ณผ ์ตœ์ ํ™”๋ฅผ ํ†ตํ•ด ํŒŒ์ด์ฌ ํ”„๋กœ๊ทธ๋žจ์˜ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

from typing import List, Set, Dict, Tuple, Optional, Any
import heapq
import time
import random
import bisect
from functools import lru_cache

def measure_time(func, *args, **kwargs):
    """ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •"""
    start_time = time.time()
    result = func(*args, **kwargs)
    end_time = time.time()
    print(f"{func.__name__} ์‹คํ–‰ ์‹œ๊ฐ„: {end_time - start_time:.6f}์ดˆ")
    return result

# 1. ์ด์ง„ ๊ฒ€์ƒ‰ ์ตœ์ ํ™”
def linear_search(arr: List[int], target: int) -> int:
    """์„ ํ˜• ๊ฒ€์ƒ‰ ๊ตฌํ˜„ (O(n))"""
    for i, val in enumerate(arr):
        if val == target:
            return i
    return -1

def binary_search(arr: List[int], target: int) -> int:
    """์ด์ง„ ๊ฒ€์ƒ‰ ๊ตฌํ˜„ (O(log n))"""
    left, right = 0, len(arr) - 1
    
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return -1

def bisect_search(arr: List[int], target: int) -> int:
    """bisect ๋ชจ๋“ˆ ์‚ฌ์šฉ ์ด์ง„ ๊ฒ€์ƒ‰"""
    i = bisect.bisect_left(arr, target)
    if i != len(arr) and arr[i] == target:
        return i
    return -1

# 2. ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ ํƒ
def bubble_sort(arr: List[int]) -> List[int]:
    """๋ฒ„๋ธ” ์ •๋ ฌ ๊ตฌํ˜„ (O(nยฒ))"""
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

def insertion_sort(arr: List[int]) -> List[int]:
    """์‚ฝ์ž… ์ •๋ ฌ ๊ตฌํ˜„ (O(nยฒ), ์ž‘์€ ๋ฐฐ์—ด์— ํšจ์œจ์ )"""
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

def merge_sort(arr: List[int]) -> List[int]:
    """๋ณ‘ํ•ฉ ์ •๋ ฌ ๊ตฌํ˜„ (O(n log n))"""
    if len(arr) <= 1:
        return arr
    
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    
    return merge(left, right)

def merge(left: List[int], right: List[int]) -> List[int]:
    """๋ณ‘ํ•ฉ ์ •๋ ฌ์˜ ๋ณ‘ํ•ฉ ๋‹จ๊ณ„"""
    result = []
    i = j = 0
    
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    
    result.extend(left[i:])
    result.extend(right[j:])
    return result

def quick_sort(arr: List[int]) -> List[int]:
    """ํ€ต ์ •๋ ฌ ๊ตฌํ˜„ (ํ‰๊ท  O(n log n))"""
    if len(arr) <= 1:
        return arr
    
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    
    return quick_sort(left) + middle + quick_sort(right)

def timsort_python(arr: List[int]) -> List[int]:
    """ํŒŒ์ด์ฌ ๋‚ด์žฅ ์ •๋ ฌ (Timsort)"""
    return sorted(arr)

# 3. ๋™์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ
def fibonacci_recursive(n: int) -> int:
    """์žฌ๊ท€์  ํ”ผ๋ณด๋‚˜์น˜ (์ง€์ˆ˜์  ์‹œ๊ฐ„ ๋ณต์žก๋„)"""
    if n <= 1:
        return n
    return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)

def fibonacci_dp(n: int) -> int:
    """๋™์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํ”ผ๋ณด๋‚˜์น˜ (์„ ํ˜• ์‹œ๊ฐ„ ๋ณต์žก๋„)"""
    if n <= 1:
        return n
    
    fib = [0] * (n + 1)
    fib[1] = 1
    
    for i in range(2, n + 1):
        fib[i] = fib[i-1] + fib[i-2]
    
    return fib[n]

@lru_cache(maxsize=None)
def fibonacci_memoization(n: int) -> int:
    """๋ฉ”๋ชจ์ด์ œ์ด์…˜ ํ”ผ๋ณด๋‚˜์น˜ (์„ ํ˜• ์‹œ๊ฐ„ ๋ณต์žก๋„)"""
    if n <= 1:
        return n
    return fibonacci_memoization(n-1) + fibonacci_memoization(n-2)

# 4. ๊ทธ๋ฆฌ๋”” ์•Œ๊ณ ๋ฆฌ์ฆ˜
def coin_change_greedy(coins: List[int], amount: int) -> List[int]:
    """๊ทธ๋ฆฌ๋”” ๋™์ „ ๊ตํ™˜ (์ตœ์ ํ•ด๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ์Œ)"""
    coins.sort(reverse=True)
    result = []
    
    for coin in coins:
        while amount >= coin:
            amount -= coin
            result.append(coin)
    
    if amount == 0:
        return result
    return []  # ํ•ด๊ฒฐ์ฑ…์ด ์—†์Œ

def coin_change_dp(coins: List[int], amount: int) -> int:
    """๋™์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋™์ „ ๊ตํ™˜ (์ตœ์ ํ•ด)"""
    # dp[i] = amount i๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ํ•„์š”ํ•œ ์ตœ์†Œ ๋™์ „ ์ˆ˜
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    
    for coin in coins:
        for i in range(coin, amount + 1):
            dp[i] = min(dp[i], dp[i - coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

# 5. ๊ทธ๋ž˜ํ”„ ์•Œ๊ณ ๋ฆฌ์ฆ˜
def dijkstra(graph: Dict[str, Dict[str, int]], start: str) -> Dict[str, int]:
    """๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜ (์ตœ๋‹จ ๊ฒฝ๋กœ)"""
    # ์ดˆ๊ธฐํ™”
    distances = {node: float('infinity') for node in graph}
    distances[start] = 0
    priority_queue = [(0, start)]
    
    while priority_queue:
        current_distance, current_node = heapq.heappop(priority_queue)
        
        # ์ด๋ฏธ ์ฒ˜๋ฆฌํ•œ ๋…ธ๋“œ๋Š” ๊ฑด๋„ˆ๋œ€
        if current_distance > distances[current_node]:
            continue
        
        for neighbor, weight in graph[current_node].items():
            distance = current_distance + weight
            
            # ๋” ์งง์€ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์œผ๋ฉด ์—…๋ฐ์ดํŠธ
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))
    
    return distances

# ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ฑ๋Šฅ ๋น„๊ต
if __name__ == "__main__":
    # ๊ฒ€์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋น„๊ต
    print("===== ๊ฒ€์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋น„๊ต =====")
    arr = sorted(random.sample(range(1000000), 10000))
    target = arr[5000]  # ์กด์žฌํ•˜๋Š” ๊ฐ’
    
    measure_time(linear_search, arr, target)
    measure_time(binary_search, arr, target)
    measure_time(bisect_search, arr, target)
    
    # ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋น„๊ต
    print("\n===== ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋น„๊ต =====")
    # ์ž‘์€ ๋ฐฐ์—ด์—์„œ๋Š” ์‚ฝ์ž… ์ •๋ ฌ์ด ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์Œ
    small_arr = random.sample(range(100), 20)
    measure_time(bubble_sort, small_arr.copy())
    measure_time(insertion_sort, small_arr.copy())
    
    # ํฐ ๋ฐฐ์—ด์—์„œ๋Š” ํšจ์œจ์ ์ธ ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ•„์š”
    large_arr = random.sample(range(10000), 1000)
    measure_time(merge_sort, large_arr.copy())
    measure_time(quick_sort, large_arr.copy())
    measure_time(timsort_python, large_arr.copy())
    
    # ํ”ผ๋ณด๋‚˜์น˜ ๊ตฌํ˜„ ๋น„๊ต
    print("\n===== ํ”ผ๋ณด๋‚˜์น˜ ๊ตฌํ˜„ ๋น„๊ต =====")
    n = 30
    measure_time(fibonacci_recursive, n)
    measure_time(fibonacci_dp, n)
    measure_time(fibonacci_memoization, n)
    
    # ๋™์ „ ๊ตํ™˜ ๋ฌธ์ œ
    print("\n===== ๋™์ „ ๊ตํ™˜ ๋ฌธ์ œ =====")
    coins = [1, 5, 10, 25]
    amount = 63
    greedy_result = measure_time(coin_change_greedy, coins, amount)
    dp_result = measure_time(coin_change_dp, coins, amount)
    print(f"๊ทธ๋ฆฌ๋”” ๊ฒฐ๊ณผ: {greedy_result} (๋™์ „ ์ˆ˜: {len(greedy_result)})")
    print(f"DP ๊ฒฐ๊ณผ: ์ตœ์†Œ ๋™์ „ ์ˆ˜ {dp_result}๊ฐœ")
    
    # ๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜
    print("\n===== ๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜ =====")
    graph = {
        'A': {'B': 2, 'C': 5},
        'B': {'A': 2, 'D': 3, 'E': 1},
        'C': {'A': 5, 'F': 3},
        'D': {'B': 3},
        'E': {'B': 1, 'F': 1},
        'F': {'C': 3, 'E': 1}
    }
    shortest_paths = measure_time(dijkstra, graph, 'A')
    print(f"A๋กœ๋ถ€ํ„ฐ์˜ ์ตœ๋‹จ ๊ฒฝ๋กœ: {shortest_paths}")

โœ… ํŠน์ง•:

  • ํšจ์œจ์ ์ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜
  • ์‹œ๊ฐ„ ๋ณต์žก๋„ ๊ฐœ์„ 
  • ๊ณต๊ฐ„ ๋ณต์žก๋„ ๊ณ ๋ ค
  • ๋™์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ
  • ๋ฉ”๋ชจ์ด์ œ์ด์…˜
  • ๊ทธ๋ฆฌ๋”” ์•Œ๊ณ ๋ฆฌ์ฆ˜
  • ๊ณ ๊ธ‰ ๊ทธ๋ž˜ํ”„ ์•Œ๊ณ ๋ฆฌ์ฆ˜

์‹œ๊ฐ„ ๋ณต์žก๋„ ๋น„๊ต

์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ์„ฑ๋Šฅ ํŠน์„ฑ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ์‹œ๊ฐ„ ๋ณต์žก๋„ ๋น„๊ต์ด๋‹ค.

์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‹œ๊ฐ„ ๋ณต์žก๋„ ๊ณต๊ฐ„ ๋ณต์žก๋„ ์šฉ๋„ ์žฅ์  ๋‹จ์ 
์„ ํ˜• ๊ฒ€์ƒ‰ O(n) O(1) ์ •๋ ฌ๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ ๊ตฌํ˜„ ๊ฐ„๋‹จ ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์— ๋น„ํšจ์œจ์ 
์ด์ง„ ๊ฒ€์ƒ‰ O(log n) O(1) ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ ๋งค์šฐ ๋น ๋ฅธ ๊ฒ€์ƒ‰ ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ ํ•„์š”
๋ฒ„๋ธ” ์ •๋ ฌ O(nยฒ) O(1) ์ž‘์€ ๋ฐ์ดํ„ฐ์…‹ ๊ตฌํ˜„ ๊ฐ„๋‹จ ๋งค์šฐ ๋А๋ฆผ
์‚ฝ์ž… ์ •๋ ฌ O(nยฒ) O(1) ๊ฑฐ์˜ ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ ์ž‘์€ ๋ฐ์ดํ„ฐ์— ํšจ์œจ์  ํฐ ๋ฐ์ดํ„ฐ์— ๋น„ํšจ์œจ์ 
๋ณ‘ํ•ฉ ์ •๋ ฌ O(n log n) O(n) ์•ˆ์ •์  ์ •๋ ฌ ํ•„์š” ์ผ๊ด€๋œ ์„ฑ๋Šฅ ์ถ”๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ํ•„์š”
ํ€ต ์ •๋ ฌ O(n log n) ~ O(nยฒ) O(log n) ์ผ๋ฐ˜์  ์ •๋ ฌ ํ‰๊ท ์ ์œผ๋กœ ๋น ๋ฆ„ ์ตœ์•…์˜ ๊ฒฝ์šฐ O(nยฒ)
๋‹ค์ต์ŠคํŠธ๋ผ O((V+E)log V) O(V) ์ตœ๋‹จ ๊ฒฝ๋กœ ๊ฐ€์ค‘์น˜ ๊ทธ๋ž˜ํ”„์— ํšจ๊ณผ์  ์Œ์ˆ˜ ๊ฐ€์ค‘์น˜ ์ฒ˜๋ฆฌ ๋ถˆ๊ฐ€


4๏ธโƒฃ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ์™€ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

ํŒŒ์ด์ฌ์—์„œ ๋ณ‘๋ ฌ ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

import asyncio
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import multiprocessing
import threading
import time
from typing import List, Callable, Any
import requests
import aiohttp
import math

def measure_time(func: Callable, *args, **kwargs) -> Any:
    """ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •"""
    start_time = time.time()
    result = func(*args, **kwargs)
    end_time = time.time()
    print(f"{func.__name__} ์‹คํ–‰ ์‹œ๊ฐ„: {end_time - start_time:.4f}์ดˆ")
    return result

# 1. CPU ๋ฐ”์šด๋“œ ์ž‘์—…: ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ
def cpu_intensive_task(n: int) -> int:
    """CPU ์ง‘์•ฝ์  ์ž‘์—… (์†Œ์ˆ˜ ๊ณ„์‚ฐ)
    
    Args:
        n: ๊ณ„์‚ฐํ•  ๋ฒ”์œ„
        
    Returns:
        int: ์†Œ์ˆ˜ ๊ฐœ์ˆ˜
    """
    count = 0
    for i in range(2, n + 1):
        is_prime = True
        for j in range(2, int(math.sqrt(i)) + 1):
            if i % j == 0:
                is_prime = False
                break
        if is_prime:
            count += 1
    return count

def process_data_sequential(numbers: List[int]) -> List[int]:
    """์ˆœ์ฐจ ์ฒ˜๋ฆฌ"""
    return [cpu_intensive_task(n) for n in numbers]

def process_data_multiprocessing(numbers: List[int], num_processes: int = None) -> List[int]:
    """๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ ์ฒ˜๋ฆฌ
    
    Args:
        numbers: ์ฒ˜๋ฆฌํ•  ์ˆซ์ž ๋ฆฌ์ŠคํŠธ
        num_processes: ํ”„๋กœ์„ธ์Šค ์ˆ˜ (None์ด๋ฉด CPU ์ฝ”์–ด ์ˆ˜ ์‚ฌ์šฉ)
        
    Returns:
        List[int]: ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ
    """
    if num_processes is None:
        num_processes = multiprocessing.cpu_count()
    
    with ProcessPoolExecutor(max_workers=num_processes) as executor:
        results = list(executor.map(cpu_intensive_task, numbers))
    
    return results

# 2. I/O ๋ฐ”์šด๋“œ ์ž‘์—…: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ
def io_task(url: str) -> int:
    """I/O ์ž‘์—… (์›น ์š”์ฒญ)
    
    Args:
        url: ์š”์ฒญํ•  URL
        
    Returns:
        int: ์‘๋‹ต ํฌ๊ธฐ
    """
    response = requests.get(url)
    return len(response.content)

def process_urls_sequential(urls: List[str]) -> List[int]:
    """์ˆœ์ฐจ ์ฒ˜๋ฆฌ"""
    return [io_task(url) for url in urls]

def process_urls_multithreading(urls: List[str], num_threads: int = 10) -> List[int]:
    """๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ ์ฒ˜๋ฆฌ
    
    Args:
        urls: ์ฒ˜๋ฆฌํ•  URL ๋ฆฌ์ŠคํŠธ
        num_threads: ์Šค๋ ˆ๋“œ ์ˆ˜
        
    Returns:
        List[int]: ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ
    """
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        results = list(executor.map(io_task, urls))
    
    return results

# 3. ๋น„๋™๊ธฐ I/O
async def async_io_task(url: str, session: aiohttp.ClientSession) -> int:
    """๋น„๋™๊ธฐ I/O ์ž‘์—…
    
    Args:
        url: ์š”์ฒญํ•  URL
        session: aiohttp ์„ธ์…˜
        
    Returns:
        int: ์‘๋‹ต ํฌ๊ธฐ
    """
    async with session.get(url) as response:
        content = await response.read()
        return len(content)

async def process_urls_async(urls: List[str]) -> List[int]:
    """๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
    
    Args:
        urls: ์ฒ˜๋ฆฌํ•  URL ๋ฆฌ์ŠคํŠธ
        
    Returns:
        List[int]: ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ
    """
    async with aiohttp.ClientSession() as session:
        tasks = [async_io_task(url, session) for url in urls]
        return await asyncio.gather(*tasks)

# 4. ํ˜ผํ•ฉ ์ ‘๊ทผ: ํ”„๋กœ์„ธ์Šค ํ’€ + ์Šค๋ ˆ๋“œ ํ’€
def hybrid_task(data: tuple) -> int:
    """CPU ๋ฐ I/O ํ˜ผํ•ฉ ์ž‘์—…
    
    Args:
        data: (๊ณ„์‚ฐํ•  ์ˆซ์ž, ์š”์ฒญํ•  URL) ํŠœํ”Œ
        
    Returns:
        int: ๊ณ„์‚ฐ ๊ฒฐ๊ณผ + ์‘๋‹ต ํฌ๊ธฐ
    """
    n, url = data
    
    # CPU ์ž‘์—…
    prime_count = cpu_intensive_task(n)
    
    # I/O ์ž‘์—…
    with ThreadPoolExecutor(max_workers=1) as executor:
        response_size = executor.submit(io_task, url).result()
    
    return prime_count + response_size

def process_hybrid_tasks_sequential(data_list: List[tuple]) -> List[int]:
    """์ˆœ์ฐจ ์ฒ˜๋ฆฌ"""
    return [hybrid_task(data) for data in data_list]

def process_hybrid_tasks_parallel(data_list: List[tuple], num_processes: int = None) -> List[int]:
    """๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ (ํ”„๋กœ์„ธ์Šค ํ’€)
    
    Args:
        data_list: ์ฒ˜๋ฆฌํ•  ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ
        num_processes: ํ”„๋กœ์„ธ์Šค ์ˆ˜
        
    Returns:
        List[int]: ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ
    """
    if num_processes is None:
        num_processes = multiprocessing.cpu_count()
    
    with ProcessPoolExecutor(max_workers=num_processes) as executor:
        results = list(executor.map(hybrid_task, data_list))
    
    return results

# 5. ๋น„๋™๊ธฐ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ
async def async_generator(n: int):
    """๋น„๋™๊ธฐ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ
    
    Args:
        n: ์ƒ์„ฑํ•  ํ•ญ๋ชฉ ์ˆ˜
    """
    for i in range(n):
        await asyncio.sleep(0.01)  # ๋น„๋™๊ธฐ ๋Œ€๊ธฐ
        yield i * i

async def consume_async_generator(n: int) -> List[int]:
    """๋น„๋™๊ธฐ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์†Œ๋น„
    
    Args:
        n: ์ฒ˜๋ฆฌํ•  ํ•ญ๋ชฉ ์ˆ˜
        
    Returns:
        List[int]: ์ˆ˜์ง‘๋œ ๊ฒฐ๊ณผ
    """
    results = []
    async for value in async_generator(n):
        results.append(value)
    return results

# ๋ณ‘๋ ฌ ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ฒค์น˜๋งˆํฌ
def run_benchmarks():
    """๋‹ค์–‘ํ•œ ๋ณ‘๋ ฌ ๋ฐ ๋น„๋™๊ธฐ ์ ‘๊ทผ ๋ฐฉ์‹ ๋ฒค์น˜๋งˆํฌ"""
    # 1. CPU ๋ฐ”์šด๋“œ ์ž‘์—… ๋ฒค์น˜๋งˆํฌ
    print("===== CPU ๋ฐ”์šด๋“œ ์ž‘์—… ๋น„๊ต =====")
    numbers = [10000, 20000, 30000, 40000]
    
    sequential_results = measure_time(process_data_sequential, numbers)
    multiprocessing_results = measure_time(process_data_multiprocessing, numbers)
    
    print(f"์ˆœ์ฐจ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ: {sequential_results}")
    print(f"๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ ๊ฒฐ๊ณผ: {multiprocessing_results}")
    
    # 2. I/O ๋ฐ”์šด๋“œ ์ž‘์—… ๋ฒค์น˜๋งˆํฌ
    print("\n===== I/O ๋ฐ”์šด๋“œ ์ž‘์—… ๋น„๊ต =====")
    urls = [
        "https://www.python.org",
        "https://docs.python.org",
        "https://pypi.org",
        "https://www.djangoproject.com",
        "https://flask.palletsprojects.com"
    ] * 2  # 10๊ฐœ URL
    
    sequential_io = measure_time(process_urls_sequential, urls)
    multithreading_io = measure_time(process_urls_multithreading, urls)
    
    # ๋น„๋™๊ธฐ I/O ์‹คํ–‰
    async def run_async():
        return await process_urls_async(urls)
    
    start_time = time.time()
    async_io = asyncio.run(run_async())
    end_time = time.time()
    print(f"process_urls_async ์‹คํ–‰ ์‹œ๊ฐ„: {end_time - start_time:.4f}์ดˆ")
    
    print(f"์ˆœ์ฐจ I/O ๊ฒฐ๊ณผ ์ˆ˜: {len(sequential_io)}")
    print(f"๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ I/O ๊ฒฐ๊ณผ ์ˆ˜: {len(multithreading_io)}")
    print(f"๋น„๋™๊ธฐ I/O ๊ฒฐ๊ณผ ์ˆ˜: {len(async_io)}")
    
    # 3. ํ˜ผํ•ฉ ์ž‘์—… ๋ฒค์น˜๋งˆํฌ
    print("\n===== ํ˜ผํ•ฉ ์ž‘์—… ๋น„๊ต =====")
    hybrid_data = list(zip(numbers, urls[:len(numbers)]))
    
    sequential_hybrid = measure_time(process_hybrid_tasks_sequential, hybrid_data)
    parallel_hybrid = measure_time(process_hybrid_tasks_parallel, hybrid_data)
    
    print(f"์ˆœ์ฐจ ํ˜ผํ•ฉ ๊ฒฐ๊ณผ: {sequential_hybrid}")
    print(f"๋ณ‘๋ ฌ ํ˜ผํ•ฉ ๊ฒฐ๊ณผ: {parallel_hybrid}")

if __name__ == "__main__":
    run_benchmarks()

โœ… ํŠน์ง•:

  • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
  • ๋ฆฌ์†Œ์Šค ํ™œ์šฉ
  • CPU ๋ฐ”์šด๋“œ ์ตœ์ ํ™”
  • I/O ๋ฐ”์šด๋“œ ์ตœ์ ํ™”
  • ํ˜ผํ•ฉ ์ ‘๊ทผ๋ฒ•
  • ์„ฑ๋Šฅ ํ–ฅ์ƒ

๋ณ‘๋ ฌ/๋น„๋™๊ธฐ ๋ฐฉ์‹ ๋น„๊ต

๋‹ค์–‘ํ•œ ๋ณ‘๋ ฌ ๋ฐ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์˜ ํŠน์„ฑ ๋น„๊ต์ด๋‹ค.

์ ‘๊ทผ ๋ฐฉ์‹ ์‚ฌ์šฉ ์‚ฌ๋ก€ ์žฅ์  ๋‹จ์  ๊ตฌํ˜„ ๋ฐฉ๋ฒ•
๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ CPU ๋ฐ”์šด๋“œ ์ž‘์—… GIL ์šฐํšŒ, ๋‹ค์ค‘ ์ฝ”์–ด ํ™œ์šฉ ์˜ค๋ฒ„ํ—ค๋“œ, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€ ProcessPoolExecutor, multiprocessing
๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ I/O ๋ฐ”์šด๋“œ ์ž‘์—… ์ž์› ๊ณต์œ , ์ ์€ ์˜ค๋ฒ„ํ—ค๋“œ GIL๋กœ ์ธํ•œ ์ œ์•ฝ ThreadPoolExecutor, threading
๋น„๋™๊ธฐ I/O ๋„คํŠธ์›Œํฌ ์ž‘์—… ๋‹จ์ผ ์Šค๋ ˆ๋“œ๋กœ ๋†’์€ ๋™์‹œ์„ฑ ๋น„๋™๊ธฐ ์ฝ”๋“œ ๋ณต์žก์„ฑ asyncio, aiohttp
ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๋ณตํ•ฉ ์ž‘์—… ์ž‘์—… ํŠน์„ฑ์— ๋งž๋Š” ์ตœ์ ํ™” ๊ตฌํ˜„ ๋ณต์žก์„ฑ ํ”„๋กœ์„ธ์Šค ๋‚ด ์Šค๋ ˆ๋“œ ํ’€


5๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”

ํŒŒ์ด์ฌ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์„ ์ตœ์ ํ™”ํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

from typing import Iterator, Generator, List, Dict, Any, Callable
import gc
import sys
import psutil
import os
import weakref
import time
import numpy as np
from memory_profiler import profile

def get_memory_usage() -> float:
    """ํ˜„์žฌ ํ”„๋กœ์„ธ์Šค์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ฐ˜ํ™˜ (MB)"""
    process = psutil.Process(os.getpid())
    return process.memory_info().rss / 1024 / 1024

def print_memory_usage(label: str) -> None:
    """๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ถœ๋ ฅ"""
    print(f"{label}: {get_memory_usage():.2f} MB")

# 1. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
def read_large_file_into_memory(file_path: str) -> List[str]:
    """ํŒŒ์ผ์„ ๋ชจ๋‘ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ (๋น„ํšจ์œจ์ )"""
    with open(file_path, 'r') as f:
        return f.readlines()

def read_large_file_generator(file_path: str) -> Generator[str, None, None]:
    """ํŒŒ์ผ์„ ํ•œ ์ค„์”ฉ ์ฒ˜๋ฆฌํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ (ํšจ์œจ์ )"""
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

def demonstrate_file_reading(file_path: str) -> None:
    """ํŒŒ์ผ ์ฝ๊ธฐ ๋ฐฉ์‹ ๋น„๊ต"""
    # ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์ƒ์„ฑ
    with open(file_path, 'w') as f:
        for i in range(1000000):
            f.write(f"Line {i}\n")
    
    # ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ ๋กœ๋“œ
    print_memory_usage("ํŒŒ์ผ ์ฝ๊ธฐ ์ „")
    try:
        lines = read_large_file_into_memory(file_path)
        print(f"์ „์ฒด ๋กœ๋“œ: {len(lines)} ์ค„")
    except MemoryError:
        print("๋ฉ”๋ชจ๋ฆฌ ์—๋Ÿฌ! ํŒŒ์ผ์ด ๋„ˆ๋ฌด ํฝ๋‹ˆ๋‹ค.")
    print_memory_usage("์ „์ฒด ํŒŒ์ผ ๋กœ๋“œ ํ›„")
    
    # ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ
    print_memory_usage("์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ ์ „")
    count = 0
    for line in read_large_file_generator(file_path):
        count += 1
        if count % 100000 == 0:
            print(f"์ฒ˜๋ฆฌ ์ค‘: {count} ์ค„")
    print(f"์ œ๋„ˆ๋ ˆ์ดํ„ฐ: {count} ์ค„ ์ฒ˜๋ฆฌ")
    print_memory_usage("์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ ํ›„")
    
    # ํŒŒ์ผ ์‚ญ์ œ
    os.remove(file_path)

# 2. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€
class ResourceManager:
    """๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ ํด๋ž˜์Šค"""
    
    def __init__(self):
        """์ดˆ๊ธฐํ™”"""
        self.resources = []
    
    def add_resource(self, resource: Any) -> None:
        """๋ฆฌ์†Œ์Šค ์ถ”๊ฐ€"""
        self.resources.append(resource)
    
    def cleanup(self) -> None:
        """๋ฆฌ์†Œ์Šค ์ •๋ฆฌ"""
        for resource in self.resources:
            # ๋ฆฌ์†Œ์Šค ํ•ด์ œ ๋กœ์ง (ํŒŒ์ผ, ์—ฐ๊ฒฐ ๋“ฑ)
            if hasattr(resource, 'close') and callable(resource.close):
                resource.close()
        self.resources.clear()
        
        # ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๊ฐ•์ œ ์‹คํ–‰
        gc.collect()

def simulate_memory_leak() -> None:
    """๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
    class Resource:
        """๋ฆฌ์†Œ์Šค ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํด๋ž˜์Šค"""
        def __init__(self, size: int):
            self.data = [0] * size
        
        def close(self):
            self.data = None
    
    # ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ์žˆ๋Š” ์ฝ”๋“œ
    print_memory_usage("๋ˆ„์ˆ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์ž‘")
    leaky_resources = []
    for i in range(10):
        leaky_resources.append(Resource(1000000))  # ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜
        print_memory_usage(f"๋ฆฌ์†Œ์Šค {i+1}๊ฐœ ์ถ”๊ฐ€")
    
    # ์ฐธ์กฐ๋ฅผ ์ œ๊ฑฐํ•ด๋„ ๋ฉ”๋ชจ๋ฆฌ๋Š” ํšŒ์ˆ˜๋˜์ง€ ์•Š์Œ
    leaky_resources = None
    print_memory_usage("์ฐธ์กฐ ์ œ๊ฑฐ ํ›„")
    gc.collect()
    print_memory_usage("๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ํ›„")
    
    # ์ ์ ˆํ•œ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ
    print_memory_usage("๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ์ž ์‹œ์ž‘")
    manager = ResourceManager()
    for i in range(10):
        manager.add_resource(Resource(1000000))
        print_memory_usage(f"๊ด€๋ฆฌ๋œ ๋ฆฌ์†Œ์Šค {i+1}๊ฐœ ์ถ”๊ฐ€")
    
    # ์ ์ ˆํ•œ ์ •๋ฆฌ
    manager.cleanup()
    print_memory_usage("๋ฆฌ์†Œ์Šค ์ •๋ฆฌ ํ›„")

# 3. ์•ฝํ•œ ์ฐธ์กฐ ์‚ฌ์šฉ
def demonstrate_weak_references() -> None:
    """์•ฝํ•œ ์ฐธ์กฐ ์‚ฌ์šฉ ์˜ˆ์‹œ"""
    class LargeObject:
        """ํฐ ๊ฐ์ฒด ์‹œ๋ฎฌ๋ ˆ์ด์…˜"""
        def __init__(self, name: str):
            self.name = name
            self.data = [0] * 1000000
    
    # ์ผ๋ฐ˜ ์ฐธ์กฐ
    print_memory_usage("์‹œ์ž‘")
    obj = LargeObject("big_object")
    print_memory_usage("๊ฐ์ฒด ์ƒ์„ฑ ํ›„")
    
    # ์•ฝํ•œ ์ฐธ์กฐ
    weak_ref = weakref.ref(obj)
    print(f"์•ฝํ•œ ์ฐธ์กฐ ์œ ํšจ: {weak_ref() is not None}")
    
    # ์›๋ณธ ์ฐธ์กฐ ์ œ๊ฑฐ
    obj = None
    print_memory_usage("์›๋ณธ ์ฐธ์กฐ ์ œ๊ฑฐ ํ›„")
    print(f"์•ฝํ•œ ์ฐธ์กฐ ์œ ํšจ: {weak_ref() is not None}")
    
    # ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜
    gc.collect()
    print_memory_usage("๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ํ›„")

# 4. ์บ์‹œ ์ตœ์ ํ™”
def demonstrate_caching() -> None:
    """์บ์‹œ ์ตœ์ ํ™” ์˜ˆ์‹œ"""
    # ๋น„ํšจ์œจ์ ์ธ ์บ์‹œ (๋ฌด์ œํ•œ ์„ฑ์žฅ)
    unlimited_cache = {}
    
    # LRU ์บ์‹œ (์ œํ•œ๋œ ํฌ๊ธฐ)
    from functools import lru_cache
    
    @lru_cache(maxsize=100)
    def fibonacci(n: int) -> int:
        if n < 2:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    # ์บ์‹œ ์„ฑ์žฅ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
    print_memory_usage("์บ์‹œ ์‚ฌ์šฉ ์ „")
    
    # ๋ฌด์ œํ•œ ์บ์‹œ ์„ฑ์žฅ
    for i in range(1000):
        key = f"key_{i}"
        unlimited_cache[key] = [0] * 10000  # ๊ฐ ํ•ญ๋ชฉ๋‹น ~80KB
        if i % 100 == 0:
            print_memory_usage(f"๋ฌด์ œํ•œ ์บ์‹œ {i}๊ฐœ ํ•ญ๋ชฉ")
    
    # LRU ์บ์‹œ ์‚ฌ์šฉ
    for i in range(200):
        fibonacci(i)
    print_memory_usage("LRU ์บ์‹œ ์‚ฌ์šฉ ํ›„")
    
    # ์บ์‹œ ์ •๋ฆฌ
    unlimited_cache.clear()
    gc.collect()
    print_memory_usage("์บ์‹œ ์ •๋ฆฌ ํ›„")

# 5. NumPy ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๋ทฐ ์‚ฌ์šฉ
def demonstrate_numpy_memory_views() -> None:
    """NumPy ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๋ทฐ ์ตœ์ ํ™” ์˜ˆ์‹œ"""
    # ์ผ๋ฐ˜ ํŒŒ์ด์ฌ ๋ฆฌ์ŠคํŠธ
    print_memory_usage("์‹œ์ž‘")
    py_list = list(range(10000000))  # ํฐ ๋ฆฌ์ŠคํŠธ
    print_memory_usage("ํŒŒ์ด์ฌ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ ํ›„")
    
    # NumPy ๋ฐฐ์—ด
    np_array = np.arange(10000000)  # ๋™์ผํ•œ ํฌ๊ธฐ์˜ NumPy ๋ฐฐ์—ด
    print_memory_usage("NumPy ๋ฐฐ์—ด ์ƒ์„ฑ ํ›„")
    
    # ๋ฉ”๋ชจ๋ฆฌ ๋ทฐ
    buffer = bytes(range(255)) * 1000  # ๋ฐ”์ดํŠธ ๋ฒ„ํผ
    mem_view = memoryview(buffer)  # ๋ณต์‚ฌ ์—†์ด ๋ฒ„ํผ ์ฐธ์กฐ
    print_memory_usage("๋ฉ”๋ชจ๋ฆฌ ๋ทฐ ์ƒ์„ฑ ํ›„")
    
    # ์Šฌ๋ผ์ด์‹ฑ ๋น„๊ต
    # ํŒŒ์ด์ฌ ๋ฆฌ์ŠคํŠธ ์Šฌ๋ผ์ด์‹ฑ (๋ณต์‚ฌ๋ณธ ์ƒ์„ฑ)
    py_slice = py_list[1000000:2000000]
    print_memory_usage("ํŒŒ์ด์ฌ ์Šฌ๋ผ์ด์Šค ํ›„")
    
    # NumPy ์Šฌ๋ผ์ด์Šค (๋ทฐ)
    np_slice = np_array[1000000:2000000]
    print_memory_usage("NumPy ์Šฌ๋ผ์ด์Šค ํ›„")
    
    # ์ •๋ฆฌ
    py_list = None
    np_array = None
    buffer = None
    mem_view = None
    py_slice = None
    np_slice = None
    gc.collect()
    print_memory_usage("์ •๋ฆฌ ํ›„")

# ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ์‹คํ–‰
if __name__ == "__main__":
    print("===== ์ œ๋„ˆ๋ ˆ์ดํ„ฐ์™€ ํŒŒ์ผ ์ฒ˜๋ฆฌ =====")
    demonstrate_file_reading("large_test_file.txt")
    
    print("\n===== ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ =====")
    simulate_memory_leak()
    
    print("\n===== ์•ฝํ•œ ์ฐธ์กฐ ํ™œ์šฉ =====")
    demonstrate_weak_references()
    
    print("\n===== ์บ์‹œ ์ตœ์ ํ™” =====")
    demonstrate_caching()
    
    print("\n===== NumPy ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ๋ทฐ =====")
    demonstrate_numpy_memory_views()

โœ… ํŠน์ง•:

  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ
  • ๋ฆฌ์†Œ์Šค ํ•ด์ œ
  • ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜
  • ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ™œ์šฉ
  • ์•ฝํ•œ ์ฐธ์กฐ
  • ๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ๋ง
  • ์บ์‹œ ์ „๋žต

๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ์ „๋žต ๋น„๊ต

๋‹ค์–‘ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™” ์ „๋žต์˜ ํšจ๊ณผ ๋น„๊ต์ด๋‹ค.

์ „๋žต ํšจ๊ณผ ์ ์šฉ ๋Œ€์ƒ ์žฅ์  ๋‹จ์ 
์ œ๋„ˆ๋ ˆ์ดํ„ฐ ๋งค์šฐ ํผ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์†Œํ™” ์ž„์˜ ์ ‘๊ทผ ๋ถˆ๊ฐ€
๋ฉ”๋ชจ๋ฆฌ ๋ทฐ ํผ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ ์—†๋Š” ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ์ œํ•œ๋œ ์‚ฌ์šฉ ์‚ฌ๋ก€
NumPy ํผ ์ˆ˜์น˜ ๊ณ„์‚ฐ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ, ์„ฑ๋Šฅ ๋ณ„๋„ ๋ชจ๋“ˆ ํ•„์š”
์•ฝํ•œ ์ฐธ์กฐ ์ค‘๊ฐ„ ๊ฐ์ฒด ์บ์‹œ, ๊ด€์ฐฐ์ž ์ฐธ์กฐ ์‚ฌ์ดํด ๋ฐฉ์ง€ ํŠน์ • ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๊ตญํ•œ
LRU ์บ์‹œ ์ค‘๊ฐ„ ๋ฐ˜๋ณต ๊ณ„์‚ฐ ๊ณ„์‚ฐ ๋น„์šฉ ์ ˆ๊ฐ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€
๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ ๋‹ค์–‘ํ•จ ํŒŒ์ผ, ์—ฐ๊ฒฐ ๋“ฑ ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ ๋ช…์‹œ์  ๊ด€๋ฆฌ ํ•„์š”


์ฃผ์š” ํŒ

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

  • ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ ํ™œ์šฉ
  • ๋ณ‘๋ชฉ ์ง€์  ์‹๋ณ„
  • ์ ์ ˆํ•œ ์ž๋ฃŒ๊ตฌ์กฐ ์„ ํƒ
  • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ณต์žก๋„ ๊ณ ๋ ค
  • ์บ์‹œ ์ „๋žต ์ˆ˜๋ฆฝ
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ™œ์šฉ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”
  • ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง
  • ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”

์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ณผ์ •

ํšจ๊ณผ์ ์ธ ํŒŒ์ด์ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋‹จ๊ณ„๋ณ„ ์ ‘๊ทผ๋ฒ•์ด๋‹ค.

  1. ์ธก์ •๊ณผ ํ”„๋กœํŒŒ์ผ๋ง

    • ์‹œ๊ฐ„/๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ธก์ •
    • ๋ณ‘๋ชฉ ์ง€์  ์‹๋ณ„
    • ๊ฐ€์„ค ์„ค์ •
  2. ์ฝ”๋“œ ์ตœ์ ํ™”

    • ํšจ์œจ์ ์ธ ์ž๋ฃŒ๊ตฌ์กฐ ์„ ํƒ
    • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ฐœ์„ 
    • ๋‚ด์žฅ ํ•จ์ˆ˜ ํ™œ์šฉ
  3. ์ปดํ“จํŒ… ๋ฆฌ์†Œ์Šค ์ตœ์ ํ™”

    • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ ์šฉ
    • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ™œ์šฉ
    • ์บ์‹ฑ ์ „๋žต ์ ์šฉ
  4. ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”

    • ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ
    • ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€
    • ๋ฐ์ดํ„ฐ ์••์ถ•/์ธ์ฝ”๋”ฉ
  5. ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค ์ตœ์ ํ™”

    • I/O ์ตœ์ ํ™”
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ๊ฐœ์„ 
    • ๋„คํŠธ์›Œํฌ ํ†ต์‹  ํšจ์œจํ™”


โš ๏ธ **GitHub.com Fallback** โš ๏ธ