본문 바로가기

카테고리 없음

[파이썬/Ctypes] Ctypes으로 numpy.sum() 구현하기 (2차원 배열, 행렬 조작)

파이썬의 Ctypes 기능을 활용해 numpy의 sum() 함수를 만들어보자.

 

기왕 하는 김에, axis 를 지정해 세로축 합, 가로축 합, 전체 합을 구하도록 해보자.

numpy.sum(x, axis=0) 

이런 식으로 더러 사용해보셨을 것이다.

이와 같은 기능을 만들어보는 것이다.

 

C 함수에서 파이썬으로 배열을 return하는 것은 복잡하고 별도의 공부가 필요하므로,

파이썬 numpy에서, C 함수에서 조작할 수 있도록  미리 크기에 맞는 배열을 만들어주자.

그리고 아주 중요한, C 함수에서 사용할 수 있도록 파이썬 자료형을 Ctypes으로 변환해줘야 한다.

그리고 double은 64비트 리눅스에서 8바이트로, 파이썬의 float64 자료형에 해당한다. 

넘파이에서 배열을 만들 때 자료형으로 이를 명시해서 혼동이 없도록 한다.

// hsum.c

// This is an implimentation of numpy.sum(), ctypes version.
// ex)  [[1, 2, 3],
//       [4, 5, 6]]
// shape = (2, 3), height = 2, width = 3
// axis = 0 => sum of row axis
// axis = 1 => sum of column axis
// axis = -1 => sum of all

void hsum(double * src, double * dst, int height, int width, int axis) {
    if (axis == -1) {   // np.sum(src)
       dst[0] = 0;
       // 전체 합을 구한다. 1차원 배열 다루듯이.
       for (int i = 0; i < height * width ; i++) {
          dst[0] += src[i];
       }
       // 세로축 합을 구한다. 인덱싱에 유의
    } else if (axis == 0) {  // np.sum(src, axis=0)
        for (int i = 0; i < width; i++) {
            dst[i] = 0;
            for (int j = 0; j < height; j++) {
                dst[i] += src[i + width * j];
            }

        }
        // 가로축 합을 구한다. 인덱싱에 유의
    } else if (axis == 1) { // np.sum(src, axis=1)
        for (int i = 0; i < height; i++) {
            dst[i] = 0;
            for (int j = 0; j < width; j++) {
                dst[i] += src[j + width * i];
            }

        }

    }
}

 

 

# numpy_2.py

import ctypes
import numpy as np
import sys
np.set_printoptions(precision=0, suppress=True)

# 데이터 타입을 지정해줘야 안심할 수 있다.
# (3, 4) 타입의 넘파이 행렬을 만든다.
x = np.arange(0, 12, dtype=np.float64)
x = x.reshape(3, 4)
height, width = x.shape

# 동적 라이브러리를 불러온다.
c_lib = ctypes.CDLL("./hsum.so")
#c_lib.hsum.argtypes = [ctypes.POINTER(ctypes.c_double), \
#    ctypes.POINTER(ctypes.c_double), ctypes.c_int, ctypes.c_int, ctypes.c_int]
#c_lib.hsum.restype = None

# 원본 배열에 대한 C 타입 자료형을 구한다.
src = x.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

# axis = 0 은 세로축 합으로 numpy.sum(x, axis=0)와 같다
# axis = 1 은 가로축 합으로 numpy.sum(x, axis=1)와 같다
# axis = -1 은 전체 합으로 numpy.sum(x)와 같다

axis = 0 
if axis == 0:
    y = np.zeros((width,), dtype=np.float64)
elif axis == 1:
    y = np.zeros((height,), dtype=np.float64)
elif axis == -1:
    y = np.zeros((1,), dtype=np.float64)
else:
    sys.exit("axis shoud be -1, 0 or 1.")

# 합을 저장할 배열의 C 타입 자료형을 구한다.
dest = y.ctypes.data_as(ctypes.POINTER(ctypes.c_double))

# Ctype 함수를 호출한다. 각기, (원본 배열, 합을 담을 배열, 원본 height, 원본 width, 합 구할 축)
c_lib.hsum(src, dest, height, width, axis)

print("----------------")
# C에서 포인터를 통해 원본 데이터를 변경했다.
print(x)
print("----------------")

if axis == -1:
	# 전체 합을 구할 경우, 브라켓([])을 떼면 좀더 깔끔한 값을 보여준다.
    print(*y)
else:
    print(y)

[실행 결과] axis = 0 일 때

----------------
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
----------------
[12. 15. 18. 21.]

 

[실행 결과] axis = 1 일 때

----------------
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
----------------
[ 6. 22. 38.]

 

[실행 결과] axis = -1 일 때

----------------
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
----------------
66.0