KR_Profiling - somaz94/python-study GitHub Wiki
cProfile์ Python ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋ ๊ฐ๋ ฅํ ํ๋กํ์ผ๋ง ๋๊ตฌ์ด๋ค.
import cProfile
import pstats
import io
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# ๊ธฐ๋ณธ ํ๋กํ์ผ๋ง ์คํ
def basic_profiling():
profiler = cProfile.Profile()
profiler.enable()
# ํ
์คํธํ ์ฝ๋
result = fibonacci(30)
profiler.disable()
stats = pstats.Stats(profiler).sort_stats('cumulative')
stats.print_stats(20) # ์์ 20๊ฐ ํญ๋ชฉ๋ง ์ถ๋ ฅ
# ๊ฒฐ๊ณผ๋ฅผ ํ์ผ๋ก ์ ์ฅ
stats.dump_stats('profile.stats')
return result
# ์ปจํ
์คํธ ๋งค๋์ ๋ฅผ ์ฌ์ฉํ ํ๋กํ์ผ๋ง
def context_manager_profiling():
result = None
with cProfile.Profile() as pr:
result = fibonacci(30)
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats('tottime')
ps.print_stats(10)
print(s.getvalue())
return result
# ํจ์ ์ง์ ํ๋กํ์ผ๋ง
def profile_function():
cProfile.run('fibonacci(30)', 'profile.stats')
# ๊ฒฐ๊ณผ ๋ถ์
stats = pstats.Stats('profile.stats')
stats.strip_dirs().sort_stats('cumtime').print_stats(10)
# ๊ฒฐ๊ณผ ํํฐ๋ง ๋ฐ ์๊ฐํ
def analyze_profile_results():
stats = pstats.Stats('profile.stats')
# ํธ์ถ ์๊ฐ์ด 0.1์ด ์ด์์ธ ํจ์๋ง ํ์
stats.strip_dirs().sort_stats('cumtime').print_stats(.1)
# ํน์ ํจ์๋ง ์ ํํด์ ํ์
stats.print_callers('fibonacci')
# ํธ์ถ์ ์ ๋ณด ํ์
stats.print_callees('fibonacci')
# ๋ค์ํ ์ ๋ ฌ ๋ฐฉ์
stats.sort_stats('ncalls').print_stats(5) # ํธ์ถ ํ์
stats.sort_stats('tottime').print_stats(5) # ์์ ์คํ ์๊ฐ
stats.sort_stats('cumtime').print_stats(5) # ๋์ ์คํ ์๊ฐ
if __name__ == "__main__":
basic_profiling()
# context_manager_profiling()
# profile_function()
# analyze_profile_results()
โ
ํน์ง:
- ํจ์ ํธ์ถ ํ์ ๋ฐ ํธ์ถ ๊ด๊ณ ์ถ์
- ๋์ ๋ฐ ์์ ์คํ ์๊ฐ ์ธก์
- ์ฌ๋ฌ ์ ๋ ฌ ๋ฐฉ์์ผ๋ก ๊ฒฐ๊ณผ ๋ถ์ ๊ฐ๋ฅ
- ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ถ๊ฐ ์ค์น ๋ถํ์
- ๊ฒฐ๊ณผ๋ฅผ ํ์ผ๋ก ์ ์ฅํ์ฌ ํ์ ๋ถ์ ์ง์
- ๋ค์ํ ์ฌ์ฉ ๋ฐฉ์ (ํจ์, ์ปจํ ์คํธ ๋งค๋์ )
- ํธ์ถ ๊ทธ๋ํ ์๊ฐํ ๋๊ตฌ์ ํตํฉ ๊ฐ๋ฅ
- ๋ฎ์ ์ค๋ฒํค๋๋ก ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฌ์ฉ ๊ฐ๋ฅ
line_profiler๋ ์ฝ๋์ ๊ฐ ๋ผ์ธ๋ณ ์คํ ์๊ฐ์ ์ ๋ฐํ๊ฒ ์ธก์ ํ๋ ์ ๋ฌธ ํ๋กํ์ผ๋ง ๋๊ตฌ์ด๋ค.
# ๋จผ์ ์ค์น: pip install line_profiler
from line_profiler import LineProfiler
import numpy as np
import time
# ํ๋กํ์ผ๋งํ ํจ์๋ฅผ ์ ์
def process_data(data):
"""๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์"""
result = []
# ๋ฐ์ดํฐ ํํฐ๋ง
filtered_data = [item for item in data if item > 0]
# ๋ฐ์ดํฐ ๋ณํ
for item in filtered_data:
# ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ์์
์๋ฎฌ๋ ์ด์
time.sleep(0.001)
processed = item * 2
result.append(processed)
# ๊ฒฐ๊ณผ ์ ๋ ฌ ๋ฐ ๊ณ์ฐ
result.sort()
total = sum(result)
average = total / len(result) if result else 0
# NumPy ์ฌ์ฉ ์ฐ์ฐ
np_array = np.array(result)
std_dev = np.std(np_array) if len(np_array) > 0 else 0
return {
'filtered_count': len(filtered_data),
'result_count': len(result),
'total': total,
'average': average,
'std_dev': std_dev
}
# ํจ์ ๋ฐ์ฝ๋ ์ดํฐ ๋ฐฉ์
@profile # ์ฐธ๊ณ : ์ด ๋ฐ์ฝ๋ ์ดํฐ๋ ์คํ์ kernprof์ ์ํด ์ธ์๋จ
def process_with_decorator(data):
"""๋ฐ์ฝ๋ ์ดํฐ๋ก ํ๋กํ์ผ๋ง๋๋ ํจ์"""
return process_data(data)
# ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ํ๋กํ์ผ๋ง ์ค์
def manual_profiling():
# ํ๋กํ์ผ๋ฌ ์ธ์คํด์ค ์์ฑ
profiler = LineProfiler()
# ํ๋กํ์ผ๋งํ ํจ์ ์ถ๊ฐ
profiler.add_function(process_data)
# ํ
์คํธ ๋ฐ์ดํฐ ์์ฑ (์ผ๋ถ ์์ ํฌํจ)
data = list(range(-5000, 5000))
# ํ๋กํ์ผ๋ง ์คํ
profiler.runcall(process_data, data)
# ๊ฒฐ๊ณผ ์ถ๋ ฅ
profiler.print_stats(output_unit=1e-3) # ๋ฐ๋ฆฌ์ด ๋จ์๋ก ์ถ๋ ฅ
# ๊ฒฐ๊ณผ๋ฅผ ํ์ผ๋ก ์ ์ฅ
with open('line_profile_results.txt', 'w') as f:
profiler.print_stats(stream=f, output_unit=1e-3)
# HTML ํ์์ผ๋ก ์ ์ฅ (์๊ฐ์ ๋ถ์์ ์ ์ฉ)
profiler.dump_stats('line_profile.lprof')
# ์ฌ๋ฌ ํจ์ ๋์ ํ๋กํ์ผ๋ง
def multi_function_profiling():
# ์ถ๊ฐ ํจ์ ์ ์
def helper_function(data):
"""๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ ํจ์"""
return [x for x in data if isinstance(x, (int, float))]
def another_function(data):
"""๋ค๋ฅธ ์์
์ ์ํํ๋ ํจ์"""
if not data:
return []
return [x ** 2 for x in data]
# ํ๋กํ์ผ๋ฌ ์ค์
profiler = LineProfiler()
profiler.add_function(process_data)
profiler.add_function(helper_function)
profiler.add_function(another_function)
# ์คํ ์ฝ๋ ์์ฑ
def main_function():
data = list(range(-100, 100))
cleaned_data = helper_function(data)
processed_data = process_data(cleaned_data)
squared_data = another_function(cleaned_data)
return processed_data, squared_data
# ํ๋กํ์ผ๋ง ์คํ
profiler.runcall(main_function)
profiler.print_stats()
if __name__ == "__main__":
manual_profiling()
# multi_function_profiling()
# ์ปค๋งจ๋๋ผ์ธ์์ ์คํ ์:
# python -m kernprof -l script_name.py
# python -m line_profiler script_name.py.lprof
โ
ํน์ง:
- ์ฝ๋ ๋ผ์ธ๋ณ ์ ๋ฐํ ์คํ ์๊ฐ ์ธก์
- CPU ์๊ฐ ๋ฐ ํํธ ์นด์ดํธ ์์ธ ๋ถ์
- ๋ณ๋ชฉ ํ์์ด ์ ํํ ์ด๋ ๋ผ์ธ์์ ๋ฐ์ํ๋์ง ์๋ณ
- ๋ฐ์ฝ๋ ์ดํฐ ๋๋ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ ์ฐํ๊ฒ ์ฌ์ฉ
- ๊ฒฐ๊ณผ๋ฅผ ๋ค์ํ ํ์(ํ ์คํธ, HTML)์ผ๋ก ์ถ๋ ฅ
- ์ฌ๋ฌ ํจ์๋ฅผ ๋์์ ํ๋กํ์ผ๋งํ ์ ์๋ ๊ธฐ๋ฅ
- ์๊ฐ์ ๋ถ์์ ์ํ ๊ฒฐ๊ณผ ํ์ผ ์์ฑ
- ๋ง์ดํฌ๋ก์ด ๋จ์์ ๊ณ ์ ๋ฐ ์ธก์ ์ง์
memory_profiler๋ Python ์ฝ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ๋ผ์ธ๋ณ๋ก ์ธก์ ํ๊ณ ๋ถ์ํ๋ ๋๊ตฌ์ด๋ค.
# ๋จผ์ ์ค์น: pip install memory_profiler
from memory_profiler import profile
import numpy as np
import matplotlib.pyplot as plt
from functools import lru_cache
import gc
import os
import tempfile
# ๋ผ์ธ๋ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ธก์
@profile
def memory_heavy_function():
"""๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ ํจ์ ์์"""
results = []
# ๋์ฉ๋ ๋ฆฌ์คํธ ์์ฑ
print("๋์ฉ๋ ๋ฆฌ์คํธ ์์ฑ")
big_list = [i for i in range(1000000)]
results.append(sum(big_list))
# ๋์
๋๋ฆฌ ์์ฑ
print("๋์
๋๋ฆฌ ์์ฑ")
big_dict = {i: str(i) for i in range(100000)}
results.append(len(big_dict))
# NumPy ๋ฐฐ์ด ์์ฑ
print("NumPy ๋ฐฐ์ด ์์ฑ")
big_array = np.random.random((1000, 1000))
results.append(big_array.mean())
# ์ค์ฒฉ ๋ฆฌ์คํธ
print("์ค์ฒฉ ๋ฆฌ์คํธ ์์ฑ")
nested_list = [[i for i in range(1000)] for _ in range(100)]
results.append(sum(sum(nested_list, [])))
# ๋ฌธ์์ด ์กฐ์
print("๋ฌธ์์ด ์กฐ์")
text = "hello" * 100000
results.append(len(text))
# ๋ฆฌ์์ค ์ ๋ฆฌ (๋ฉ๋ชจ๋ฆฌ ํด์ )
print("๋ฆฌ์์ค ์ ๋ฆฌ")
del big_list, big_dict, big_array, nested_list, text
gc.collect() # ๊ฐ๋น์ง ์ปฌ๋ ์
๊ฐ์ ์คํ
return results
# ๋ฉ๋ชจ๋ฆฌ ๋์ ํจํด ์์
class DataNode:
"""๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์์ฐํ๊ธฐ ์ํ ํด๋์ค"""
_cache = {} # ํด๋์ค ๋ณ์๋ก ๋ชจ๋ ์ธ์คํด์ค๊ฐ ๊ณต์
def __init__(self, name, data):
self.name = name
self.data = data
# ์๊ธฐ ์์ ์ ์บ์์ ์ ์ฅ (๋ฉ๋ชจ๋ฆฌ ๋์ ํจํด)
DataNode._cache[name] = self
@classmethod
def clear_cache(cls):
cls._cache.clear()
@profile
def memory_leak_demo():
"""๋ฉ๋ชจ๋ฆฌ ๋์ ์์ฐ"""
# ๊ฐ๋น์ง ์ปฌ๋ ํฐ ์ค์
gc.disable() # ์์ฐ์ ์ํด ๊ฐ๋น์ง ์ปฌ๋ ์
๋นํ์ฑํ
print("1. ์ด๊ธฐ ์ํ")
print("2. ๊ฐ์ฒด ์์ฑ ์์")
# ๋ฉ๋ชจ๋ฆฌ์ ์ง์์ ์ผ๋ก ๋จ๋ ๊ฐ์ฒด ์์ฑ
for i in range(1000):
DataNode(f"node_{i}", np.random.random((100, 100)))
print("3. ๋ช
์์ ์ธ ์ฐธ์กฐ ์ ๊ฑฐ")
# ๋ก์ปฌ ์ฐธ์กฐ๋ ์ฌ๋ผ์ง์ง๋ง ํด๋์ค ์บ์์ ๋จ์์์
print("4. ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ ์๋")
gc.collect() # ์๋์ผ๋ก ๊ฐ๋น์ง ์ปฌ๋ ์
์คํ
print("5. ์บ์ ์ ๋ฆฌ")
DataNode.clear_cache() # ์บ์ ์ ๋ฆฌํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์ ํด๊ฒฐ
gc.collect()
gc.enable() # ๊ฐ๋น์ง ์ปฌ๋ ํฐ ๋ค์ ํ์ฑํ
return "์๋ฃ"
# ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง ๋๊ตฌ
def monitor_memory_usage(func, *args, **kwargs):
"""ํจ์ ์คํ ์ค ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง"""
import time
from memory_profiler import memory_usage
# ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ธก์
mem_usage = []
def recorder():
# 0.1์ด ๊ฐ๊ฒฉ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ธฐ๋ก
mem_usage.append(memory_usage(os.getpid(), interval=0.1, timeout=1))
import threading
t = threading.Thread(target=recorder)
t.daemon = True
# ์ธก์ ์์
t.start()
start_time = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start_time
# ์ธก์ ๊ฒฐ๊ณผ ๊ฐ์ํ
if mem_usage:
# ํํํ
all_measurements = [item for sublist in mem_usage for item in sublist]
plt.figure(figsize=(10, 6))
plt.plot(all_measurements)
plt.title(f'{func.__name__} ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋')
plt.xlabel('์๊ฐ (0.1์ด ๋จ์)')
plt.ylabel('๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ (MB)')
plt.grid(True)
plt.savefig(f'{func.__name__}_memory_profile.png')
plt.close()
print(f"์ต๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: {max(all_measurements):.2f} MB")
print(f"ํ๊ท ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: {sum(all_measurements)/len(all_measurements):.2f} MB")
print(f"์คํ ์๊ฐ: {elapsed:.2f} ์ด")
return result
# ๋์ฉ๋ ํ์ผ ์ฒ๋ฆฌ ์์
@profile
def process_large_file():
"""๋์ฉ๋ ํ์ผ์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ"""
# ํ
์คํธ์ฉ ๋์ฉ๋ ํ์ผ ์์ฑ
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
file_path = temp_file.name
# ์ฝ 100MB ํฌ๊ธฐ์ ํ์ผ ์์ฑ
for i in range(5_000_000):
temp_file.write(f"{i},{i*2},{i*3}\n")
print(f"ํ์ผ ์์ฑ ์๋ฃ: {file_path}")
# ๋นํจ์จ์ ์ธ ๋ฐฉ์ (์ ์ฒด ํ์ผ์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋)
def inefficient_read():
print("๋นํจ์จ์ ๋ฐฉ์: ์ ์ฒด ํ์ผ ๋ก๋")
with open(file_path, 'r') as f:
data = f.readlines() # ์ ์ฒด ํ์ผ์ ๋ฉ๋ชจ๋ฆฌ์ ๋ก๋
return len(data)
# ํจ์จ์ ์ธ ๋ฐฉ์ (๋ผ์ธ๋ณ ์ฒ๋ฆฌ)
def efficient_read():
print("ํจ์จ์ ๋ฐฉ์: ๋ผ์ธ๋ณ ์ฒ๋ฆฌ")
count = 0
total = 0
with open(file_path, 'r') as f:
for line in f: # ํ ๋ฒ์ ํ ๋ผ์ธ์ฉ ์ฒ๋ฆฌ
values = line.strip().split(',')
if values and len(values) >= 3:
total += int(values[2])
count += 1
return count, total
# ๋ ๋ฐฉ์์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋น๊ต
try:
# ๋นํจ์จ์ ๋ฐฉ์ ์คํ (์ฃผ์: ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ๊ฐ๋ฅ์ฑ ์์)
# inefficient_count = inefficient_read()
# print(f"๋นํจ์จ์ ๋ฐฉ์์ผ๋ก ์ฝ์ ๋ผ์ธ ์: {inefficient_count}")
# ํจ์จ์ ๋ฐฉ์ ์คํ
efficient_count, total = efficient_read()
print(f"ํจ์จ์ ๋ฐฉ์์ผ๋ก ์ฝ์ ๋ผ์ธ ์: {efficient_count}, ํฉ๊ณ: {total}")
finally:
# ์์ ํ์ผ ์ญ์
try:
os.unlink(file_path)
print(f"์์ ํ์ผ ์ญ์ ์๋ฃ: {file_path}")
except:
pass
return "ํ์ผ ์ฒ๋ฆฌ ์๋ฃ"
if __name__ == "__main__":
# ๊ธฐ๋ณธ ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง
# memory_heavy_function()
# ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ฐ
# memory_leak_demo()
# ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง
# monitor_memory_usage(memory_heavy_function)
# ๋์ฉ๋ ํ์ผ ์ฒ๋ฆฌ
# process_large_file()
# ๋ค์ํ ๋ฐฉ์์ ๋ฉ๋ชจ๋ฆฌ ํ๋กํ์ผ๋ง
# python -m memory_profiler script_name.py
pass
โ
ํน์ง:
- ์ฝ๋ ๊ฐ ๋ผ์ธ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ ๋ฐ ์ธก์
- ๋ฉ๋ชจ๋ฆฌ ๋์ ํจํด ํ์ง ๋ฐ ๋ถ์
- ๋ฐ์ฝ๋ ์ดํฐ ๋๋ ๋ช ๋ น์ค ๋๊ตฌ๋ก ๊ฐํธํ๊ฒ ์ฌ์ฉ
- ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ต์ ํ์ ์ ์ฉ
- ์ค์๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง ๊ธฐ๋ฅ
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ทธ๋ํ ์๊ฐํ ์ง์
- ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ฐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ์ ๋ต ๊ฐ๋ฐ
- ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ธ ์ฝ๋ ์์ฑ์ ํ์ ๋๊ตฌ