2014-04-16 17 views
5

Próbuję przekonwertować moduł Pythona do cython, ma wiele serializacji i deserializacji pracy.Jak wykonać struct.pack i struct.unpack w cython?

Obecnie mam to zrobić:

import struct 

from libc.stdint cimport (
    int32_t, 
    int64_t, 
) 

cpdef bytes write_int(int32_t i): 
    return struct.pack("!i", i) 

cpdef bytes write_long(int64_t i): 
    return struct.pack("!q", i) 

cdef bytes write_double(double val): 
    return struct.pack("!d", val) 

cdef bytes write_string(bytes val): 
    cdef int32_t length = len(val) 
    cdef str fmt 
    fmt = "!i%ds" % length 
    return struct.pack(fmt, length, val) 

Czy jest równe c lib do struct.pack i struct.unpack? Jaki jest najlepszy sposób robienia takich rzeczy w cytoncie?

+0

Jeśli jest tylko o całkowite Polecam prośbą zwłaszcza dla tych, również w tytule. – User

+0

Witam Właśnie zaktualizowałem kod, aby dostarczyć więcej szczegółów. Chodzi o int/double/string. – lxyu

Odpowiedz

7

Spojrzałem na moduły (this i this) i właśnie przetłumaczyłem kod na Cython i usunąłem części PyObject. W teorii powinno to działać, ale niektóre elementy (takie jak części float) nie mam sposobu rygorystycznego testowania:

Niektóre Import:

from cpython.array cimport array, clone 
from libc.string cimport memcmp, memcpy 
from libc.math cimport frexp, ldexp 
from libc.stdint cimport int32_t, int64_t 

Zapisz jakiś kod z topionego typu. To nie jest technicznie stabilny funkcja, ale działa bez zarzutu dla mnie:

ctypedef fused integer: 
    int32_t 
    int64_t 

Ta część testuje endianness urządzenia. To działa dla mnie, ale to nie jest kompletny zestaw. OTOH, wygląda o prawo

cdef enum float_format_type: 
    unknown_format, 
    ieee_big_endian_format, 
    ieee_little_endian_format 

# Set-up 
cdef array stringtemplate = array('B') 
cdef float_format_type double_format 

cdef double x = 9006104071832581.0 

if sizeof(double) == 8: 
    if memcmp(&x, b"\x43\x3f\xff\x01\x02\x03\x04\x05", 8) == 0: 
     double_format = ieee_big_endian_format 
    elif memcmp(&x, b"\x05\x04\x03\x02\x01\xff\x3f\x43", 8) == 0: 
     double_format = ieee_little_endian_format 
    else: 
     double_format = unknown_format 

else: 
    double_format = unknown_format; 

(stringtemplate jest używany, aby móc dokonać bytes obiektów za szybko)

Ta część jest prosta:

cdef void _write_integer(integer x, char* output): 
    cdef int i 
    for i in range(sizeof(integer)-1, -1, -1): 
     output[i] = <char>x 
     x >>= 8 

cpdef bytes write_int(int32_t i): 
    cdef array output = clone(stringtemplate, sizeof(int32_t), False) 
    _write_integer(i, output.data.as_chars) 
    return output.data.as_chars[:sizeof(int32_t)] 

cpdef bytes write_long(int64_t i): 
    cdef array output = clone(stringtemplate, sizeof(int64_t), False) 
    _write_integer(i, output.data.as_chars) 
    return output.data.as_chars[:sizeof(int64_t)] 

array jest podobna do malloc ale to śmieci zebrane :).

W tej części głównie nie mam pojęcia. Moje "testy" przeszły, ale to głównie nadzieja:

cdef void _write_double(double x, char* output): 
    cdef: 
     unsigned char sign 
     int e 
     double f 
     unsigned int fhi, flo, i 
     char *s 

    if double_format == unknown_format or True: 
     if x < 0: 
      sign = 1 
      x = -x 

     else: 
      sign = 0 

     f = frexp(x, &e) 

     # Normalize f to be in the range [1.0, 2.0) 

     if 0.5 <= f < 1.0: 
      f *= 2.0 
      e -= 1 

     elif f == 0.0: 
      e = 0 

     else: 
      raise SystemError("frexp() result out of range") 

     if e >= 1024: 
      raise OverflowError("float too large to pack with d format") 

     elif e < -1022: 
      # Gradual underflow 
      f = ldexp(f, 1022 + e) 
      e = 0; 

     elif not (e == 0 and f == 0.0): 
      e += 1023 
      f -= 1.0 # Get rid of leading 1 

     # fhi receives the high 28 bits; flo the low 24 bits (== 52 bits) 
     f *= 2.0 ** 28 
     fhi = <unsigned int>f # Truncate 

     assert fhi < 268435456 

     f -= <double>fhi 
     f *= 2.0 ** 24 
     flo = <unsigned int>(f + 0.5) # Round 

     assert(flo <= 16777216); 

     if flo >> 24: 
      # The carry propagated out of a string of 24 1 bits. 
      flo = 0 
      fhi += 1 
      if fhi >> 28: 
       # And it also progagated out of the next 28 bits. 
       fhi = 0 
       e += 1 
       if e >= 2047: 
        raise OverflowError("float too large to pack with d format") 

     output[0] = (sign << 7) | (e >> 4) 
     output[1] = <unsigned char> (((e & 0xF) << 4) | (fhi >> 24)) 
     output[2] = 0xFF & (fhi >> 16) 
     output[3] = 0xFF & (fhi >> 8) 
     output[4] = 0xFF & fhi 
     output[5] = 0xFF & (flo >> 16) 
     output[6] = 0xFF & (flo >> 8) 
     output[7] = 0xFF & flo 

    else: 
     s = <char*>&x; 

     if double_format == ieee_little_endian_format: 
      for i in range(8): 
       output[i] = s[7-i] 

     else: 
      for i in range(8): 
       output[i] = s[i] 

Jeśli potrafisz zrozumieć, jak to działa, koniecznie sprawdź to sam.

Następnie owinąć go jak poprzednio:

cdef bytes write_double(double x): 
    cdef array output = clone(stringtemplate, sizeof(double), False) 
    _write_double(x, output.data.as_chars) 
    return output.data.as_chars[:sizeof(double)] 

Ciąg jeden jest rzeczywiście bardzo prosty i wyjaśnia, dlaczego go ustawić jak ja powyżej:

cdef bytes write_string(bytes val): 
    cdef: 
     int32_t int_length = sizeof(int32_t) 
     int32_t input_length = len(val) 
     array output = clone(stringtemplate, int_length + input_length, True) 

    _write_integer(input_length, output.data.as_chars) 
    memcpy(output.data.as_chars + int_length, <char*>val, input_length) 

    return output.data.as_chars[:int_length + input_length] 
0

Jeśli tylko pakowanie jednego typu danych na polecenie (np. grupa ints, następnie grupa floats itp.), można użyć array.array(), aby uzyskać szybsze wyniki, albo za pośrednictwem Pythona lub Cythona.

Źródło: Serialize a group of integers using Cython