web-dev-qa-db-ja.com

golangでオブジェクトをディープコピーするより速い方法

使っています go 1.9。オブジェクトの値を別のオブジェクトにディープコピーしたい。 encoding/gobとencoding/jsonでそれをやろうとしています。ただし、jsonエンコーディングよりもgobエンコーディングの方が時間がかかります。 this のような他の質問がいくつかありますが、gobエンコーディングの方が速いはずです。しかし、私は正反対の行動を見ています。私が何か間違ったことをしている場合、誰かが教えてもらえますか?または、これらの2つよりも深いコピーを作成するためのより優れた迅速な方法はありますか?私のオブジェクトの構造体は複雑でネストされています。

テストコード:

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

次の出力が表示されます。

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

JSONとgobの違い

encoding/gob パッケージはタイプ定義を送信する必要があります:

実装は、ストリーム内の各データ型のカスタムコーデックをコンパイルします。単一のエンコーダーを使用して値のストリームを送信し、コンパイルのコストを償却する場合に最も効率的です。

タイプの値を「最初に」シリアル化するとき、タイプのdefinitionも含める/送信する必要があるため、デコーダーはストリームを適切に解釈してデコードできます。

ゴブのストリームは自己記述的です。ストリーム内の各データ項目の前には、そのタイプの仕様があり、事前定義されたタイプの小さなセットで表されます。

これはここで非常に詳細に説明されています: ディスクへの構造体の効率的なGoシリアル化

そのため、あなたのケースでは毎回新しいゴブエンコーダーとデコーダーを作成する必要がありますが、それでも「ボトルネック」であり、遅くなります。 JSON形式へのエンコード/ JSON形式からのデコード、型の説明は表現に含まれません。

それを証明するには、次の簡単な変更を加えます。

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

ここで行ったことは、フィールド配列のタイプを作成し、値を1,000倍にして、タイプ情報は実質的に同じままにします(配列内のすべての要素は同じタイプです)。次のようにそれらの値を作成します。

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

ここでテストプログラムを実行します。私のマシンの出力:

元の:

2017/10/17 14:55:53アイテムの合計3000、クローン平均33.63µs、DeepCopy平均2.326µs、合計差93.910918ms

変更されたバージョン:

2017/10/17 14:56:38アイテムの合計3000、クローン平均119.899µs、DeepCopy平均。 462.608µs、合計差-1.02812648s

ご覧のとおり、元のバージョンではJSONの方が高速ですが、変更されたバージョンではgobが高速になったため、タイプ情報の送信コストが償却されました。

テスト/ベンチング法

次に、テスト方法について説明します。このパフォーマンス測定方法は良くなく、非常に不正確な結果をもたらす可能性があります。代わりに、Goの組み込みテストおよびベンチマークツールを使用する必要があります。詳細は コードとパフォーマンスの順序 をご覧ください。

これらのクローニングの注意事項

これらのメソッドはリフレクションで機能するため、リフレクションを介してアクセス可能なフィールド(つまり、エクスポート)のみを「クローン」できます。また、ポインタの等価性を管理しないこともよくあります。つまり、構造体に2つのポインターフィールドがあり、どちらも同じオブジェクト(ポインターが等しい)を指している場合、マーシャリングとマーシャリング解除の後に、2つの異なるポインターが2つの異なる値を指すことになります。これにより、特定の状況で問題が発生する場合もあります。

クローニングの「適切な」方法

上記の警告を考慮すると、多くの場合、適切なクローン作成方法は「内部」からの助けになります。つまり、特定のタイプ(またはそのタイプのパッケージ)がこの機能を提供する場合にのみ、特定のタイプのクローンを作成できることがよくあります。

はい。「手動」のクローン機能を提供するのは便利ではありませんが、反対に、上記の方法よりもパフォーマンスが優れており(場合によっては桁違い)、クローン作成プロセスに必要な「作業用」メモリの量が最小限で済みます。

13
icza