반응형

 

파이썬은 굉장히 파워풀한 언어다.

특히 Numpy와 Pandas를 활용하면 기존 언어에선 어렵게 처리할 문제를 손쉽게 해결할 수 있다.

 

하지만, 파이썬의 특성상 멀티코어를 활용하거나 SIMD를 자유롭게 활용하기 어렵다.

그래서 C/C++로 dll을 만들어 파이썬에서 이를 불러 사용하는 방식을 종종 사용한다.

 

파이썬(Numpy)은 기본적으로 배열을 C언어와 동일한 순서[각주:1]로 저장한다.

하지만 연산을 처리하는 과정에서 더 효율적이라 판단되면 이 저장 순서를 바꿔버린다.

대표적으로 볼 수 있는 것은 전치행렬[각주:2].

 

이렇게 저장된 데이터를 그대로 C/C++에서 접근하면 기대와 전혀 다른 동작을 하게 된다.

 


 

일단 아래와 같이 입력해서 2×3 크기의 int 배열(행렬)을 만들어보자.

 

>>> a = np.array([[1,2,3],[4,5,6]], dtype=int)
>>> a
array([[1, 2, 3],
       [4, 5, 6]])

 

이 배열을 C/C++에서 사용할 수 있도록 각 행의 주소를 추출해보면 아래와 같은 결과를 볼 수 있다.

 

>>> hex(a[0].ctypes.data)
'0x1d23e8d90c0'
>>> hex(a[1].ctypes.data)
'0x1d23e8d90cc'
>>> a[1].ctypes.data - a[0].ctypes.data
12

 

두 행의 주소는 당연히도 12만큼 차이가 난다.

확인을 위해 이 배열의 플래그들을 보면 아래와 같다.

 

>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False

 


 

이제 이 배열(행렬)의 전치행렬을 만들어보자.

 

>>> a = a.T
>>> a
array([[1, 4],
       [2, 5],
       [3, 6]])

 

이렇게 만들어진 전치행렬에서 직접 주소로 접근해보면 아래와 같이 뭔가 다른 결과를 보여준다.

 

>>> hex(a[0].ctypes.data)
'0x1d23e8d90c0'
>>> hex(a[1].ctypes.data)
'0x1d23e8d90c4'
>>> a[1].ctypes.data - a[0].ctypes.data
4

 

두 행의 주소가 int의 크기인 4바이트의 차이밖에 나지 않는다.

또한, 위의 내용을 유심히 보면 a[0]의 주소는 앞과 동일하다는 것을 알 수 있다[각주:3].

 

 

>>> a.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False

 

앞에서도 설명했듯이 실제 데이터를 이동시키지 않고 읽는 순서만 바꾸는 것으로 전치행렬을 생성했기 때문이다.

 

 


 

이 데이터를 C/C++에서 활용하기 위해 외부로 전달하려면 메모리에 저장되는 순서를 바꿔줘야 한다[각주:4].

 

>>> a = np.ascontiguousarray(a)
>>> a
array([[1, 4],
       [2, 5],
       [3, 6]])

 

이렇게 하면 아래와 같이 두 행의 주소가 8바이트 차이가 나는 것을 확인할 수 있다.

 

>>> hex(a[0].ctypes.data)
'0x1d23e8d8b60'
>>> hex(a[1].ctypes.data)
'0x1d23e8d8b68'
>>> a[1].ctypes.data - a[0].ctypes.data
8

 

플래그들을 보면 C_CONTIGUOUS가 True인 것을 확인할 수 있다.

 

>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False

 

참고로, 코드 내에서 저장 순서를 확인하려면 아래와 같이 하면 된다.

 

order_is_c = a.flags['C_CONTIGUOUS']

 

 

사실 C/C++에서 이를 사용하면 아래와 같이 모든 것을 명시적으로 쓰는 것이 합리적이다.

 

n = np.zeros((3,4), dtype=np.double, order='C')

 

 

  1. 행 단위로 먼저 데이터를 저장한다는 뜻 [본문으로]
  2. 즉, 형렬을 전치시키면 메모리 내에서 배열을 바꾸는 것이 아니라 데이터는 그대로 두고 읽는 순서만 바꿔버림 [본문으로]
  3. 즉, 아무런 데이터 이동 없이 읽는 순서만 바꿨다는 뜻임 [본문으로]
  4. 물론, 저장순서에 대한 정보만 전달하는 방법도 있지만, C/C++에서 그걸 굳이 하고싶을 리가... [본문으로]
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band