From 94179336ca58429e4bec773fa59c52d96228ef98 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 28 Oct 2017 21:28:14 -0700 Subject: [PATCH] Optimize `BeanSerializer` by using "blocks of 4" unrolling for bigger pojos --- .../jackson/databind/ser/BeanSerializer.java | 17 +- .../databind/ser/UnrolledBeanSerializer.java | 75 ++----- .../ser/impl/UnwrappingBeanSerializer.java | 27 ++- .../databind/ser/std/BeanSerializerBase.java | 191 +++++++++++++++--- 4 files changed, 207 insertions(+), 103 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java index 6b9ab7a344..3cd57aeb81 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java @@ -140,12 +140,21 @@ public final void serialize(Object bean, JsonGenerator gen, SerializerProvider p _serializeWithObjectId(bean, gen, provider, true); return; } - gen.writeStartObject(bean); if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, gen, provider); - } else { - serializeFields(bean, gen, provider); + gen.writeStartObject(bean); + _serializeFieldsFiltered(bean, gen, provider, _propertyFilterId); + gen.writeEndObject(); + return; } + BeanPropertyWriter[] fProps = _filteredProps; + if ((fProps != null) && (provider.getActiveView() != null)) { + gen.writeStartObject(bean); + _serializeFieldsWithView(bean, gen, provider, fProps); + gen.writeEndObject(); + return; + } + gen.writeStartObject(bean); + _serializeFieldsNoView(bean, gen, provider, _props); gen.writeEndObject(); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/UnrolledBeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/UnrolledBeanSerializer.java index a5ffb4d1b1..4a8ee148b3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/UnrolledBeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/UnrolledBeanSerializer.java @@ -21,7 +21,13 @@ public class UnrolledBeanSerializer { private static final long serialVersionUID = 30; // as per jackson 3.0 - public static final int MAX_PROPS = 6; + /* 28-Oct-2017, tatu: Exact choice for max number of properties to unroll + * is difficult to pin down, but probably has to be at least 4, and + * at most 8. Partly this is due to "blocks of 4" that default bean + * serializer now uses, and partly guessing how aggressively JVM might + * inline larger methods (more unroll, bigger method). + */ + private static final int MAX_PROPS = 6; protected final int _propCount; @@ -145,8 +151,11 @@ public void serialize(Object bean, JsonGenerator gen, SerializerProvider provide // NOTE! We have ensured that "JSON Filter" and "Object Id" cases // always use "vanilla" BeanSerializer, so no need to check here - if ((_filteredProps != null) && (provider.getActiveView() != null)) { - serializeWithView(bean, gen, provider, _filteredProps); + BeanPropertyWriter[] fProps = _filteredProps; + if ((fProps != null) && (provider.getActiveView() != null)) { + gen.writeStartObject(bean); + _serializeFieldsWithView(bean, gen, provider, fProps); + gen.writeEndObject(); return; } serializeNonFiltered(bean, gen, provider); @@ -197,64 +206,4 @@ protected void serializeNonFiltered(Object bean, JsonGenerator gen, SerializerPr } gen.writeEndObject(); } - - protected void serializeWithView(Object bean, JsonGenerator gen, SerializerProvider provider, - BeanPropertyWriter[] props) - throws IOException - { - gen.writeStartObject(bean); - BeanPropertyWriter prop = null; - - try { - // NOTE: slightly less optimal as we do not use local variables, need offset - final int offset = props.length-1; - switch (_propCount) { - default: - //case 6: - prop = props[offset-5]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - // fall through - case 5: - prop = props[offset-4]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - case 4: - prop = props[offset-3]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - case 3: - prop = props[offset-2]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - case 2: - prop = props[offset-1]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - case 1: - prop = props[offset]; - if (prop != null) { // can have nulls in filtered list - prop.serializeAsField(bean, gen, provider); - } - } - prop = null; - if (_anyGetterWriter != null) { - _anyGetterWriter.getAndSerialize(bean, gen, provider); - } - } catch (Exception e) { - String name = (prop == null) ? "[anySetter]" : prop.getName(); - wrapAndThrow(provider, e, bean, name); - } catch (StackOverflowError e) { - JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e); - String name = (prop == null) ? "[anySetter]" : prop.getName(); - mapE.prependPath(new JsonMappingException.Reference(bean, name)); - throw mapE; - } - gen.writeEndObject(); - } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java index 7c425020e8..7d106e58e8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/UnwrappingBeanSerializer.java @@ -117,12 +117,17 @@ public final void serialize(Object bean, JsonGenerator gen, SerializerProvider p // (although... is that a problem, overwriting it now?) gen.setCurrentValue(bean); // [databind#631] if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, gen, provider); - } else { - serializeFields(bean, gen, provider); + _serializeFieldsFiltered(bean, gen, provider, _propertyFilterId); + return; + } + BeanPropertyWriter[] fProps = _filteredProps; + if ((fProps != null) && (provider.getActiveView() != null)) { + _serializeFieldsWithView(bean, gen, provider, fProps); + return; } + _serializeFieldsNoView(bean, gen, provider, _props); } - + @Override public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException @@ -135,12 +140,18 @@ public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider _serializeWithObjectId(bean, gen, provider, typeSer); return; } - gen.setCurrentValue(bean); // [databind#631] + // Because we do not write start-object need to call this explicitly: + gen.setCurrentValue(bean); if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, gen, provider); - } else { - serializeFields(bean, gen, provider); + _serializeFieldsFiltered(bean, gen, provider, _propertyFilterId); + return; + } + BeanPropertyWriter[] fProps = _filteredProps; + if ((fProps != null) && (provider.getActiveView() != null)) { + _serializeFieldsWithView(bean, gen, provider, fProps); + return; } + _serializeFieldsNoView(bean, gen, provider, _props); } /* diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java index fb92793b54..da0f1b4627 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java @@ -603,7 +603,7 @@ public void serializeWithType(Object bean, JsonGenerator gen, WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_OBJECT); typeSer.writeTypePrefix(gen, typeIdDef); if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, gen, provider); + _serializeFieldsFiltered(bean, gen, provider, _propertyFilterId); } else { serializeFields(bean, gen, provider); } @@ -631,7 +631,7 @@ protected final void _serializeWithObjectId(Object bean, JsonGenerator gen, } objectId.writeAsField(gen, provider, w); if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, gen, provider); + _serializeFieldsFiltered(bean, gen, provider, _propertyFilterId); } else { serializeFields(bean, gen, provider); } @@ -669,7 +669,7 @@ protected void _serializeObjectId(Object bean, JsonGenerator g, typeSer.writeTypePrefix(g, typeIdDef); objectId.writeAsField(g, provider, w); if (_propertyFilterId != null) { - serializeFieldsFiltered(bean, g, provider); + _serializeFieldsFiltered(bean, g, provider, _propertyFilterId); } else { serializeFields(bean, g, provider); } @@ -691,66 +691,159 @@ protected final WritableTypeId _typeIdDef(TypeSerializer typeSer, /* /********************************************************** - /* Field serialization methods + /* Field serialization methods, 3.0 /********************************************************** */ - protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) + /** + * Method called called when neither JSON Filter is to be applied, nor + * view-filtering. This means that all property writers are non null + * and can be called directly. + * + * @since 3.0 + */ + protected void _serializeFieldsNoView(Object bean, JsonGenerator gen, + SerializerProvider provider, BeanPropertyWriter[] props) throws IOException { - final BeanPropertyWriter[] props; - if (_filteredProps != null && provider.getActiveView() != null) { - props = _filteredProps; - } else { - props = _props; + int i = 0; + int left = props.length; + BeanPropertyWriter prop = null; + + try { + if (left > 3) { + do { + prop = props[i]; + prop.serializeAsField(bean, gen, provider); + prop = props[i+1]; + prop.serializeAsField(bean, gen, provider); + prop = props[i+2]; + prop.serializeAsField(bean, gen, provider); + prop = props[i+3]; + prop.serializeAsField(bean, gen, provider); + left -= 4; + i += 4; + } while (left > 3); + } + switch (left) { + case 3: + prop = props[i++]; + prop.serializeAsField(bean, gen, provider); + case 2: + prop = props[i++]; + prop.serializeAsField(bean, gen, provider); + case 1: + prop = props[i++]; + prop.serializeAsField(bean, gen, provider); + } + if (_anyGetterWriter != null) { + prop = null; + _anyGetterWriter.getAndSerialize(bean, gen, provider); + } + } catch (Exception e) { + String name = (prop == null) ? "[anySetter]" : prop.getName(); + wrapAndThrow(provider, e, bean, name); + } catch (StackOverflowError e) { + JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e); + String name = (prop == null) ? "[anySetter]" : prop.getName(); + mapE.prependPath(new JsonMappingException.Reference(bean, name)); + throw mapE; } + } + /** + * Method called called when no JSON Filter is to be applied, but + * View filtering is in effect and so some of properties may be + * nulls to check. + * + * @since 3.0 + */ + protected void _serializeFieldsWithView(Object bean, JsonGenerator gen, + SerializerProvider provider, BeanPropertyWriter[] props) + throws IOException + { int i = 0; + int left = props.length; + BeanPropertyWriter prop = null; + try { - for (final int len = props.length; i < len; ++i) { - BeanPropertyWriter prop = props[i]; - if (prop != null) { // can have nulls in filtered list + if (left > 3) { + do { + prop = props[i]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + prop = props[i+1]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + prop = props[i+2]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + prop = props[i+3]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + left -= 4; + i += 4; + } while (left > 3); + } + switch (left) { + case 3: + prop = props[i++]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + case 2: + prop = props[i++]; + if (prop != null) { + prop.serializeAsField(bean, gen, provider); + } + case 1: + prop = props[i++]; + if (prop != null) { prop.serializeAsField(bean, gen, provider); } } if (_anyGetterWriter != null) { + prop = null; _anyGetterWriter.getAndSerialize(bean, gen, provider); } } catch (Exception e) { - String name = (i == props.length) ? "[anySetter]" : props[i].getName(); + String name = (prop == null) ? "[anySetter]" : prop.getName(); wrapAndThrow(provider, e, bean, name); } catch (StackOverflowError e) { - // 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many - // stack frames to spare... just one or two; can't make many calls. - - // 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly: - //JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e); JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e); - - String name = (i == props.length) ? "[anySetter]" : props[i].getName(); + String name = (prop == null) ? "[anySetter]" : prop.getName(); mapE.prependPath(new JsonMappingException.Reference(bean, name)); throw mapE; } } + /* + /********************************************************** + /* Field serialization methods, 2.x + /********************************************************** + */ + /** * Alternative serialization method that gets called when there is a * {@link PropertyFilter} that needs to be called to determine * which properties are to be serialized (and possibly how) */ - protected void serializeFieldsFiltered(Object bean, JsonGenerator gen, - SerializerProvider provider) - throws IOException, JsonGenerationException + protected void _serializeFieldsFiltered(Object bean, JsonGenerator gen, + SerializerProvider provider, Object filterId) + throws IOException { - /* note: almost verbatim copy of "serializeFields"; copied (instead of merged) - * so that old method need not add check for existence of filter. - */ + // note: almost verbatim copy of "serializeFields"; copied (instead of merged) + // so that old method need not add check for existence of filter. final BeanPropertyWriter[] props; if (_filteredProps != null && provider.getActiveView() != null) { props = _filteredProps; } else { props = _props; } - final PropertyFilter filter = findPropertyFilter(provider, _propertyFilterId, bean); + final PropertyFilter filter = findPropertyFilter(provider, filterId, bean); // better also allow missing filter actually.. if (filter == null) { serializeFields(bean, gen, provider); @@ -780,6 +873,48 @@ protected void serializeFieldsFiltered(Object bean, JsonGenerator gen, } } + @Deprecated + private void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) + throws IOException + { + final BeanPropertyWriter[] props; + if (_filteredProps != null && provider.getActiveView() != null) { + props = _filteredProps; + } else { + props = _props; + } + int i = 0; + try { + for (final int len = props.length; i < len; ++i) { + BeanPropertyWriter prop = props[i]; + if (prop != null) { // can have nulls in filtered list + prop.serializeAsField(bean, gen, provider); + } + } + if (_anyGetterWriter != null) { + _anyGetterWriter.getAndSerialize(bean, gen, provider); + } + } catch (Exception e) { + String name = (i == props.length) ? "[anySetter]" : props[i].getName(); + wrapAndThrow(provider, e, bean, name); + } catch (StackOverflowError e) { + // Dealing with this is tricky, since we don't have many stack frames to spare + // So to avoid "from" method, call ctor directly: + //JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e); + JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e); + + String name = (i == props.length) ? "[anySetter]" : props[i].getName(); + mapE.prependPath(new JsonMappingException.Reference(bean, name)); + throw mapE; + } + } + + /* + /********************************************************** + /* Introspection (for schema generation etc) + /********************************************************** + */ + @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException