프로그래밍/Python

[Python] struct와 numpy의 bytes 만드는 성능 비교

채윤아빠 2022. 6. 1. 15:39
728x90
반응형

개요

구조체나 배열을 bytes로 만드는데, struct나 numpy를 혼용해서 사용해 왔습니다.

그러다 문득 어느 것이 더 좋은 성능을 내는지 궁금해져서 아래와 같은 성능 비교 시험을 해 보았습니다.


bytes 변환 예제

아래는 동일한 구조의 정보를 bytes로 변환하는 예제로, 하나는 struct.pack() 함수를 이용하였고 다른 하나는 numpy array의 tobytes() 함수를 이용한 것입니다.

from struct import pack
import numpy as np

struct_result = pack('<bbHIIIII'
        , 0x03, 0x4d            # PREFIX
        , 0x4356                # Command ID
        , 16                    # Payload Size
        , 0x00000000            # reserved
        , 0x00000000            # reserved
        , 0x00000000            # reserved
        , 0x00000000            # reserved
)
print(struct_result)

result = np.ndarray((1, ), dtype = 'uint8, uint8, int16, int32, int32, int32, int32, int32')
result[0] = 0x03, 0x4d, 0x4356, 16, 0x00000000, 0x00000000, 0x00000000, 0x00000000
result_bytes = result[:1].tobytes()
print(result_bytes)

위 예제를 실행하면 다음과 같이 동일한 결과가 출력됩니다.
두 개 모두 동일하게 길이기 24인 bytes 결과물을 반환합니다.

b'\x03MVC\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x03MVC\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

bytes 변환 성능 비교

위 예제를 기준으로 bytes 변환 성능을 다음과 같이 비교해 보았습니다.

import timeit

result1 = timeit.timeit("""
for index in range(1000):
    result = pack('<bbHIIIII'
            , 0x03, 0x4d            # PREFIX
            , 0x4356                # Command ID
            , 16                    # Payload Size
            , 0x00000000            # reserved
            , 0x00000000            # reserved
            , 0x00000000            # reserved
            , 0x00000000            # reserved
        )
""", setup = 'from struct import pack', number = 3000)
print(result1)

result2 = timeit.timeit("""
for index in range(1000):
    result[0] = 0x03, 0x4d, 0x4356, 16, 0x00000000, 0x00000000, 0x00000000, 0x00000000
    result_bytes = result[:1].tobytes()
""", setup = """import numpy as np
result = np.ndarray((1000, ), dtype = 'uint8, uint8, int16, int32, int32, int32, int32, int32')""", number = 3000)
print(result2)

print('#####################')

result3 = timeit.timeit("""
total_result = bytes(0)
for index in range(1000):
    result = pack('<bbHIIIII'
            , 0x03, 0x4d            # PREFIX
            , 0x4356                # Command ID
            , 16                    # Payload Size
            , 0x00000000            # reserved
            , 0x00000000            # reserved
            , 0x00000000            # reserved
            , 0x00000000            # reserved
        )
    total_result += result
""", setup = 'from struct import pack', number = 3000)
print(result3)

result4 = timeit.timeit("""
for index in range(1000):
    result[index] = 0x03, 0x4d, 0x4356, 16, 0x00000000, 0x00000000, 0x00000000, 0x00000000
total_result = result[:1000].tobytes()
""", setup = """import numpy as np
result = np.ndarray((1000, ), dtype = 'uint8, uint8, int16, int32, int32, int32, int32, int32')""", number = 3000)
print(result4)

위 예제를 실행한 결과는 다음과 같습니다.

0.5483859
1.6267609
#####################
2.2189446999999998
0.9086498999999995

위 예제에서 3번, 4번은 다수의 항목(배열)에 대하여 bytes 변환하는 것에 대한 비교 시험입니다.

struct를 이용할 경우, 각 변환된 bytes를 "+" 연산자를 이용하여 계속 더해 주어야 합니다. 하지만 numpy는 배열로 처리되기 때문에 배열을 모두 채운 후 마지막에 tobytes() 함수를 한 번만 호출해 주면 됩니다. 아무래도 이부분으로 성능이 서로 차이가 나는 것으로 보입니다.


결론

단일 항목에 대한 bytes 변환을 할 때는 struct가 numpy보다 월등히 빠릅니다.

복수 항목(배열 등)에 대한 bytes 변환을 할 때는 numpy가 더 나은 성능을 보여줍니다.
제가 찾지 못한 것일 수도 있지만 struct를 이용할 경우 "+" 연산자를 이용하다 보니, 메모리 연산이 지속적으로 늘어나기 때문인 것으로 보입니다. 메모리 스트림 같은 것이 있다면 bytes에 대한 "+" 연산에 대한 성능도 개선이 가능할 것 같기는 한데, 알고 계시는 분이 있으시다면 댓글 부탁 드립니다.

결과적으로 "단일 패킷 처리"나, 서로 다른 형태의 비정형 구조를 bytes로 변환하는 곳에는 struct를 이용하는 것이 유리할 것이고, 다수의 항목을 가지고 처리하는 배열 등과 같은 구조라면, numpy로 처리하는 것이 유리할 것입니다.