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

(perf) avoid object.keys on hot path for performance - simplify #772

Merged
merged 2 commits into from
Jan 7, 2018

Conversation

vigneshshanmugam
Copy link
Member

@vigneshshanmugam vigneshshanmugam commented Dec 29, 2017

  • I am getting rid of this logic since its not used for anything else other than expression type

  • Doing this yields an 1-1.5 seconds improvement on average on a large 2 MB files.

Before

minify-simplify 10689.635
minify-simplify 10661.954

After

minify-simplify 8829.373
minify-simplify 9054.459

This is not a huge improvement but still helps when used on bigger bundles.

  • I am also not sure why iterating over ~250 elements in babel types and creating symbols in global symbol registry is really slow. Not sure if symbol creation logic is slow on v8. /cc @mathiasbynens Can you help here?

Sample code

const minify = require("../packages/babel-minify");
const fs = require("fs");
const path = require("path");

const code = fs.readFileSync(
  path.join(__dirname, "huge-file.js"), // replace it with file > 2 MB
  "utf-8"
);

console.time("minify");
minify(code);
console.timeEnd("minify");

Got to know about the issue on profiling it using chrome devtools.

@vigneshshanmugam vigneshshanmugam added discussion perf Topics related to Performance labels Dec 29, 2017
@mathiasbynens
Copy link
Contributor

I’m on vacation and haven’t looked at this in detail, but I’ll look into whether we can do anything to speed up symbol creation in V8. cc @bmeurer

// computing this involves checking object.keys() to be of length 0
// skipped otherwise
const types = {};
const typeSymbols = t => {
Copy link
Member

Choose a reason for hiding this comment

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

I made it like this to solve a more general case of different types (Expression, LogicalExpression, etc.. - but LogicalExpression was recently removed leaving out expression as the only type used now.) in pattern matching - keeping only Expression seems wrong usage and this entire file helpers can be removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah i totally understand that this was for general use case. But when i was playing around, I figured out that we are not using it for anything other than expression and symbol creation was bit slow in v8. So thought of keeping it open here so we can figure out whats the best.

return types;
};

const isNodeOfType = (t, node, typeSymbol) =>
Copy link
Member

Choose a reason for hiding this comment

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

To me this function isNodeOfType looks like the reason for the slow-down. The t["is" + Symbol.keyFor(typeForSymbol)] property access looks particularly bad, since it's highly megamorphic and always passes a newly concatenated string, which means it'll always use the slow-path via the C++ runtime.

Copy link
Member Author

Choose a reason for hiding this comment

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

@bmeurer Thanks for finding out the root cause.

Copy link
Member

Choose a reason for hiding this comment

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

Did you verify my theory? I just guessed based on staring at your diff 😁

Copy link
Member Author

@vigneshshanmugam vigneshshanmugam Jan 3, 2018

Choose a reason for hiding this comment

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

My bad, I did check now and don't think that was the cause. It does have a minor increase though

Before

screen shot 2018-01-03 at 14 39 13

After (Without polymorphic state)

screen shot 2018-01-03 at 14 33 32

Symbol creation logic

const typeSymbols = t => {
  // don't recompute
  if (Object.keys(types).length < 1) {
    t.TYPES.forEach(type => {
      types[type] = Symbol.for(type);
    });
  }
  return types;
};

Still the problem of creating too many symbols stays the same as i stated before.

screen shot 2018-01-03 at 14 44 05

Copy link
Member

Choose a reason for hiding this comment

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

That's really odd. Can you run with --runtime-call-stats and search for SymbolFor in the output?

Copy link
Member Author

@vigneshshanmugam vigneshshanmugam Jan 4, 2018

Choose a reason for hiding this comment

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

We invoke twice per minification process. But we compute it only once and return it from cache for successive invocations.

Copy link
Member

Choose a reason for hiding this comment

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

Can you post the first 10 items from --runtime-call-stats?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure.

        JS_Execution  22563.74ms  65.10%       780   0.01%
                  GC   5822.25ms  16.80%      6042   0.06%
    FunctionCallback   1567.68ms   4.52%      7327   0.07%
    KeyedGetProperty    742.17ms   2.14%   3932424  36.55%
RecompileSynchronous    729.72ms   2.11%      1414   0.01%
          ArrayConcat    401.79ms   1.16%    588038   5.47%
          ObjectKeys    389.82ms   1.12%     35271   0.33%
          StringSplit    379.40ms   1.09%   1751662  16.28%
    WeakCollectionSet    325.77ms   0.94%    523176   4.86%
          ArrayShift    136.54ms   0.39%    439691   4.09%
          ArraySlice    120.08ms   0.35%    278001   2.58%
          SetProperty    109.09ms   0.31%    272005   2.53%
ParseFunctionLiteral    104.88ms   0.30%      4990   0.05%


Copy link
Member Author

Choose a reason for hiding this comment

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

The only relevant thing I can think of from the list is Object.keys which we are using as part of typeSymbols.

Copy link
Member Author

Choose a reason for hiding this comment

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

After few confirmations, changing Object.keys with simple check is way faster. results here https://gist.github.com/vigneshshanmugam/c766550ecd02292dcdfbf0bf013b9d3d

t.TYPES.forEach(type => {
types[type] = Symbol.for(type);
});
if (types[testKey] !== undefined) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Yields around 300-400ms on average - Results -> https://gist.github.com/vigneshshanmugam/c766550ecd02292dcdfbf0bf013b9d3d

@vigneshshanmugam vigneshshanmugam changed the title remove types to symbol logic for performance gains - simplify avoid object.keys on hot path for performance - simplify Jan 5, 2018
@bmeurer
Copy link
Member

bmeurer commented Jan 7, 2018

Awesome finding! I totally didn't realize that Object.keys is on the hot path here, sorry.

@boopathi boopathi merged commit 879f25e into master Jan 7, 2018
@boopathi boopathi deleted the perf-test branch January 7, 2018 16:35
@vigneshshanmugam vigneshshanmugam added Tag: Chore Pull Request updating grunt tasks etc... Tag: Polish Pull Request for formatting, style changes, code cleanups, comments etc... and removed Tag: Chore Pull Request updating grunt tasks etc... labels Jan 31, 2018
@vigneshshanmugam vigneshshanmugam changed the title avoid object.keys on hot path for performance - simplify (perf)avoid object.keys on hot path for performance - simplify Jan 31, 2018
@vigneshshanmugam vigneshshanmugam changed the title (perf)avoid object.keys on hot path for performance - simplify (perf) avoid object.keys on hot path for performance - simplify Jan 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion perf Topics related to Performance Tag: Polish Pull Request for formatting, style changes, code cleanups, comments etc...
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants