파이썬은 굉장히 파워풀한 언어다.
특히 Numpy와 Pandas를 활용하면 기존 언어에선 어렵게 처리할 문제를 손쉽게 해결할 수 있다.
하지만, 파이썬의 특성상 멀티코어를 활용하거나 SIMD를 자유롭게 활용하기 어렵다.
그래서 C/C++로 dll을 만들어 파이썬에서 이를 불러 사용하는 방식을 종종 사용한다.
파이썬(Numpy)은 기본적으로 배열을 C언어와 동일한 순서[각주:1]로 저장한다.
하지만 연산을 처리하는 과정에서 더 효율적이라 판단되면 이 저장 순서를 바꿔버린다.
대표적으로 볼 수 있는 것은 전치행렬[각주:2].
이렇게 저장된 데이터를 그대로 C/C++에서 접근하면 기대와 전혀 다른 동작을 하게 된다.
일단 아래와 같이 입력해서 2×3 크기의 int 배열(행렬)을 만들어보자.
1 2 3 4 | >>> a = np.array([[ 1 , 2 , 3 ],[ 4 , 5 , 6 ]], dtype = int ) >>> a array([[ 1 , 2 , 3 ], [ 4 , 5 , 6 ]]) |
이 배열을 C/C++에서 사용할 수 있도록 각 행의 주소를 추출해보면 아래와 같은 결과를 볼 수 있다.
1 2 3 4 5 6 | >>> hex (a[ 0 ].ctypes.data)'0x1d23e8d90c0' >>> hex (a[ 1 ].ctypes.data)'0x1d23e8d90cc' >>> a[ 1 ].ctypes.data - a[ 0 ].ctypes.data12 |
두 행의 주소는 당연히도 12만큼 차이가 난다.
확인을 위해 이 배열의 플래그들을 보면 아래와 같다.
1 2 3 4 5 6 7 | >>> a.flags C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True WRITEABLE : True ALIGNED : True WRITEBACKIFCOPY : False |
이제 이 배열(행렬)의 전치행렬을 만들어보자.
1 2 3 4 5 | >>> a = a.T >>> a array([[ 1 , 4 ], [ 2 , 5 ], [ 3 , 6 ]]) |
이렇게 만들어진 전치행렬에서 직접 주소로 접근해보면 아래와 같이 뭔가 다른 결과를 보여준다.
1 2 3 4 5 6 | >>> hex (a[ 0 ].ctypes.data)'0x1d23e8d90c0' >>> hex (a[ 1 ].ctypes.data)'0x1d23e8d90c4' >>> a[ 1 ].ctypes.data - a[ 0 ].ctypes.data4 |
두 행의 주소가 int의 크기인 4바이트의 차이밖에 나지 않는다.
또한, 위의 내용을 유심히 보면 a[0]의 주소는 앞과 동일하다는 것을 알 수 있다[각주:3].
1 2 3 4 5 6 7 | >>> a.flags C_CONTIGUOUS : False F_CONTIGUOUS : True OWNDATA : False WRITEABLE : True ALIGNED : True WRITEBACKIFCOPY : False |
앞에서도 설명했듯이 실제 데이터를 이동시키지 않고 읽는 순서만 바꾸는 것으로 전치행렬을 생성했기 때문이다.
이 데이터를 C/C++에서 활용하기 위해 외부로 전달하려면 메모리에 저장되는 순서를 바꿔줘야 한다[각주:4].
1 2 3 4 5 | >>> a = np.ascontiguousarray(a) >>> a array([[ 1 , 4 ], [ 2 , 5 ], [ 3 , 6 ]]) |
이렇게 하면 아래와 같이 두 행의 주소가 8바이트 차이가 나는 것을 확인할 수 있다.
1 2 3 4 5 6 | >>> hex (a[ 0 ].ctypes.data)'0x1d23e8d8b60' >>> hex (a[ 1 ].ctypes.data)'0x1d23e8d8b68' >>> a[ 1 ].ctypes.data - a[ 0 ].ctypes.data8 |
플래그들을 보면 C_CONTIGUOUS가 True인 것을 확인할 수 있다.
1 2 3 4 5 6 7 | >>> a.flags C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True WRITEABLE : True ALIGNED : True WRITEBACKIFCOPY : False |
참고로, 코드 내에서 저장 순서를 확인하려면 아래와 같이 하면 된다.
1 | order_is_c = a.flags[ 'C_CONTIGUOUS' ] |
사실 C/C++에서 이를 사용하면 아래와 같이 모든 것을 명시적으로 쓰는 것이 합리적이다.
1 | 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 |
내 블로그 - 관리자 홈 전환 |
Q
Q
|
---|---|
새 글 쓰기 |
W
W
|
글 수정 (권한 있는 경우) |
E
E
|
---|---|
댓글 영역으로 이동 |
C
C
|
이 페이지의 URL 복사 |
S
S
|
---|---|
맨 위로 이동 |
T
T
|
티스토리 홈 이동 |
H
H
|
단축키 안내 |
Shift + /
⇧ + /
|
* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.