파이썬은 굉장히 파워풀한 언어다.
특히 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')
memcpy() 최적화 2차 도전 (0) | 2023.10.23 |
---|---|
정수 범위에서 제곱근의 최적화된 구현 방법은? (1) | 2022.12.24 |
자신보다 크거나 같은 최소의 2의 제곱수는? (1) | 2022.09.18 |
CMap vs std::map (4) | 2022.09.13 |
Visual C++에서 Epoch time 계산하기 (0) | 2022.08.28 |