Create a new method UnsafeConvertToVersion for faster convert

Only encode/decode will call this path, to allow us to optimize for
unsafe operations.
This commit is contained in:
Clayton Coleman 2016-04-30 19:03:22 -04:00
parent 51b624103f
commit ea7e7a18cb
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
4 changed files with 118 additions and 19 deletions

View File

@ -50,11 +50,11 @@ func BenchmarkPodConversion(b *testing.B) {
var result *api.Pod
for i := 0; i < b.N; i++ {
pod := &items[i%width]
versionedObj, err := scheme.ConvertToVersion(pod, *testapi.Default.GroupVersion())
versionedObj, err := scheme.UnsafeConvertToVersion(pod, *testapi.Default.GroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}
obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}
@ -76,11 +76,11 @@ func BenchmarkNodeConversion(b *testing.B) {
scheme := api.Scheme
var result *api.Node
for i := 0; i < b.N; i++ {
versionedObj, err := scheme.ConvertToVersion(&node, *testapi.Default.GroupVersion())
versionedObj, err := scheme.UnsafeConvertToVersion(&node, *testapi.Default.GroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}
obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}
@ -104,11 +104,11 @@ func BenchmarkReplicationControllerConversion(b *testing.B) {
scheme := api.Scheme
var result *api.ReplicationController
for i := 0; i < b.N; i++ {
versionedObj, err := scheme.ConvertToVersion(&replicationController, *testapi.Default.GroupVersion())
versionedObj, err := scheme.UnsafeConvertToVersion(&replicationController, *testapi.Default.GroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}
obj, err := scheme.ConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
obj, err := scheme.UnsafeConvertToVersion(versionedObj, testapi.Default.InternalGroupVersion())
if err != nil {
b.Fatalf("Conversion error: %v", err)
}

View File

@ -43,10 +43,31 @@ func (t objectTyperToTyper) ObjectKind(obj Object) (*unversioned.GroupVersionKin
return &gvk, unversionedType, nil
}
// ObjectTyperToTyper casts the old typer interface to the new typer interface
func ObjectTyperToTyper(typer ObjectTyper) Typer {
return objectTyperToTyper{typer: typer}
}
// unsafeObjectConvertor implements ObjectConvertor using the unsafe conversion path.
type unsafeObjectConvertor struct {
*Scheme
}
var _ ObjectConvertor = unsafeObjectConvertor{}
// ConvertToVersion converts in to the provided outVersion without copying the input first, which
// is only safe if the output object is not mutated or reused.
func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) {
return c.Scheme.UnsafeConvertToVersion(in, outVersion)
}
// UnsafeObjectConvertor performs object conversion without copying the object structure,
// for use when the converted object will not be reused or mutated. Primarily for use within
// versioned codecs, which use the external object for serialization but do not return it.
func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor {
return unsafeObjectConvertor{scheme}
}
// fieldPtr puts the address of fieldName, which must be a member of v,
// into dest, which must be an address of a variable to which this field's
// address can be assigned.

View File

@ -523,11 +523,80 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion unversioned.GroupVersion
return out, nil
}
// UnsafeConvertToVersion will convert in to the provided outVersion if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion.
func (s *Scheme) UnsafeConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) {
switch t := in.(type) {
case *Unknown:
t.APIVersion = outVersion.String()
return t, nil
case *Unstructured:
t.SetAPIVersion(outVersion.String())
return t, nil
case *UnstructuredList:
t.SetAPIVersion(outVersion.String())
return t, nil
}
// determine the incoming kinds with as few allocations as possible.
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outVersion)
}
// if the Go type is also registered to the destination kind, no conversion is necessary
for i := range kinds {
if kinds[i].Version == outVersion.Version && kinds[i].Group == outVersion.Group {
setTargetKind(in, kinds[i])
return in, nil
}
}
// type is unversioned, no conversion necessary
// it should be possible to avoid this allocation
if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind := unversionedKind
outKind := outVersion.WithKind(kind.Kind)
setTargetKind(in, outKind)
return in, nil
}
// allocate a new object as the target using the target kind
// TODO: this should look in the target group version and find the first kind that matches, rather than the
// first kind registered in typeToGVK
kind := kinds[0]
kind.Version = outVersion.Version
kind.Group = outVersion.Group
out, err := s.New(kind)
if err != nil {
return nil, err
}
// TODO: try to avoid the allocations here - in fast paths we are not likely to need these flags or meta
flags, meta := s.converter.DefaultMeta(t)
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
setTargetKind(out, kind)
return out, nil
}
// generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
return s.converter.DefaultMeta(reflect.TypeOf(in))
}
// setTargetVersion is deprecated and should be replaced by use of setTargetKind
func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) {
if gv.Version == APIVersionInternal {
// internal is a special case
@ -537,3 +606,14 @@ func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) {
gvk, _ := raw.ObjectKind(obj)
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind})
}
// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.
func setTargetKind(obj Object, kind unversioned.GroupVersionKind) {
if kind.Version == APIVersionInternal {
// internal is a special case
// TODO: look at removing the need to special case this
obj.GetObjectKind().SetGroupVersionKind(unversioned.GroupVersionKind{})
return
}
obj.GetObjectKind().SetGroupVersionKind(kind)
}

View File

@ -71,7 +71,7 @@ func NewCodecForScheme(
encodeVersion []unversioned.GroupVersion,
decodeVersion []unversioned.GroupVersion,
) runtime.Codec {
return NewCodec(encoder, decoder, scheme, scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion)
return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion)
}
// NewCodec takes objects in their internal versions and converts them to external versions before
@ -263,21 +263,19 @@ func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unv
}
// Perform a conversion if necessary
if gvk.GroupVersion() != targetGV {
out, err := c.convertor.ConvertToVersion(obj, targetGV)
if err != nil {
if ok {
return err
}
} else {
obj = out
}
}
objectKind := obj.GetObjectKind()
old := objectKind.GroupVersionKind()
objectKind.SetGroupVersionKind(unversioned.GroupVersionKind{Group: targetGV.Group, Version: targetGV.Version, Kind: gvk.Kind})
out, err := c.convertor.ConvertToVersion(obj, targetGV)
if err != nil {
if ok {
return err
}
} else {
obj = out
}
// Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
err = c.encoder.EncodeToStream(obj, w, overrides...)
// restore the old GVK, in case conversion returned the same object
objectKind.SetGroupVersionKind(old)
return err
}