-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Speed up font-width computation in most cases #1390
Conversation
Generated by 🚫 dangerJS |
I can replicate this failure locally. It must have crept in as I was cleaning up the code. I think the problem is due to global state and test ordering. They used to pass locally regardless of whether I chose Verdana or DejaVu Sans. Now they pass locally only with Verdana. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot! This is solid work!
I can confirm that I replicate your findings related to the benchmark with a modification of this patch (going from 0.197ms to 0.048ms).
lib/measure-text.js
Outdated
module.exports = { | ||
PDFKitTextMeasurer, | ||
QuickTextMeasurer, | ||
// measure: defaultMeasurer.measure.bind(defaultMeasurer), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked having the measure function as the default export.
Also, I see no benefit to using promises for server initialization procedures.
Could we simply have initialization be synchronous?
(As a result, the distinction between new TextMeasurer()
and TextMeasurer.create()
is not that useful.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked having the measure function as the default export.
Will try to restore that. Was in the middle of cleaning up a gnarly module state bug, though if the initialization can all be done synchronously that will help a lot. Building the measurer is a little slow, so it would probably be good to avoid doing it when it's not necessary. Will think about a way to do that.
Could we simply have initialization be synchronous?
Totally! I completely missed that. loadFont
was using a callback before and I robotically ported it to promises, not realizing all the work was synchronous. That's great. It'll simplify this a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up removing this, because the quick measurer takes a while to generate, and it was slowing down the CLI and the CLI tests. Plus it makes running exclusive tests longer, as the cache needs to be built even if it's not used by the tests being run.
lib/make-badge.js
Outdated
@@ -152,6 +152,5 @@ function makeBadge (data) { | |||
} | |||
|
|||
module.exports = makeBadge; | |||
module.exports.loadFont = measureTextWidth.loadFont; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was that umused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I think so. It might have been part of the gh-badges library usage. If it is I'll make sure the docs are updated as part of #1388.
lib/measure-text.js
Outdated
} | ||
} | ||
|
||
loadFont(path.join(__dirname, '..', 'Verdana.ttf'), function (err) { | ||
if (err && process.env.FALLBACK_FONT_PATH) { | ||
loadFont(process.env.FALLBACK_FONT_PATH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you plan on keeping supporting FALLBACK_FONT_PATH and using FONT_PATH?
If I am reading the patch correctly, you currently no longer use any of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, sorry, that bit was WIP. I'll get it fixed up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
lib/measure-text.js
Outdated
loadFont(process.env.FALLBACK_FONT_PATH); | ||
class QuickTextMeasurer { | ||
constructor(baseMeasurer) { | ||
Object.assign(this, { baseMeasurer }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we benefit from the generality this is meant to create?
Shouldn't we directly use PDFKitTextMeasurer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Injecting it makes testing a little easier, because I can place the spy on the measurer instance. Caching code is notoriously difficult to test so I feel it's important to test that the cache object doesn't call through to the base object when it's not being used. I'll take a look though; maybe that is easily changed now. When this is all synchronous it'll be way simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is working just fine by mocking the method on the prototype instead!
Thanks for the review! Will pick this up today and respond to your comments. |
This needs cleanup…
This reverts commit 267effd.
I re-ran the benchmark on the last commit and the 73% is holding up. I got 0.041 vs 0.144 in master. |
This simplifies and further optimizes text-width computation by computing the entire width table in advance, and serializing it in the style of QuickTextMeasurer (#1390). This entirely removes the need for PDFKit at runtime. This has the advantage of fixing #1305 – more generally: producing the same result everywhere – without having to deploy a copy of Verdana. The lifting is delegated to these three libraries, which are housed in a monorepo: https://github.com/metabolize/anafanafo I'd be happy to move it into the badges org if folks want to collaborate on maintaining them. QuickTextMeasurer took kerning pairs into account, whereas this implementation does not. I was thinking kerning would be a necessary refinement, though this seems to work well enough. I dropped in a binary-search package to traverse the data structure, in part to conserve space. This causes a moderate performance regression, though there is ample room for improving on that: #2311 (comment)
Ref: #1379
This takes a naive approach to font-width computation, the most compute-intensive part of rendering badges.
This branch averaged 0.049 ms in
makeBadge
, compared to 0.182 ms on master, a speedup of 73%. That was on a test of 10,000 consecutive requests (using the method in #1379).The speedup applies to badges containing exclusively printable ASCII characters. It wouldn't be as dramatic on non-ASCII text. Though, we could add some frequently used non-ASCII characters to the cached set.