web-dev-qa-db-fra.com

Un moyen plus rapide de copier des objets en profondeur dans Golang

J'utilise go 1.9. Et je veux copier en profondeur la valeur de l'objet dans un autre objet. J'essaie de le faire avec l'encodage/gob et l'encodage/json. Mais cela prend plus de temps pour l'encodage gob que l'encodage json. Je vois d'autres questions comme this et ils suggèrent que l'encodage gob devrait être plus rapide. Mais je vois un comportement opposé exact. Quelqu'un peut-il me dire si je fais quelque chose de mal? Ou un moyen meilleur et plus rapide de réaliser une copie profonde que ces deux-là? La structure de mon objet est complexe et imbriquée.

Le code de test:

package main

import (
    "bytes"
    "encoding/gob"
    "encoding/json"
    "log"
    "time"

    "strconv"
)

// Test ...
type Test struct {
    Prop1 int
    Prop2 string
}

// Clone deep-copies a to b
func Clone(a, b interface{}) {

    buff := new(bytes.Buffer)
    enc := gob.NewEncoder(buff)
    dec := gob.NewDecoder(buff)
    enc.Encode(a)
    dec.Decode(b)
}

// DeepCopy deepcopies a to b using json marshaling
func DeepCopy(a, b interface{}) {
    byt, _ := json.Marshal(a)
    json.Unmarshal(byt, b)
}

func main() {
    i := 0
    tClone := time.Duration(0)
    tCopy := time.Duration(0)
    end := 3000
    for {
        if i == end {
            break
        }

        r := Test{Prop1: i, Prop2: strconv.Itoa(i)}
        var rNew Test
        t0 := time.Now()
        Clone(r, &rNew)
        t2 := time.Now().Sub(t0)
        tClone += t2

        r2 := Test{Prop1: i, Prop2: strconv.Itoa(i)}
        var rNew2 Test
        t0 = time.Now()
        DeepCopy(&r2, &rNew2)
        t2 = time.Now().Sub(t0)
        tCopy += t2

        i++
    }
    log.Printf("Total items %+v, Clone avg. %+v, DeepCopy avg. %+v, Total Difference %+v\n", i, tClone/3000, tCopy/3000, (tClone - tCopy))
}

J'obtiens la sortie suivante:

Total items 3000, Clone avg. 30.883µs, DeepCopy avg. 6.747µs, Total Difference 72.409084ms
8
Rohanil

JSON vs gob différence

Le encoding/gob le package doit transmettre les définitions de type:

L'implémentation compile un codec personnalisé pour chaque type de données dans le flux et est plus efficace lorsqu'un seul encodeur est utilisé pour transmettre un flux de valeurs, amortissant le coût de la compilation.

Lorsque vous "sérialisez" d'abord une valeur d'un type, la définition du type doit également être incluse/transmise, afin que le décodeur puisse correctement interpréter et décoder le flux:

Un flux de gobs est auto-descriptif. Chaque élément de données du flux est précédé d'une spécification de son type, exprimée en termes d'un petit ensemble de types prédéfinis.

Ceci est expliqué en détail ici: sérialisation Go efficace de la structure sur le disque

Ainsi, alors que dans votre cas, il est nécessaire de créer à chaque fois un nouvel encodeur et décodeur gob, c'est toujours le "goulot d'étranglement", la partie qui le ralentit. Encodage/décodage à partir du format JSON, la description du type n'est pas incluse dans la représentation.

Pour le prouver, faites ce simple changement:

type Test struct {
    Prop1 [1000]int
    Prop2 [1000]string
}

Ce que nous avons fait ici, ce sont les types de tableaux de champs, en "multipliant" les valeurs mille fois, tandis que les informations de type sont effectivement restées les mêmes (tous les éléments des tableaux ont le même type). En créant des valeurs comme celles-ci:

r := Test{Prop1: [1000]int{}, Prop2: [1000]string{}}

Maintenant, exécutez votre programme de test, la sortie sur ma machine:

Original:

17/10/2017 14:55:53 Total des éléments 3000, Clone moy. 33,63µs , DeepCopy moy. 2,326µs , Différence totale 93,910918ms

Version modifiée:

2017/10/17 14:56:38 Total des éléments 3000, Clone moy. 119.899µs , DeepCopy moy. 462.608µs , Différence totale -1.02812648s

Comme vous pouvez le voir, dans la version d'origine, JSON est plus rapide, mais dans la version modifiée, gob est devenu plus rapide, car le coût de transmission des informations de type s'est amorti.

Méthode d'essai/de banc d'essai

Passons maintenant à votre méthode de test. Cette façon de mesurer les performances est mauvaise et peut donner des résultats assez inexacts. Au lieu de cela, vous devez utiliser les outils de test et de référence intégrés de Go. Pour plus de détails, lisez Ordre du code et performances .

Mises en garde de ces clonages

Ces méthodes fonctionnent avec la réflexion et ne peuvent donc que "cloner" des champs accessibles via la réflexion, c'est-à-dire: exportés. De plus, ils ne gèrent souvent pas l'égalité des pointeurs. J'entends par là si vous avez 2 champs de pointeurs dans une structure, pointant tous deux vers le même objet (les pointeurs étant égaux), après marshaling et démarshaling, vous obtiendrez 2 pointeurs différents pointant vers 2 valeurs différentes. Cela peut même provoquer des problèmes dans certaines situations.

La manière "correcte" de cloner

Compte tenu des mises en garde mentionnées ci-dessus, la bonne façon de cloner a souvent besoin d'aide de "l'intérieur". Autrement dit, le clonage d'un type spécifique n'est souvent possible que si ce type (ou le package de ce type) fournit cette fonctionnalité.

Oui, fournir une fonctionnalité de clonage "manuel" n'est pas pratique, mais d'un autre côté, il surclassera les méthodes ci-dessus (peut-être même par ordre de grandeur) et nécessitera le moins de mémoire "de travail" requise pour le processus de clonage.

13
icza