-
Notifications
You must be signed in to change notification settings - Fork 44
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
feat: add --fast mode #49
Conversation
Skipping frames can artificially make the visual jitter (jankiness) go away from the calculations. Please make sure that layout instability effects that perceptual speed index captures aren't sacrificed due to this frame-skipping (technically, this is temporal downsampling). |
@pahammad by its nature, the impact on the indexes cannot be avoided as you point out. However, in my experience there are a number of issues with using these metrics too as a signal of layout stability in the first place and frequently I've seen jitter artificially be rewarded by speed index rather than punished. Would a warning and tunable threshold address your concerns or are you against including the option at all with these drawbacks? |
I am OK with including the frame-skipping as an option if it comes with appropriate warning when the option is exercised. I am very curious about your sentence: "frequently I've seen jitter artificially be rewarded by speed index rather than punished". Are you referring to the classical (histogram-based) speed index, or are you referring to SSIM based perceptual speed index (PSI) ? If you are referring to PSI in that sentence (where it rewards jitter instead of punishing it), I would love to see an example or two - this is opposite to what I've seen. |
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 for doing this. will be a big help.
test/speed-index.js
Outdated
@@ -136,6 +136,15 @@ test('speed indexes calculated for realistic trace', async t => { | |||
t.is(Math.floor(indexes.perceptualSpeedIndex), 578); | |||
}); | |||
|
|||
test('speed indexes calculated for realistic trace with --fast', async t => { |
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.
let's also assert that we didn't call getPerceptualProgress, etc on every frame, since that's the essence of this.
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.
done
src/speed-index.js
Outdated
frames, | ||
0, | ||
frames.length - 1, | ||
opts && opts.fast ? 5 : 0, |
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.
set 5 as a constant above?
reading this line it's hard to understand what "5" as a "threshold" even means.
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.
yup will do
src/speed-index.js
Outdated
0, | ||
frames.length - 1, | ||
opts && opts.fast ? 5 : 0, | ||
frame => { |
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.
extract this and the next fn into named variables
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.
done
src/speed-index.js
Outdated
0, | ||
frames.length - 1, | ||
opts && opts.fast ? 5 : 0, | ||
frame => frame.getProgress() || calculateFrameProgress(frame, initial, target), |
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.
maybe not enough coffee, but ... brain hurts.
this is an expression of fn1 || fn2
and will be passed in as the getProgress
parameter.
when called, the fn1 will evaluate and then return and if that result is falsy then the fn2
will then run?
is there another way to model this?
src/speed-index.js
Outdated
|
||
if (Math.abs(lowerProgress - upperProgress) < threshold) { | ||
for (let i = lowerBound; i < upperBound; i++) { | ||
setProgress(frames[i], lowerProgress); |
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.
should we also set a property on the frame indicating this value was interpolated? just an idea.
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.
yeah good plan, make the assertion tests easy too
src/speed-index.js
Outdated
const lowerProgress = getProgress(lowerFrame); | ||
const upperProgress = getProgress(upperFrame); | ||
|
||
setProgress(lowerFrame, lowerProgress); |
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.
should we just put the setProgress
call inside of our custom getProgress
fn?
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.
that seems to breakdown the perf win of differentiating between setting the progress and parsing the jpeg to get progress, no?
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.
well i meant we cache the result value and only do the work if necessary.
but admittedly there isn't a nice way to do this with the frame object API.
aiight nevermind.
src/speed-index.js
Outdated
setProgress(lowerFrame, lowerProgress); | ||
setProgress(upperFrame, upperProgress); | ||
|
||
if (Math.abs(lowerProgress - upperProgress) < threshold) { |
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.
as discussed earlier i'm thinking we might want to threshold on time as well.
const FastMode_FrameProgressDelta = 5; // units: percent
const FastMode_TimeDelta = 200; // units: ms
wdyt?
this way we can handle a 5% change over 5 seconds (probably worth inspecting) vs a 5% change over 100ms (not worth it)
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.
yeah I like it, but it'll likely erase a lot of the perf win if it's just straight up &&
. In the CNN case, the same exact image is used across ~10s of the trace which accounts for ~30 frames. Thoughts on combining progress and time?
wdyt of using that fun exponentiation window size function from TTFI?
allowable percent change = f(time elapsed in seconds) = 4 * e^(-.69 * t) + 1
f(0) = 4 + 1 = 5% allowed change when elapsed time is 0s
f(1) = 2 + 1 = 3% allowed change when elapsed time is 1s
f(∞) = 0 + 1 = 1% allowed change when elapsed time is very large
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.
yeah i like that approach.
src/speed-index.js
Outdated
@@ -2,6 +2,31 @@ | |||
|
|||
const imageSSIM = require('image-ssim'); | |||
|
|||
/* BEGIN FAST MODE CONSTANTS - See function doc for explanation */ | |||
const FAST_MODE_ALLOWABLE_CHANGE_MAX = 5; |
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.
speedline doesnt neccessarily follow the google js style guide.
i'd love to take that opportunity to not caps_lock our constants :)
You good with something like... fastModeAllowableChangeMax
?
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.
haha sure
* | ||
* f(t) = FAST_MODE_MULTIPLIER * e^(FAST_MODE_EXPONENTIATION_COEFFICIENT * t) + FAST_MODE_CONSTANT | ||
*/ | ||
function calculateFastModeAllowableChange(elapsedTime) { |
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.
can we add a few tests for calculateFastModeAllowableChange
to just to confirm our landmark values give the right results
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.
done
cli.js
Outdated
console.warn('WARNING: using --fast may result in different metrics due to skipped frames'); | ||
} | ||
|
||
speedIndex(filePath, {fast: cli.flags.fast}).then(function (res) { |
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.
let's change this opt property name from fast
to fastMode
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.
done
t.is(Math.floor(indexes.speedIndex), 4360); | ||
t.is(Math.floor(indexes.perceptualSpeedIndex), 4360); | ||
|
||
// Ensure we skipped computation of most of the 60 fps frames |
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.
loooooooove it
changes lgtm but 2 test failures we need to work out. |
done |
[email protected] published with this. |
This PR changes the progress calculation to optionally skip frames that are between ones that have similar progress toward the target. When enabled, it speeds up processing by about 40% and has minimal impact when not enabled via the shortcut. This also aligns perceptual progress and visual progress by sharing the same computation code and fixes #48 by virtue of the fact that we can't wait for the global min when we only parse some of the JPEGs.
Timings
1 - (34/55) = ~38%
Open Questions
similarProgressThreshold
?