2015-07-22 15 views
10

Come noto, go non ha un tipo di unione e deve essere simulato solo tramite l'interfaccia.golang: come simulare efficacemente il tipo di unione

cerco due metodi per simulare l'unione, ma il risultato è tutt'altro che buono come C.

package main 

import (
    "fmt" 
    "time" 
) 

type U interface { 
    i32() int32 
    i16() int16 
} 

type i32 int32 

func (u i32) i32() int32 { 
    return int32(u) 
} 

func (u i32) i16() int16 { 
    return int16(u) 
} 

type i16 int16 

func (u i16) i32() int32 { 
    return int32(u) 
} 

func (u i16) i16() int16 { 
    return int16(u) 
} 

func test() (total int64) { 
    type A struct { 
     t int32 
     u interface{} 
    } 
    a := [...]A{{1, int32(100)}, {2, int16(3)}} 

    for i := 0; i < 5000000000; i++ { 
     p := &a[i%2] 
     switch p.t { 
     case 1: 
      total += int64(p.u.(int32)) 
     case 2: 
      total += int64(p.u.(int16)) 
     } 
    } 
    return 
} 

func test2() (total int64) { 
    type A struct { 
     t int32 
     u U 
    } 
    a := [...]A{{1, i32(100)}, {2, i16(3)}} 

    for i := 0; i < 5000000000; i++ { 
     p := &a[i%2] 
     switch p.t { 
     case 1: 
      total += int64(p.u.i32()) 
     case 2: 
      total += int64(p.u.i16()) 
     } 
    } 
    return 
} 

type testfn func() int64 

func run(f testfn) { 
    ts := time.Now() 
    total := f() 
    te := time.Now() 
    fmt.Println(total) 
    fmt.Println(te.Sub(ts)) 
} 

func main() { 
    run(test) 
    run(test2) 
} 

risultato:

257500000000 
1m23.508223094s 
257500000000 
34.95081661s 

Il modo in cui il metodo è migliore, e il tipo pressofuso modo costa più tempo della CPU.

La versione C:

#include <stdio.h> 

struct A { 
    int t; 
    union { 
     int i; 
     short v; 
    } u; 
}; 

long test() 
{ 
    struct A a[2]; 
    a[0].t = 1; 
    a[0].u.i = 100; 
    a[1].t = 2; 
    a[1].u.v = 3; 

    long total = 0; 
    long i; 
    for (i = 0; i < 5000000000; i++) { 
     struct A* p = &a[i % 2]; 
     switch(p->t) { 
     case 1: 
      total += p->u.i; 
      break; 
     case 2: 
      total += p->u.v; 
      break; 
     } 
    } 
    return total; 
} 
int main() 
{ 
    long total = test(); 
    printf("%ld\n", total); 
} 

risultato:

257500000000 

real 0m5.620s 
user 0m5.620s 
sys 0m0.000s 

Il tipo unione è utile per molte applicazioni, ad esempio il protocollo di rete potrebbe contenere un tipo di calcestruzzo variante. Quindi, forse l'accesso ai dati di unione può diventare il collo di bottiglia dell'applicazione.

Chiunque può aiutare? Grazie.

risposta

6

È possibile utilizzare le matrici per rappresentare un singolo int32 come due int16 s e poi assemblarli con turni as Rob Pike recommends:

func test3() (total int64) { 
    type A struct { 
     t int32 
     u [2]int16 
    } 
    a := [...]A{ 
     {1, [2]int16{100, 0}}, 
     {2, [2]int16{3, 0}}, 
    } 

    for i := 0; i < N; i++ { 
     p := &a[i%2] 
     switch p.t { 
     case 1: 
      total += int64(p.u[0]<<0 | p.u[1]<<8) 
     case 2: 
      total += int64(p.u[0]) 
     } 
    } 
    return 
} 

con il compilatore originale Go funziona circa 2 volte più lento rispetto alla versione C, e con gccgo (-O3) funziona all'incirca quanto C.

Attenzione però che questo approccio presuppone intro little endian. Avrai bisogno di cambiare l'ordine dei turni per l'architettura big-endian.

Inoltre, se è necessario decodificare le strutture da una sezione di byte, si dovrebbe utilizzare realmente encoding/binary. Questa libreria è creata per tradurre tra sequenze di byte e altri tipi.

+1

Sì, può migliorare le prestazioni, ma a volte il tipo di variante può includere altri tipi, ad es. array di stringhe o byte. – kingluo

+0

Vedi modifica. Ho rimosso gli usi di "non sicuro" poiché non è realmente necessario e ho aggiunto una nota su altri tipi. –

+1

Grazie. Provo le funzioni binarie, sembrano molto costose. Dal momento che il sindacato non intende essere un formato wire, quindi la rappresentazione binaria non è necessaria per i tipi numerici. Penso che non sia più sicuro nel mio caso. – kingluo

2

L'unione può contenere i tipi numerici e la stringa di ottetto, quindi provo a utilizzare la sezione di byte come contenitore dei valori e ad utilizzare unsafe.Pointer per accedervi in ​​base al tipo di calcestruzzo.

func test3() (total int64) { 
    type A struct { 
     t int32 
     u []byte 
    } 

    a := [...]A{{1, make([]byte, 8)}, {2, make([]byte, 8)}} 
    *(*int32)(unsafe.Pointer(&a[0].u)) = 100 
    *(*int16)(unsafe.Pointer(&a[1].u)) = 3 

    for i := 0; i < 5000000000; i++ { 
     p := &a[i%2] 
     switch p.t { 
     case 1: 
      total += int64(*(*int32)(unsafe.Pointer(&p.u))) 
     case 2: 
      total += int64(*(*int16)(unsafe.Pointer(&p.u))) 
     } 
    } 
    return 
} 

risultato:

$ go run union.go 
257500000000 
12.844752701s 

$ go run -compiler gccgo -gccgoflags -O3 union.go 
257500000000 
6.640667s 

E 'la versione migliore?

+0

L'utilizzo di matrici (o puntatori agli array) anziché di sezioni potrebbe teoricamente migliorare le prestazioni. –

+0

Cerco array di byte, ma le prestazioni sono quasi le stesse. Comunque, nel mio caso, la stringa di ottetto è di lunghezza variabile, quindi la slice di byte dovrebbe essere l'unica scelta. – kingluo

Problemi correlati