2014-04-16 12 views
5

Sto provando a convertire un modulo python in cython, fa un sacco di lavoro serializza e deserializza.Come fare struct.pack e struct.unpack in cython?

Attualmente ho a fare questo:

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) 

c'è una uguale in c lib per struct.pack e struct.unpack? Qual è il modo migliore per fare cose come questa in cython?

+0

Se si tratta solo di numeri interi Raccomando chiedendo soprattutto per chi, anche nel titolo. – User

+0

Ciao, ho appena aggiornato il codice per fornire maggiori dettagli. Si tratta di int/double/string. – lxyu

risposta

7

Ho esaminato i moduli (this e this) e ho appena tradotto il codice in Cython e rimosso le parti PyObject. In teoria, questo dovrebbe funzionare, ma alcune parti (come i float parti) non ho modo di testare con rigore:

Alcune importazioni:

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 

risparmiare un po 'di codice con un tipo fuso. Non è tecnicamente una caratteristica stabile, ma funziona senza problemi per me:

ctypedef fused integer: 
    int32_t 
    int64_t 

Questa parte alla prova endianness della macchina. Funziona per me, ma non è certo una suite completa. OTOH, sembra circa la destra

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; 

(Il stringtemplate viene utilizzato per essere in grado di fare bytes oggetti in fretta)

di questa parte semplice:

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)] 

Il array è simile a malloc ma è raccolta dei rifiuti :).

Questa parte per la maggior parte non ne ho idea. Le mie "prove" passati, ma è per lo più speranza:

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] 

se si può capire come funziona, assicurati di controllare voi stessi.

Poi ci avvolgiamo come prima:

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)] 

La stringa è in realtà molto semplice, e spiega il motivo per cui impostare il tutto come ho fatto in precedenza:

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

Se sei solo comprimendo un tipo di dati per comando (ad esempio un gruppo di ints, quindi un gruppo di floats ecc.), è possibile utilizzare array.array() per risultati più rapidi, tramite Python o Cython.

Fonte: Serialize a group of integers using Cython