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

Improve rendering performance #1993

Merged

Conversation

mohd-akram
Copy link
Contributor

@mohd-akram mohd-akram commented Sep 3, 2023

Avoid unnecessary copies via Utils.extend in hot paths.

This leads to a 2x improvement in my testing.

See #1991.

Copy link
Member

@jaylinski jaylinski left a comment

Choose a reason for hiding this comment

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

Thanks for the improvement! LGTM, security tests are also passing.

@jaylinski jaylinski added this to the 4.7.9 milestone Sep 6, 2023
@aleen42
Copy link

aleen42 commented Aug 30, 2024

any progress?

@jaylinski
Copy link
Member

I was hoping to get a review from @nknapp, since he introduced the Utils.extend()-code in commit 2078c72 to address a security issue.

I'm also not sure if the new code causes side-effects, since it mutates an object now (before it created a copy).

As I said, the tests are good, so it should be good to merge. I'll wait for a week for @nknapp to share his thoughts, otherwise I'll merge it.

context,
extendedOptions
);
options.hooks = this.hooks;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it is good practice to modify the options object as a side-effect.

I can see that "Util.extend" may be slow, since it checks for "hasOwnProperty" all the time, but I would at least try to create a shallow copy of the object.

Maybe

const extendedOptions = {
    ...options,
    hooks: this.hooks,
    protoAccessControl: this.protoAccessControl
}

Could you check how the performance behaves for that code?
Apart from that, since there is a test for "blockHelperMissing" and "helperMissing", I don't think there is a security risk here, although I feel to far off the topic to give a good estimate.

What you could also try is just calling Object.assign instead of "Util.extend".

This just occured to me, but "Object.assign" seems to do extactly the same thing as "Util.extend", probably faster, since it is built-in.

For backwards compatibily with historic browsers, maybe in "utils.js" do

export const extend = Object.assign || function extend(obj/* , ...source */) {
  for (let i = 1; i < arguments.length; i++) {
    for (let key in arguments[i]) {
      if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
        obj[key] = arguments[i][key];
      }

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this proposal will ruin test-coverage, but maybe there is a way around that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried profiling this using the script in the linked issue. This PR is 6% faster than using Object.assign, and 12% faster than using Utils.extend. Note that the options object is already modified in this function in a couple places (options.ids and options.partials). It's also modified in the invokePartial function. My understanding is that options is a literal that comes from the template, so it's always new, and this function just augments that. There's no benefit to copying AFAICT.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I can see what you say. I still think that side-effects are bad, but I can understand your motivations.

Its been a long time that I wrote this code, but I think the main reason to use "extend" here was clean code and avoiding side-effects, not some particular bad thing that would happen otherwise.

I wonder what the performance improvement is on a real-life template though.

@mohd-akram mohd-akram changed the title Improve invokePartialWrapper performance Improve rendering performance Sep 1, 2024
@mohd-akram
Copy link
Contributor Author

While profiling this again, I noticed one more place show up where there are unnecessary copies. I've also put #1994 here, since it's related.

@mohd-akram mohd-akram force-pushed the perf-invokepartialwrapper branch 2 times, most recently from 9c8dc2b to 93a7125 Compare September 1, 2024 16:04
Avoid unnecessary copies via Utils.extend in hot paths.
@jaylinski
Copy link
Member

Two checks are failing, but this is because of nodejs/node#52554.

Improvements will be also applied to master.

@jaylinski jaylinski merged commit e914d60 into handlebars-lang:4.x Sep 3, 2024
13 of 15 checks passed
@mohd-akram mohd-akram deleted the perf-invokepartialwrapper branch September 4, 2024 02:46
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