Smeg: Chi sono?
Dementus: Qualcuno competente ed eccessivamente risentito.
Smeg: Cosa pensi che vogliano?
Dementus: Me, senza il mio equipaggio.
Ed eccoci di nuovo sul pezzo. E, visto che si tratta della seconda parte dell'articolo Fury Go, non posso non agganciarmi allo splendido prequel di Mad Max: Fury Road, e cioè a Furiosa: A Mad Max Saga diretto sempre dal Maestro George Miller. E, in effetti, anche questo articolo è un prequel: nell'altro avevo descritto come spedire una struttura in Go via IPC socket (UNIX domain socket), e il Software l'avevo scritto partendo da una mia versione "base" che inviava solo dei semplici testi. Ed ecco, in questa seconda parte vi mostrerò, come prequel, la versione base, dimostrandovi, come promesso, che le prestazioni buone ma non eccellenti del benchmark erano dovute (spoiler) solo alla codifica della struttura, perché con i soli testi il Go va come un treno!
...adesso vi faccio vedere cosa sa fare il Go... |
Ok, e allora andiamo con il prequel, descrivendo la versione base che ho scritto prima di quella che usa le strutture. Siamo pronti? Si? E allora partiamo direttamente con il codice!
// reader.go - main processo figlio: è un reader (un server) su IPC socketpackage mainimport ("bufio""fmt""net""os""time")// funzione mainfunc main() {// start ascolto sul file di scambio "myipcs" (con UNIX domain socket)fmt.Printf("processo %d partito (reader)\n", os.Getpid())addr := net.UnixAddr{Name: "./myipcs", Net: "unix"}lner, err := net.ListenUnix("unix", &addr)if err != nil {// errore listenfmt.Println(err)return}// prenoto la chiusura del listener e rimuovo (eventualmente) il file di scambiodefer lner.Close()defer os.Remove("./myipcs")// accetta connessioni da un writer entranteconn, err := lner.AcceptUnix()if err != nil {// errore acceptfmt.Println(err)return}// set time di partenza per calcolare il tempo impiegatostart := time.Now()// loop di lettura messaggi dal writern_msg := 0connrdr := bufio.NewReader(conn) // reader sulla connessionefor {// leggo con il conn readerclient_msg, err := connrdr.ReadString('\n')if err != nil {// errore di letturafmt.Println(err)return}// test numero messaggi per forzare l'uscitan_msg++if n_msg == 2000000 {// il processo chiude la connessione ed esce per numero raggiuntofmt.Printf("reader: ultimo messaggio ricevuto: %s", client_msg)fmt.Printf("reader: processo %d terminato (messaggi=%d tempo totale:%s)\n",os.Getpid(), n_msg,time.Since(start).Truncate(time.Millisecond).String())conn.Close()return}}}
// writer.go - main processo figlio: è un writer (un client) su IPC socketpackage mainimport ("fmt""net""os""time")// funzione mainfunc main() {// mi assicuro che il writer parta dopo il readerfmt.Printf("processo %d partito (writer)\n", os.Getpid())time.Sleep(100 * time.Millisecond)// connessione al server remoto sul file di scambio "myipcs"addr := net.UnixAddr{Name: "./myipcs", Net: "unix"}conn, err := net.DialUnix("unix", nil, &addr)if err != nil {// errore dialfmt.Println(err)return}// loop di scrittura messaggi per il readervar my_text stringindex := 0for {// test index per forzare l'uscitaif index == 2000000 {// il processo chiude la connessione ed esce per indice raggiuntofmt.Printf("writer: processo %d terminato (text=%s messaggi=%d)\n",os.Getpid(), my_text, index)conn.Close()return}// compongo il messaggio e lo invioindex++my_text = fmt.Sprintf("un-messaggio-di-test:%d\n", index)// invio il messaggio al server remoto_, err = conn.Write([]byte(my_text))if err != nil {fmt.Println("errore di invio: ", err)return}}}
Come avrete notato dalla descrizione nella prima linea (e anche dal codice, spero!) ho usato anche questa volta gli IPC socket. Poi ho anche scritto la versione con i Network Socket, ma non vi mostrerò il codice perché è quasi identico. Effettivamente, per la magia del Go, il codice è semplicissimo rispetto alla analoga versione in C vista qui (in questo caso era la versione "fast"), ed è anche quasi identico alla versione con le strutture dello scorso articolo. Come sempre il codice è stra-commentato, e credo che possa essere facilmente compreso anche da chi non conosce il Go, però mi interessa, a questo punto, aggiungere qualche dettaglio per far notare le (poche) differenze rispetto a quello dello scorso articolo.
Cominciamo, allora, con il writer: in entrambe versioni si crea, inizialmente, un oggetto "connessione" conn:
// connessione al server remoto sul file di scambio "myipcs"addr := net.UnixAddr{Name: "./myipcs", Net: "unix"}conn, err := net.DialUnix("unix", nil, &addr)
Poi, nella versione con le strutture, si invia il messaggio attraverso il codec encoding/gob passandogli la connessione:
// set encoder e spedizione dall'encoderencoder := gob.NewEncoder(conn)err = encoder.Encode(message)
Invece, nella versione base si scrive, direttamente, con l'oggetto connessione creato all'inizio:
// invio il messaggio al server remoto_, err = conn.Write([]byte(my_text))
E ora passiamo al reader: anche qui, in entrambe versioni, si crea un oggetto "connessione" conn:
// accetta connessioni da un writer entranteconn, err := lner.AcceptUnix()
Poi, nella versione con le strutture, si riceve il messaggio attraverso il codec passandogli la connessione:
// set decoder e ricezione dal decoderdecoder := gob.NewDecoder(conn)decoder.Decode(&message)
Invece, nella versione base, si legge con un oggetto "Reader" (della libreria bufio) creato sulla connessione vista sopra:
connrdr := bufio.NewReader(conn) // reader sulla connessionefor {// leggo con il conn readerclient_msg, err := connrdr.ReadString('\n')
Come avrete notato le differenze sono poche ma significative. Notare anche che nella versione con le strutture il testo del messaggio è una stringa dentro la struttura Message, mentre nella versione base il messaggio è direttamente una stringa terminata con un "newline": questo è un particolare importante, perché in fase di ricezione con la funzione ReadString appena vista è necessario specificare qual'è il terminatore di stringa.
E vabbé, so che siete curiosi, è ora di passare ai risultati! Di seguito i risultati dei benchmark delle "versioni base" che inviano solo testi in Go:
aldo@Linux $ cd ../go-ipcsocketbase/aldo@Linux $ ./processessono il padre (15381): attendo la terminazione dei figlisono il figlio 1 (15382): eseguo il nuovo processosono il figlio 2 (15383): eseguo il nuovo processoprocesso 15382 partito (reader)processo 15383 partito (writer)writer: processo 15383 terminato (text=un-messaggio-di-test:2000000messaggi=2000000)reader: ultimo messaggio ricevuto: un-messaggio-di-test:2000000reader: processo 15382 terminato (messaggi=2000000 tempo totale:3.058s)sono il padre (15381): figlio 15382 terminato (0)sono il padre (15381): figlio 15383 terminato (0)./processes: processi terminati
aldo@Linux $ cd ../go-socketbase/aldo@Linux $ ./processessono il padre (15408): attendo la terminazione dei figlisono il figlio 1 (15409): eseguo il nuovo processosono il figlio 2 (15410): eseguo il nuovo processoprocesso 15410 partito (writer)processo 15409 partito (reader)writer: processo 15410 terminato (text=un-messaggio-di-test:2000000messaggi=2000000)reader: ultimo messaggio ricevuto: un-messaggio-di-test:2000000reader: processo 15409 terminato (messaggi=2000000 tempo totale:8.46s)sono il padre (15408): figlio 15409 terminato (0)sono il padre (15408): figlio 15410 terminato (0)./processes: processi terminati
A questo punto i risultati parlano senza temi di smentite: i tempi realizzati sono decisamente migliori di quelli mostrati per le versioni che spedivano strutture complesse, quindi è evidente che la maggior parte del tempo di CPU se la mangiava la libreria specializzata encoding/gob, che funziona bene però, a quanto pare, non è un fulmine. E, grazie ai test appena mostrati sopra si può affermare (come anticipato nella prima parte dell'articolo) che il Go è un linguaggio notevolmente veloce, alla faccia di chi pensa il contrario... Notare che la versione IPC con i suoi 3.058s è addirittura veloce come la versione in C (che impiegava 3.309s ma con messaggi leggermente più lunghi a causa della presenza dell'indice)! Anche in questo caso (come già nello scorso articolo) la versione con i Network socket è un po' più lenta (8.46s) ma è, comunque, sufficientemente veloce.
Ok, credo che, per il momento si può chiudere la parentesi Go sulla comunicazione tra processi: credo che i risultati siano stati interessanti, specialmente per il fatto di avere confrontato codici analoghi per C e Go. Non so di cosa parlerò` prossimamente: in questo momento ho in mente solo le prossime (meritate) vacanze. Ci sentiremo più avanti, ben rilassati e pronti per nuove avventure in C (o in Go...)!
Ciao, e al prossimo post!
Nessun commento:
Posta un commento