아래와 같이 실행속도를 더 빠르게 하는 방법을 진행해 보고 있습니다.
https://question99.tistory.com/1095
[파이썬] 조건에 맞는 데이터 계산(다중 for문)
어떤 데이터가 있을때 어떤 컬럼의 데이터들끼리 어떤 조건들로 찾거나 계산할때import timeimport pandas as pdimport numpy as npnp.random.seed(0) # 난수 생성 시 일관성을 위해 시드 설정### 1) 데이터 생성data_s
question99.tistory.com
https://question99.tistory.com/1096
[파이썬] 12배 빨라진다! 조건에 맞는 데이터 계산(Parallel, 병렬처리, 다중프로세스)
https://question99.tistory.com/1095 [파이썬] 조건에 맞는 데이터 계산(다중 for문)어떤 데이터가 있을때 어떤 컬럼의 데이터들끼리 어떤 조건들로 찾거나 계산할때import timeimport pandas as pdimport numpy as npnp.r
question99.tistory.com
https://question99.tistory.com/1097
[파이썬] 150배 빨라진다! 조건에 맞는 데이터 계산(병렬처리와 Numba사용)
https://question99.tistory.com/1095 [파이썬] 조건에 맞는 데이터 계산(다중 for문)어떤 데이터가 있을때 어떤 컬럼의 데이터들끼리 어떤 조건들로 찾거나 계산할때import timeimport pandas as pdimport numpy as npnp.r
question99.tistory.com
https://question99.tistory.com/1098
[파이썬] 680배 빨라진다! 조건에 맞는 데이터 계산(병렬처리 하지 않고 Numba만 사용)
https://question99.tistory.com/1095 [파이썬] 조건에 맞는 데이터 계산(다중 for문)어떤 데이터가 있을때 어떤 컬럼의 데이터들끼리 어떤 조건들로 찾거나 계산할때import timeimport pandas as pdimport numpy as npnp.r
question99.tistory.com
Numba를 사용해서 기존에는 불가능했던 처리까지 할 수 있게 되었습니다.
그러나 이런 가능성이 생기니까 더 큰 환경에서 실행해보게 되었습니다.
그래서 Numba에서 GPU를 사용하는 방법을 찾아보았습니다.
import time
import pandas as pd
import numpy as np
import itertools
from numba import cuda # Numba CUDA 임포트
np.random.seed(0) # 난수 생성 시 일관성을 위해 시드 설정
### 1) 데이터 생성
data_size = 2000
col_names = ['c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'c10'
# ,'c11', 'c12', 'c13', 'c14', 'c15', 'c16', 'c17', 'c18', 'c19', 'c20'
]
data = {
col: np.random.randint(50, 201, size=data_size) for col in col_names
}
# DataFrame 생성
df = pd.DataFrame(data)
### 2) 조건의 조합(순열) 만들기
lst = list(itertools.permutations(col_names, 2)) # 순열 만들기
# lst_no는 이제 GPU로 전달될 숫자 인덱스 튜플의 NumPy 배열이 됩니다.
lst_no_numeric = []
for item in lst:
n1 = col_names.index(item[0]) # 해당 컬럼명(문자열)의 인덱스를 찾아 정수로 사용
n2 = col_names.index(item[1])
lst_no_numeric.append((n1, n2))
# Python list of tuples -> NumPy array of tuples (for GPU)
# GPU에 전달하기 위해 (num_permutations, 2) 형태의 2D 배열로 만듭니다.
lst_no_array = np.array(lst_no_numeric, dtype=np.int32)
# GPU 커널 정의
@cuda.jit
def run_simulation_gpu_kernel(lst_no_gpu, n_df_gpu, output_max_counts_gpu, output_indices_gpu, output_cases_gpu):
# 각 스레드가 처리할 조합의 인덱스를 계산 (여기서는 item1, item2의 조합 인덱스)
# 총 조합의 개수는 lst_no_gpu의 첫 번째 차원 크기의 제곱
total_permutations = lst_no_gpu.shape[0]
# 각 GPU 스레드는 (item1, item2) 조합 중 하나를 처리합니다.
# 즉, outer_loop_idx = item1의 인덱스, inner_loop_idx = item2의 인덱스
# 1D 그리드를 2D 논리적 인덱스로 매핑합니다.
# 각 스레드는 item1과 item2의 모든 조합 중 하나를 처리합니다.
# 예를 들어, 스레드 ID가 0, 1, 2, ... 일 때,
# (0,0), (0,1), ..., (0,N), (1,0), (1,1), ... 처럼 매핑할 수 있습니다.
# 2D 인덱스를 1D 스레드 ID에 매핑
# (total_permutations * total_permutations) 만큼의 스레드가 필요합니다.
thread_id = cuda.grid(1) # 현재 스레드의 전역 ID
# 각 스레드가 처리할 item1_idx와 item2_idx를 계산
if thread_id < total_permutations * total_permutations:
item1_idx = thread_id // total_permutations # item1의 인덱스
item2_idx = thread_id % total_permutations # item2의 인덱스
# 실제로 사용할 item1과 item2의 컬럼 번호 튜플
item1_col_idx1 = lst_no_gpu[item1_idx, 0]
item1_col_idx2 = lst_no_gpu[item1_idx, 1]
item2_col_idx1 = lst_no_gpu[item2_idx, 0]
item2_col_idx2 = lst_no_gpu[item2_idx, 1]
count1_local = 0 # 각 스레드 내의 지역 변수
count2_local = 0 # 각 스레드 내의 지역 변수
# 데이터의 row별 조건 확인을 위한 반복문 (GPU 스레드 내에서 순차적으로 실행)
for i in range(n_df_gpu.shape[0]):
data1 = n_df_gpu[i, item1_col_idx1]
data2 = n_df_gpu[i, item1_col_idx2]
data3 = n_df_gpu[i, item2_col_idx1]
data4 = n_df_gpu[i, item2_col_idx2]
if data1 > data2:
count1_local += 1
elif data3 > data4:
count2_local += 1
count_sum_local = count1_local + count2_local
# 결과를 미리 할당된 출력 배열에 저장
# 각 스레드는 자신의 thread_id에 해당하는 인덱스에 결과를 씁니다.
output_max_counts_gpu[thread_id] = count_sum_local
output_indices_gpu[thread_id] = thread_id # 이 스레드의 고유 ID 또는 매핑된 idx (idx는 여기서는 thread_id)
# max_case1과 max_case2는 (item1_col_idx1, item1_col_idx2) 형태로 저장
output_cases_gpu[thread_id, 0, 0] = item1_col_idx1
output_cases_gpu[thread_id, 0, 1] = item1_col_idx2
output_cases_gpu[thread_id, 1, 0] = item2_col_idx1
output_cases_gpu[thread_id, 1, 1] = item2_col_idx2
# n_df를 NumPy 배열로 변환
n_df_array = df[col_names].values.astype(np.int32) # 데이터 타입을 명확히 지정
n_df_gpu = cuda.to_device(n_df_array) # 데이터를 GPU로 전송
# lst_no_numeric를 GPU로 전송
lst_no_gpu = cuda.to_device(lst_no_array)
# GPU 커널 실행을 위한 설정
total_combinations = lst_no_array.shape[0] * lst_no_array.shape[0] # 전체 (item1, item2) 조합의 개수
threads_per_block = 256 # 블록당 스레드 수 (튜닝 가능)
blocks_per_grid = (total_combinations + (threads_per_block - 1)) // threads_per_block
# 결과를 저장할 GPU 메모리 배열 할당
# 각 스레드가 하나의 결과를 저장
output_max_counts_gpu = cuda.device_array(total_combinations, dtype=np.int32)
output_indices_gpu = cuda.device_array(total_combinations, dtype=np.int32)
# max_case1, max_case2를 저장하기 위한 배열 (각각 2개의 인덱스를 가짐)
output_cases_gpu = cuda.device_array((total_combinations, 2, 2), dtype=np.int32) # (조합수, 2개 케이스, 각 케이스 2개 인덱스)
print(f"총 {total_combinations:,}개의 조합을 GPU에서 계산합니다.")
print(f"그리드 크기: {blocks_per_grid}, 블록당 스레드: {threads_per_block}")
# start_time = time.time()
start_time_pc = time.perf_counter()
# GPU 커널 실행
run_simulation_gpu_kernel[blocks_per_grid, threads_per_block](
lst_no_gpu,
n_df_gpu,
output_max_counts_gpu,
output_indices_gpu,
output_cases_gpu
)
cuda.synchronize() # GPU 작업이 완료될 때까지 대기
# GPU에서 CPU로 결과 가져오기
all_counts = output_max_counts_gpu.copy_to_host()
all_indices = output_indices_gpu.copy_to_host()
all_cases = output_cases_gpu.copy_to_host()
# CPU에서 최종 Best case 찾기 (리덕션)
max_count = 0
best_idx = 0
best_case1 = []
best_case2 = []
for i in range(total_combinations):
current_count = all_counts[i]
if current_count > max_count:
max_count = current_count
best_idx = all_indices[i] # 또는 i (현재 스레드 ID)
best_case1 = all_cases[i, 0, :].tolist() # NumPy 배열을 리스트로 변환
best_case2 = all_cases[i, 1, :].tolist() # NumPy 배열을 리스트로 변환
### 4) 출력
print(f"Best case : {max_count:,} (idx={best_idx})")
print(f"Best case : {max_count:,} (idx={best_idx}), ({col_names[best_case1[0]]},{col_names[best_case1[1]]}):({col_names[best_case2[0]]},{col_names[best_case2[1]]})")
# 실행기간 계산 및 출력
# end_time = time.time()
# elapsed = end_time - start_time
# minutes = int(elapsed // 60)
# seconds = int(elapsed % 60)
# print(f"\nRunning time: {minutes}m {seconds}sec")
# end_time_pc = time.perf_counter()
# gpu_elapsed_time = end_time_pc - start_time_pc
# print(f"\nGPU Kernel Running time: {gpu_elapsed_time:.6f} seconds")
# CPU에서 최종 집계 시간도 별도로 측정 가능
start_cpu_post_processing = time.perf_counter()
# ... (CPU에서 최종 Best case 찾는 루프) ...
end_cpu_post_processing = time.perf_counter()
cpu_elapsed_time = end_cpu_post_processing - start_cpu_post_processing
print(f"CPU Post-processing time: {cpu_elapsed_time:.6f} seconds")
total_elapsed_time = end_cpu_post_processing - start_time_pc
print(f"Total end-to-end Running time: {total_elapsed_time:.6f} seconds")
처음 다중for 문의 300 sec에서 지금 0.16sec로
1,875배
빨리 실행하는 방법을 찾을 수 있습니다.
위 코드를 실행하면 다음과 같이 Warning이 보입니다. 이것은 실행시간이 빨라서 GPU를 사용시간이 작다는 것입나다.
조건을 더 늘리면 이 Warning은 보이지 않게 됩니다.
numba\cuda\dispatcher.py:536: NumbaPerformanceWarning: Grid size 32 will likely result in GPU under-utilization due to low occupancy.
warn(NumbaPerformanceWarning(msg))
'프로그램' 카테고리의 다른 글
[파이썬] 여러 곡이 있는 mp3 파일 분리하기 (1) | 2025.06.04 |
---|---|
[파이썬] 680배 빨라진다! 조건에 맞는 데이터 계산(병렬처리 하지 않고 Numba만 사용) (0) | 2025.06.03 |
[파이썬] 150배 빨라진다! 조건에 맞는 데이터 계산(병렬처리와 Numba사용) (0) | 2025.06.03 |
[파이썬] 조건에 맞는 데이터 계산(다중 for문) (0) | 2025.06.03 |
[A.I] OpenManus(chatGPT) 사용하기 (2) | 2025.04.13 |
댓글