Go and Protocol Buffers, Beschleunigung

  • Tutorial
Eine Fortsetzung des Artikels Go and Protocol Buffers ein wenig Übung (oder ein schneller Start für diejenigen, die nicht vertraut sind) . Die Kodierungs- / Dekodierungsprozesse in bestimmten Formaten in Go hängen eng mit der Reflexion zusammen. Und wie wir, lieber Leser, wissen wir - Reflexion ist eine lange Zeit. Über welche Kampfmethoden gibt es in diesem Artikel. Ich denke, dass anspruchsvolle Leute wahrscheinlich nichts Neues darin finden.


Wurzel



In dem genannten Artikel ging es eigentlich um Pakete github.com/golang/protobuf/{proto,protoc-gen-go}. Was ist los mit ihnen? Diese Reflexion wird nämlich verwendet. Angenommen, Sie haben ein Projekt, das mit einem bestimmten Satz von Strukturen arbeitet. Und diese Strukturen werden ständig in Protokollpuffern codiert und umgekehrt. Wenn es immer andere, unvorhersehbare Typen waren, gibt es kein Problem. Wenn die Menge jedoch im Voraus bekannt ist, ist es absolut nicht erforderlich, Reflexion zu verwenden. Wie Sie wissen, ist es üblich, eine Schnittstelle zu verwenden, die für die Codierung verantwortlich ist. Hier ist ein Beispielstück von encoding/json:
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

Referenz: Marschall , Unmarschaller .
Wenn der Encoder auf einen Typ stößt, der eine dieser Schnittstellen verkörpert, liegt in diesem Fall die gesamte Arbeit bei ihren Methoden.
Einfaches json Beispiel
type X struct {
    Name string,
    Value int,
}
func (x *X) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf(`{"name": %q, "value": %d}`, x.Name, x.Value))
}

Sie sehen nicht immer (Un)Marshalerso rosig aus. Zum Beispiel gibt es etwas über Yaml ( engl. ) Und allgemein zu diesem Thema zu lesen .


Schlüssel



Die Lösung ist wie immer einfach. Verwenden Sie ein anderes Paket:
go get github.com/gogo/protobuf/{proto,protoc-gen-gogo,gogoproto,protoc-gen-gofast}

Diese Pakete erhöhen einfach den Komfort und die Geschwindigkeit.
Über das Paket (Links):

Wie Sie sehen können, Beschleunigung von 1,10x und höher. Es ist möglich, einfach eine Reihe von Erweiterungen zu verwenden - ohne Beschleunigung. Es besteht die Möglichkeit, einfach zu beschleunigen. Ich entschied mich für diesen Befehl:
protoc \
  --proto_path=$GOPATH/src:$GOPATH/src/github.com/gogo/protobuf/protobuf:. \
  --gogofast_out=. *.proto
und Sie erhalten sowohl Erweiterungen (falls vorhanden) als auch Beschleunigung.
ein Beispiel
Nicht dringend, Erweiterungen zu verwenden, sondern als Referenz.
syntax="proto3";
package some;
//protoc \
//  --proto_path=$GOPATH/src:$GOPATH/src/github.com/gogo/protobuf/protobuf:. \
//  --gogofast_out=. *.proto
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
 // для тестов, создаёт метод Equal, проверять идентичность
option (gogoproto.equal_all)            = true;
option (gogoproto.goproto_stringer_all) = false;
// Stringer для всех (для тестов нужно это расширение)
option (gogoproto.stringer_all)         = true;
// для тестов - наполнение случайными значениями
option (gogoproto.populate_all)         = true;
// генерация набора тестов
option (gogoproto.testgen_all)          = true;
// набор бенчмарков
option (gogoproto.benchgen_all)         = true;
// нужно
option (gogoproto.marshaler_all)        = true;
// размер сообщения
option (gogoproto.sizer_all)            = true;
// нужно
option (gogoproto.unmarshaler_all)      = true;
// enums, не важно - это для красоты
option (gogoproto.goproto_enum_prefix_all) = false;
enum Bool {
	Yes      = 0;
	No       = 1;
	DontCare = 2;
}
message Some {
	option (gogoproto.goproto_unrecognized ) = false;
	option (gogoproto.goproto_getters)       = false;
	Bool  Waht  = 1;
	int64 Count = 2;
	bytes Hash  = 3;
}

wird gelingen
/*
    большая часть выпилена
    там ещё куча методов (Size, String и т.д.) и ещё один файл с тестами
*/
type Bool int32
const (
	Yes      Bool = 0
	No       Bool = 1
	DontCare Bool = 2
)
// ...
type Some struct {
	Waht  Bool   `protobuf:"varint,1,opt,name=Waht,proto3,enum=some.Bool" json:"Waht,omitempty"`
	Count int64  `protobuf:"varint,2,opt,name=Count,proto3" json:"Count,omitempty"`
	Hash  []byte `protobuf:"bytes,3,opt,name=Hash,proto3" json:"Hash,omitempty"`
}
// воплощение интерфейса proto.Message (github.com/golang/protobuf/proto)
func (m *Some) Reset()      { *m = Some{} }
func (*Some) ProtoMessage() {}
// собственно вот
func (m *Some) Marshal() (data []byte, err error) {
	// ...
}
// и вот
func (m *Some) Unmarshal(data []byte) error {
	// ...
}


Wie Sie sehen können, haben einige Erweiterungen den Beta- Status , und dies ist auch eine Bemerkung zu proto3. Zögern Sie nicht. Dieses Paket wurde von vielen erfolgreich verwendet (siehe Homepage). Dies ist jedoch nicht vom Schreiben von Tests ausgenommen. Wenn Sie nicht an Erweiterungen und anderen Dingen interessiert sind, reicht dieser Befehl aus (wie in der README-Datei des Projekts angegeben):
protoc --gofast_out=. myproto.proto


Steine



In der Salbe fliegen

Wenn Sie nicht in den vorherigen Spoiler geschaut haben, dann möchte ich eines seiner Fragmente hervorheben, hier ist es
func (m *Some) Reset()      { *m = Some{} } // очень грубо

Tatsache ist, gogodass Sie damit „schnelle“ Strukturen generieren können. Außerdem können Sie sie mit dem "alten" verwenden github.com/golang/protobuf/proto. In diesem Fall werden Methoden verwendet Marshalund Unmarshal- es gibt kein Problem. Was aber, wenn Sie dieselbe Instanz der Struktur mehrmals verwenden? Wenn die Struktur groß ist (nein, riesig), würde es im Großen und Ganzen nicht schaden, den Pool zu verwenden, die "ausgearbeiteten" Strukturen zu speichern und sie dann wieder abzurufen - wiederzuverwenden.

Der Ansatz github.com/golang/protobuf/proto. Referenz .
func Unmarshal(buf []byte, pb Message) error {
	pb.Reset() // акцент на этом
	return UnmarshalMerge(buf, pb)
}

Herausforderung Reset. Und folglich aus *m = Some{}- die alte Struktur wird weggeworfen, die neue wird geschaffen. Diese Struktur ist klein - egal - aber ich möchte speichern Hash []byte( ich meine zugewiesenen Speicher ), falls Sie einen großen O-Hash verwenden.

Der Ansatz ist github.com/gogo/protobuf/protoähnlich - Kopieren-Einfügen. Kein Blick.

Na dann. Sie können versuchen, die Methode Unmarshaldirekt zu verwenden, oder UnmarshalMerge- fügen Sie einfach Ihre eigene MyResetMethode hinzu, schneiden Sie die Länge des Slice ab - lassen Sie die Kapazität. Nein! Hier ist die Zeile aus der generierten Unmarshal:
m.Hash = append([]byte{}, data[iNdEx:postIndex]...)

Ein neues Slice wird erstellt - das alte fliegt in die GC-Feuerbox. Wenn Sie kleine Strukturen haben ( Felder von Strukturen - und alle zusammen ) -, ist es am einfachsten, nicht zu baden. Suchen Sie bei großen Lösungen nach Problemumgehungen (lesen Sie den generierten Code neu schreiben). Bei der aktuellen Implementierung ist die Verwendung des Pools nicht sinnvoll.

Bonus



Die Bibliothek ist praktisch zum Streamen. Nachrichten schreiben io.Writer, lesen von io.Reader- ein solches Fahrrad gibt es bereits.

Seit ich darüber rede json: github.com/pquerna/ffjson . Ähnliches gilt für json. Nicht nur ein Generator, sondern ein Schweizer Messer für json+ Go.

Seit ich über Geschwindigkeit und den Pool spreche: github.com/valyala/fasthttp . "Schneller" Austausch net/http. Beschleunigung durch Wiederverwendung des Speichers. Und das Gleiche mit zusätzlichen Funktionen.

Nur registrierte Benutzer können an der Umfrage teilnehmen. Bitte komm rein .

die Form

  • 78,4% chaotisch / gemischt 73
  • 21,5% normal 20
  • 0% zu viel 0

der Inhalt

  • 20,5% nichts Neues 16
  • 32% 50/50 ungefähr 25
  • 47,4% haben etwas Interessantes 37

Jetzt auch beliebt: