Skip to content

Optimize luminance function as simple look-up#4437

Merged
thisisdano merged 2 commits into
uswds:developfrom
aduth:aduth-luminance-perf
Jan 16, 2022
Merged

Optimize luminance function as simple look-up#4437
thisisdano merged 2 commits into
uswds:developfrom
aduth:aduth-luminance-perf

Conversation

@aduth

@aduth aduth commented Dec 21, 2021

Copy link
Copy Markdown
Contributor

Description

This pull request seeks to improve the performance of the luminance function, which is currently implemented using a combination of math polyfills necessary to compute an fractional exponent value following the relative luminance formula.

Additional information

Because the relative luminance formula uses the same calculation for each of the R(ed), G(reen), and B(lue) components of a color, and because RGB component values are guaranteed to be a value between 0 and 255, it is possible to reasonably pre-compute the result for each value between 0 and 255, allowing for a trivial O(1) dictionary lookup.

I calculated the results using the following JavaScript script:

[...new Array(256)].map((v, i) => i / 255).map((v) => v < 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)).join(', ')

Evaluated using Node.js:

node -p "[...new Array(256)].map((v, i) => i / 255).map((v) => v < 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)).join(', ')"

Results

The performance of the previous implementation seemed to worsen exponentially based on the number of times it's called. In a reasonable benchmarking scenario of 20 calls, execution time is reduced by ~94%.

Benchmarking code:

// example.scss
@import './src/stylesheets/packages/required';

@for $i from 1 to 20 {
  $result: luminance(color('primary'));
}

Results, using Ruby Sass:

$ time sass example.scss
sass example.scss  28.07s user 0.32s system 99% cpu 28.634 total

$ time sass example.scss
sass example.scss  1.54s user 0.20s system 98% cpu 1.773 total

I had attempted to measure results in (native) Dart Sass, but the math of the previous logic seems to not work at all for me in Dart Sass, due to a division by zero (see related #4438). Thus, this refactoring could also serve as a bug fix for Dart Sass.


Before you hit Submit, make sure you’ve done whichever of these applies to you:

  • Follow the 18F Front End Coding Style Guide and Accessibility Guide.
  • Run npm test and make sure the tests for the files you have changed have passed.
  • Run your code through HTML_CodeSniffer and make sure it’s error free.
  • Title your pull request using this format: [Website] - [UI component]: Brief statement describing what this pull request solves.

@aduth

aduth commented Dec 21, 2021

Copy link
Copy Markdown
Contributor Author

I forgot to mention: If the plan with USWDS v3 is to drop support for non-Dart SASS (i.e. use SASS module syntax), then this could also be solved by using the new built-in math.pow, which I assume performs reasonably-well. A look-up would still probably be faster, but at least in that case you wouldn't need to include a huge list of 256 float values 😅

The main advantage of this implementation is that it works across all compilers.

@aduth aduth changed the title Optimize luminance function as O(1) look-up Optimize luminance function as simple look-up Dec 22, 2021

@mejiaj mejiaj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pretty nice! Thanks for this.

I wonder if we should move the $luminance-values somewhere else. Either inside the function or to core/_variables.scss. Otherwise LGTM!

@aduth

aduth commented Jan 13, 2022

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @mejiaj !

I wonder if we should move the $luminance-values somewhere else. Either inside the function or to core/_variables.scss.

Yeah, I think that could make sense to move it to _variables.scss. Maybe under a new section "Luminance", since I don't see an existing section that would be a great fit.

Keeping it outside the function might be a soft preference for me, since while I don't know how SASS optimizes variables within functions, I might worry if it's the sort of thing where the 255-entry list would be allocated/garbage collected for every call.

@mejiaj

mejiaj commented Jan 13, 2022

Copy link
Copy Markdown
Contributor

Keeping it outside the function might be a soft preference for me, since while I don't know how SASS optimizes variables within functions, I might worry if it's the sort of thing where the 255-entry list would be allocated/garbage collected for every call.

That's a good point. Moving this to final review 🙂

@mejiaj mejiaj requested a review from thisisdano January 13, 2022 14:37
@aduth

aduth commented Jan 14, 2022

Copy link
Copy Markdown
Contributor Author

I had attempted to measure results in (native) Dart Sass, but the math of the previous logic seems to not work at all for me in Dart Sass, due to a division by zero (see related #4438). Thus, this refactoring could also serve as a bug fix for Dart Sass.

For what it's worth, digging into this, it might be related to maximum integer size in Dart, causing the factorial function to return 0, and since the result of that is used as a denominator in exp, it's a division by zero.

Example script to reproduce:

@function factorial($value) {
  $result: 1;

  @if $value == 0 {
    @return $result;
  }

  @for $index from 1 through $value {
    $result: $result * $index;
    @debug 'index #{$index}, result #{$result}';
  }

  @return $result;
}

@debug factorial(99);
example.scss:10 Debug: index 1, result 1
example.scss:10 Debug: index 2, result 2
example.scss:10 Debug: index 3, result 6
...
example.scss:10 Debug: index 65, result -9223372036854775808
example.scss:10 Debug: index 66, result 0
...
example.scss:10 Debug: index 99, result 0

@thisisdano thisisdano merged commit dc5e06c into uswds:develop Jan 16, 2022
@thisisdano

Copy link
Copy Markdown
Contributor

Thank you!

@sawyerh

sawyerh commented Feb 16, 2022

Copy link
Copy Markdown
Contributor

@thisisdano @aduth Just wanted to pop in and say THANK YOU THANK YOU THANK YOU. It has always confused me why the CSS build step took so long in my application and this made a noticeable dent.

@aduth aduth deleted the aduth-luminance-perf branch February 16, 2022 21:49
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