앗 파이썬, C++보다 빠르다!

j___의 블로그·2022년 6월 30일
0

Python3

목록 보기
1/2
post-thumbnail
💡 이미지를 필터링할 때, 반강제적으로 파이썬 loop를 쓰게 되는데, 그때 이걸로 하니까 C++보다 빠르게 작동하더라구요. 원래는 c++보다 80배 정도 느렸는데, 이걸로 하니까 c++보다 4배가량 빨라졌다는 사람도 있었습니다.

Using Numba Module

이미지를 처리할 때, 필터링이나 컨볼루션 기법을 사용하면 모든 픽셀을 돌아야 하기 때문에 엄청난 cost가 소모됩니다. 저는 이미지 처리할 때, 그래서 for문 말고, numpy 합연산을 복잡한 알고리즘을 짜서 속도를 개선했었는데, 이걸 쓰면 좋을 것 같더라구요! 그래서 정리하려고 합니다.

기본적으로 jit이라는 컴파일러를 사용하는 것 같은데, 파이썬에서 반복문이랑 numpy를 처리하는 함수를 c, java처럼 미리 컴파일해서 속도를 끌어올리는 것으로 보여집니다.

module name : Numba

우선 설치부터 하고 시작합니다.

pip3 install numba 

How to apply Numba at Loop?

Basic Loop

from time import perf_counter

def pure_sum(n):
    result = 0
    for i in range(n):
        result += i
    return result

start = perf_counter()
pure_sum(100000000)
print(perf_counter() - start)

위의 코드의 실행시간은 제 로컬을 기준으로

4.7초 정도가 걸렸습니다.

Numba Loop

from numba import jit #numba에서 jit모듈 사용
from time import perf_counter

@jit(nopython = True, cache=True)
def numba_sum(n):
    result = 0
    for i in range(n):
        result += i
    return result

numba_sum(1) #컴파일을 위해 간단한 코드를 실행, 오래 걸리지 않음

start = perf_counter()
numba_sum(100000000)
print(perf_counter() - start)

위의 코드는 제 로컬을 기준으로 0.15초가 걸렸습니다.

잘못 들으신게 아닙니다. 무려 50배가 빨라졌습니다. C++코드보다 4배 빠른 수준입니다.

@jit 데커레이터는 nopython과 object라는 2가지 compilation 모드로 작동합니다. 위 예제에서 nopython=True를 통해 Numba에게 nopython 모드로 작동하라고 지시한 셈인데, 이 모드는 decorate된 function을 근본적으로 compile하여 Python Interpreter의 개입 없이 전체가 작동하도록 할 수 있습니다.

만약 nopython 모드가 잘 작동하지 않을 경우, Numba은 object 모드를 통해 compile 할 수 있다. @jit(nopython=True)가 아닌 @jit이라고만 데커레이팅하면 이 모드가 작동하게 된다. 본 모드에서는 Numba은 loop를 식별하여 machine code에서 compile하며 나머지는 Intereter code에서 compile하게 된다. 더 나은 성능을 기대한다면 이 모드가 아닌 nopython모드를 사용해야 합니다.

2d cross_correlation (이미지 필터링) 을 넘파이로만 짠 코드입니다. numpy pad같은게 jit으로는 활용할 수 없어서 그런 부분을 제거하여 코딩한 완벽하지는 않은 필터링 함수입니다.

import numpy as np
from numba import jit
from time import perf_counter
import cv2
import math

def _gaussian(x, y, sigma):
    inside_exp = -((x**2 + y**2)/(2*(sigma**2)))
    return 1/(2 * math.pi * sigma**2) * math.exp(inside_exp)

def get_gaussian_filter_2d(size, sigma):
    #size must be odd number
    output = np.ones([size, size])
    for i in range(size):
        for j in range(size):
            x_i = i - size//2
            y_j = j - size//2
            output[i][j] = _gaussian(x_i, y_j, sigma)
    return output/output.sum()

def cross_correlation_2d(img, kernel):
    filter_size = kernel.shape[0]
    output = np.zeros(img.shape)

    for i in range(0, output.shape[0]-3):
        for j in range(0, output.shape[1]-3):
            width_range = (i, i + filter_size)
            hight_range = (j, j + filter_size)

            this_conv = img[width_range[0]:width_range[1],hight_range[0]:hight_range[1]]
            out = (np.multiply(this_conv, kernel)).sum()
            output[i, j] = out
    return output

@jit(nopython=True, cache=True)
def numba_cross_corelation(img, kernel):
    filter_size = kernel.shape[0]
    output = np.zeros(img.shape)
    for i in range(0, output.shape[0]-3):
        for j in range(0, output.shape[1]-3):
            width_range = (i, i + filter_size)
            hight_range = (j, j + filter_size)
            
            this_conv = img[width_range[0]:width_range[1],hight_range[0]:hight_range[1]]
            out = (np.multiply(this_conv, kernel)).sum()
            output[i, j] = out
    return output

if __name__ =="__main__":
    PATH_lenna = "./lenna.png" #아무이미지나 해도 됩니다.
    img_lenna = cv2.imread(PATH_lenna, cv2.IMREAD_GRAYSCALE)
    kernel_2d = get_gaussian_filter_2d(3, 11)

    start = perf_counter()
    cross_correlation_2d(img_lenna, kernel_2d) #파이썬 + 넘파이 코드
    print(perf_counter() - start)
		#output - 1.226089504

    start = perf_counter()
    numba_cross_corelation(img_lenna, kernel_2d) # jit + 넘파이 코드
    print(perf_counter() - start)
		#output - 0.5400387520000001

이런 코드에서 대략 2배 정도의 속도 향상이 있네요. numpy에 모든 기능을 지원하는 것은 아니여서 아래의 공식 문서를 보고 지원하는 numpy를 확인하면 좋을 것 같습니다. 판다스 같은건 지원 안하고 오직 numpy만을 지원합니다.

출처 : https://greeksharifa.github.io/파이썬/2019/12/16/numba/

profile
💧 Constant dropping wears away a stone. 🪨

0개의 댓글