Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Performance] Export Model constructor throws exceptions on very hot paths #127

Merged
merged 10 commits into from
Sep 6, 2017

Conversation

i386
Copy link
Contributor

@i386 i386 commented Aug 16, 2017

Removes additional millis per call on large exports by avoiding throwing NotExportableException and instead using annotation check to check exportability e.g. type.getAnnotation(ExportedBean.class)

See jenkinsci/blueocean-plugin#1323

@reviewbybees #NightTimeCode

Adds additional millis per call on large exports.
@i386 i386 changed the title Model constructor throws exceptions on very hot paths [Performance] Export Model constructor throws exceptions on very hot paths Aug 16, 2017
@ghost
Copy link

ghost commented Aug 16, 2017

This pull request originates from a CloudBees employee. At CloudBees, we require that all pull requests be reviewed by other CloudBees employees before we seek to have the change accepted. If you want to learn more about our process please see this explanation.

}

try {
writer.type(expected, value.getClass());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like 🐛 writer.type() should be called before writing the object on line 209.

for (int i=r.min; i<len; i++) {
writeBuffered(act, Array.get(value, i), pruner, writer);
}
writer.type(expected, value.getClass());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should move down inside else condition below, like 211. handleNotExportable has its own logic to write type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right you are. Moved.

Copy link
Contributor

@vivek vivek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🐝

@i386
Copy link
Contributor Author

i386 commented Aug 17, 2017

Stapler passed locally.

@i386
Copy link
Contributor Author

i386 commented Aug 17, 2017

@stephenc @jglick @daniel-beck a review here would be fantastic please :)

Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General idea sounds right. Seems to be at least one mistake here though.

@@ -42,12 +42,22 @@
/*package*/ final Map<Class, Model> models = new ConcurrentHashMap<Class, Model>();

public <T> Model<T> get(Class<T> type) throws NotExportableException {
if (type.getAnnotation(ExportedBean.class) == null) {
throw new NotExportableException(type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not redundant? AFAICT the method it delegates to does the same thing.

return get(type, null, null);
}

public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why removing the throws clause?

return get(type, null, null);
}

public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException {
public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) {
if (type.getAnnotation(ExportedBean.class) == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you rather meant to write

Model<T> model = getOrNull(type, propertyOwner, property);
if (model == null) {
    throw new NotExportableException(type);
}
return model;

return get(type, null, null);
}

public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException {
public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull return

return getOrNull(type, propertyOwner, property);
}

public <T> Model<T> getOrNull(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CheckForNull return. And why is this declared to throw NotExportableException?

@@ -131,23 +131,22 @@ public void writeTo(Object object, TreePruner pruner, DataWriter writer) throws
TreePruner child = pruner.accept(object, this);
if (child==null) return;

Object d = writer.getExportConfig().getExportInterceptor().getValue(this,object, writer.getExportConfig());
Object d = writer.getExportConfig().getExportInterceptor().getValue(this,object,writer.getExportConfig());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gratuitous, revert

model.writeNestedObjectTo(value, pruner, writer);
writer.endObject();
} catch (NotExportableException ex) {
handleNotExportable(expected, value, pruner, writer, skipIfFail, c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduced diff is easier to read here (generally dislike patches that change indentation of large existing code blocks but sometimes it is hard to avoid)

@i386
Copy link
Contributor Author

i386 commented Aug 17, 2017

@jglick thanks for the review. There was a fair bit of dumb code here on my part, so I did my best to fix it.

for (Map.Entry e : ((Map<?,?>) value).entrySet()) {
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig());
try {
writeStartObjectNullType(buffer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to follow this long diff, but IIUC you are basically just extracting handleNotExportable and then calling it twice, right? Pretty much unreviewable so I hope the test coverage here is good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the whitespace-reduced diff is more approachable; there must have been a change in indentation for a long block of code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, Im going to revert the method extraction. Still too long for my taste but should preserve the diff.

if(STRING_TYPES.contains(c)) {
writer.value(value.toString());
return;
if (c.getAnnotation(ExportedBean.class) == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing something or would this not be caught by the null return value from getOrNull a few lines later anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right. Fixed.

Copy link
Member

@oleg-nenashev oleg-nenashev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM, some minor comments

}

@CheckForNull
public <T> Model<T> getOrNull(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs explanation of what "null" result means (with reference to the @ExportedBean custom handling)

Nice to have: @since TODO in Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return;
}
throw e;
Model model = owner.getOrNull(d.getClass(), parent.type, name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you are around, it's better to move d.getClass() to a local variable. Also improves performance of code/JIT a bit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

throw e;
Model model = owner.getOrNull(d.getClass(), parent.type, name);
if (model == null && !writer.getExportConfig().isSkipIfFail()) {
throw new NotExportableException(d.getClass());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe skipped issues deserve logging on a DEBUG level

for (Map.Entry e : ((Map<?,?>) value).entrySet()) {
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig());
try {
writeStartObjectNullType(buffer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the whitespace-reduced diff is more approachable; there must have been a change in indentation for a long block of code.

}
}

private void handleNotExportable(Type expected, Object value, TreePruner pruner, DataWriter writer, boolean skipIfFail, Class c) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name is potentially misleading. Really this method is handling anything without @ExportedBean, but that includes all sorts of objects which are “exportable”. It is only at the very end, starting where we check skipIfFail, that this method is in fact handling objects which are not exportable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed and added a description.

@@ -56,6 +60,12 @@
return model;
}

/**
* Instead of throwing {@link NotExportableException} this method will return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW tip: terminate summary sentences with ..

@jglick jglick merged commit 06d268c into jenkinsci:master Sep 6, 2017
jglick added a commit that referenced this pull request Sep 6, 2017
@i386 i386 deleted the topic/performance-enhancements branch October 9, 2017 02:20
i386 pushed a commit to jenkinsci/blueocean-plugin that referenced this pull request Oct 9, 2017
i386 pushed a commit to jenkinsci/blueocean-plugin that referenced this pull request Oct 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants