使っています 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
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つの異なる値を指すことになります。これにより、特定の状況で問題が発生する場合もあります。
上記の警告を考慮すると、多くの場合、適切なクローン作成方法は「内部」からの助けになります。つまり、特定のタイプ(またはそのタイプのパッケージ)がこの機能を提供する場合にのみ、特定のタイプのクローンを作成できることがよくあります。
はい。「手動」のクローン機能を提供するのは便利ではありませんが、反対に、上記の方法よりもパフォーマンスが優れており(場合によっては桁違い)、クローン作成プロセスに必要な「作業用」メモリの量が最小限で済みます。