chenglou Github contribution chart
chenglou Github Stats
chenglou Most Used Languages

Activity

30 Sep 2022

Chenglou

Correct aspect ratio

Pushed On 30 Sep 2022 at 09:07:01

Chenglou

Add new images

Pushed On 30 Sep 2022 at 12:24:32

Chenglou

Fix scrolling to image

Multiple calls to scheduleRender makes parameter-passing buggy. State it is

Pushed On 30 Sep 2022 at 12:14:12

Chenglou

Dirty hover effect

Pushed On 30 Sep 2022 at 11:58:21

Chenglou

Hand-rolled grid

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Responsive + cleanup

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Here we go again. Render loop

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Dump

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Tweaks

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Cleanup, move away from border

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Man, simplification is magic sometime

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Dirty code but it works

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Escape to dismiss

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

zIndex fix

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Add back prompts

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Somewhat working prompt

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Emphasize vertical imgs

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Dirty scroll to image

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Cleanup

Pushed On 30 Sep 2022 at 11:36:05

Chenglou

Cleanup

Pushed On 30 Sep 2022 at 11:34:01

Chenglou

Dirty scroll to image

Pushed On 30 Sep 2022 at 11:16:12

Chenglou

Emphasize vertical imgs

Pushed On 30 Sep 2022 at 11:01:20

Chenglou

Somewhat working prompt

Pushed On 30 Sep 2022 at 10:59:28

Chenglou

Add back prompts

Pushed On 30 Sep 2022 at 10:10:12

Chenglou

zIndex fix

Pushed On 30 Sep 2022 at 10:06:34

Chenglou

Escape to dismiss

Pushed On 30 Sep 2022 at 09:57:11

Chenglou

Dirty code but it works

Pushed On 30 Sep 2022 at 09:54:36

Chenglou

Cleanup, move away from border

Pushed On 29 Sep 2022 at 06:03:47

Chenglou

Man, simplification is magic sometime

Pushed On 29 Sep 2022 at 06:03:47

Chenglou

Tweaks

Pushed On 28 Sep 2022 at 12:56:51

Chenglou

Dump

Pushed On 28 Sep 2022 at 12:24:38

Chenglou

Here we go again. Render loop

Pushed On 28 Sep 2022 at 10:33:25

Chenglou

Responsive + cleanup

Pushed On 28 Sep 2022 at 10:30:25
Create Branch

Chenglou

Description not entered by the user.

On 28 Sep 2022 at 09:35:48
Create Branch
Chenglou In chenglou/chenglou.github.io Create Branchuse-native-layout

Chenglou

Description not entered by the user.

On 28 Sep 2022 at 08:08:49

Chenglou

temp

Pushed On 20 Sep 2022 at 07:46:35
Issue Comment

Chenglou

[perf] A lot of time is spent on `getSvgPathFromStroke` when panning

image

This seems to be true: image

I recorded that when panning around this: image

In some frames, it's the longest part: image

I feel like... for panning, the result of this function could be cached? It would probably be useful to have a standardised way of benchmarking this, first of all. I might get round to it at some point!

Forked On 19 Sep 2022 at 12:24:23

Chenglou

What's "Function call" btw? That seems like the bigger bottleneck. Caches would speed up your benchmarks for sure, but would slow down atypical cases. I'd suggest reaching for that last. It'd just make other operations, such as batch resize, even slower (unless you also put some complex caching to that, and to another, and to another).

Commented On 19 Sep 2022 at 12:24:23
Issue Comment

Chenglou

Double hot path perf, halve payload size, maintain readability

Follow-up to https://twitter.com/_chenglou/status/1570895293784391681 and https://twitter.com/_chenglou/status/1567269585585606659 which turned into some nice discussions.

PR not meant to be merged. Feel free to take various bits back into the codebase.

The goal is to improve tldraw and perfect-freehand’s UX and DX through perf improvements. Generally, perf can be obtained either by complicating things, or by simplifying things. Imo we don’t do enough of the latter, so I’d like to show some examples here.

getSvgPathFromStroke turns a list of coordinates into a path string for SVG. It’s the hottest code in perfect-freehand, which is used in tldraw, and one of the hottest there as well, as Steve profiled.

First commit

This is a classic graphics programming array-of-structure to structure-of-array transform where we turn the points data structure Array<[number, number]> into {xs: Array<number>, ys: Array<number>}. You don’t need this. I believe we’re settling with Array<{x: number, y: number}>, which is better readability and good enough perf increase compared to the current format. For more info, see the previous 2 links.

(There’s a 0th commit which turns string appends into string interpolation, also elaborated in the first tweet. Together they improve this hot path by 8-10%).

Second commit

This turns toFixed(3) into toFixed(2) for another 10% overall perf boost (!). toFixed is disproportionally the hottest call here, even if it’s already the most performant way to turn numbers into strings (again, benchmark to see whether it’s true for your own codebase. It’s likely not). A third decimal point's precision isn’t needed. I superimposed complex curves on 1. macOS with 2. Studio Display on 3. Safari with 4. pinch zoom, the extreme quadfecta for stress testing crisp curves, and I've only spotted minor anti-aliasing differences. Code-wise, no readability decrease.

Third commit

toFixed is so hot that there’s basically no point spending effort optimizing anything else. So the third commit aims at it. We’re using the Q command of <path>’s d attribute to create quadratic Bézier curves passing by average(point_i, point_(i+1)). Each Q command takes in 4 numbers that require toFixed stringification. I was thinking that, if there’s a way to use fewer numbers, then we’d have solved this bottleneck. Fortunately there’s exactly T, which implicitly assumes the control point is a reflection of the previous one (good illustrations). But, an average of two points exactly establishes such reflection 🤩. Now instead of using 4 Q numbers, we use 2 T numbers. This also improves readability. Scanned Documents

Halving the numbers halved the calls to toFixed, which gives 200% perf boost along with the earlier commits. These also halved the payload size for the path string. And since perfect-freehand and tldraw’s payload sizes (?) are dominated by Bézier curve paths, this theoretically halves all downstream sharing of their output. Note: real gains will be smaller because, real world.

Lessons

  • Profile first. One’s chance of guessing exactly the right line of code causing problems in a big codebase nears 0%. Conversely, any kind of architecture, dependency and caching added without profiling are likely based on the wrong assumptions. Thankfully, Steve did here.
  • Generic optimizations are nice when they improve readability too.
  • But nothing beats knowing the domain and applying specific optimizations for performance and readability. This has been true for https://twitter.com/_chenglou/status/1570895293784391681 as well, where the domain-specific knowledge of sin and cos in getOutline blew all the other generic optimizations out of the water. The stronger your codebases’ assumptions, the better. As an aside, imo folks' tendency of using generic abstract architectures, either by naively "future-proofing" code, or excessively dragging dependencies (by definition generic), removes critical opportunities for perf and readability improvement, causes extra bugs, which in turns make true abstractions harder to find.

Btw, these changes look simple (that’s the goal) but could have gone out of control if we weren’t careful. None of this acrobatics would have been necessary if DOM was fast and/or took a data structure of points instead of asking us to serialize them into a string, only to be deserialize again anyway.

Forked On 18 Sep 2022 at 01:01:08

Chenglou

It's up to you! My opinion is that some manual profiling like you've done is good enough, then there are bigger fish to fry. You can just keep aiming for changes that increase readability while also casually checking that they're not regressing perf. Since these have proven to be good enough, you can disregard all other optimizations and benchmarking efforts, which usually take more effort than they're worth.

But again, I'm just passing by really, so you do whatever! 😃

Commented On 18 Sep 2022 at 01:01:08
Issue Comment

Chenglou

Double hot path perf, halve payload size, maintain readability

Follow-up to https://twitter.com/_chenglou/status/1570895293784391681 and https://twitter.com/_chenglou/status/1567269585585606659 which turned into some nice discussions.

PR not meant to be merged. Feel free to take various bits back into the codebase.

The goal is to improve tldraw and perfect-freehand’s UX and DX through perf improvements. Generally, perf can be obtained either by complicating things, or by simplifying things. Imo we don’t do enough of the latter, so I’d like to show some examples here.

getSvgPathFromStroke turns a list of coordinates into a path string for SVG. It’s the hottest code in perfect-freehand, which is used in tldraw, and one of the hottest there as well, as Steve profiled.

First commit

This is a classic graphics programming array-of-structure to structure-of-array transform where we turn the points data structure Array<[number, number]> into {xs: Array<number>, ys: Array<number>}. You don’t need this. I believe we’re settling with Array<{x: number, y: number}>, which is better readability and good enough perf increase compared to the current format. For more info, see the previous 2 links.

(There’s a 0th commit which turns string appends into string interpolation, also elaborated in the first tweet. Together they improve this hot path by 8-10%).

Second commit

This turns toFixed(3) into toFixed(2) for another 10% overall perf boost (!). toFixed is disproportionally the hottest call here, even if it’s already the most performant way to turn numbers into strings (again, benchmark to see whether it’s true for your own codebase. It’s likely not). A third decimal point's precision isn’t needed. I superimposed complex curves on 1. macOS with 2. Studio Display on 3. Safari with 4. pinch zoom, the extreme quadfecta for stress testing crisp curves, and I've only spotted minor anti-aliasing differences. Code-wise, no readability decrease.

Third commit

toFixed is so hot that there’s basically no point spending effort optimizing anything else. So the third commit aims at it. We’re using the Q command of <path>’s d attribute to create quadratic Bézier curves passing by average(point_i, point_(i+1)). Each Q command takes in 4 numbers that require toFixed stringification. I was thinking that, if there’s a way to use fewer numbers, then we’d have solved this bottleneck. Fortunately there’s exactly T, which implicitly assumes the control point is a reflection of the previous one (good illustrations). But, an average of two points exactly establishes such reflection 🤩. Now instead of using 4 Q numbers, we use 2 T numbers. This also improves readability. Scanned Documents

Halving the numbers halved the calls to toFixed, which gives 200% perf boost along with the earlier commits. These also halved the payload size for the path string. And since perfect-freehand and tldraw’s payload sizes (?) are dominated by Bézier curve paths, this theoretically halves all downstream sharing of their output. Note: real gains will be smaller because, real world.

Lessons

  • Profile first. One’s chance of guessing exactly the right line of code causing problems in a big codebase nears 0%. Conversely, any kind of architecture, dependency and caching added without profiling are likely based on the wrong assumptions. Thankfully, Steve did here.
  • Generic optimizations are nice when they improve readability too.
  • But nothing beats knowing the domain and applying specific optimizations for performance and readability. This has been true for https://twitter.com/_chenglou/status/1570895293784391681 as well, where the domain-specific knowledge of sin and cos in getOutline blew all the other generic optimizations out of the water. The stronger your codebases’ assumptions, the better. As an aside, imo folks' tendency of using generic abstract architectures, either by naively "future-proofing" code, or excessively dragging dependencies (by definition generic), removes critical opportunities for perf and readability improvement, causes extra bugs, which in turns make true abstractions harder to find.

Btw, these changes look simple (that’s the goal) but could have gone out of control if we weren’t careful. None of this acrobatics would have been necessary if DOM was fast and/or took a data structure of points instead of asking us to serialize them into a string, only to be deserialize again anyway.

Forked On 18 Sep 2022 at 12:08:49

Chenglou

These 3 changes are independent of each other, with the last path string change being the biggest improvement; it also doesn't break APIs. I suggest applying the last 2 changes onto the existing data format instead.

The SoA change does break APIs, and I wouldn't do it unless it's also good for ergonomics. That'd require looking into third-party usages of perfect-freehand and see whether folks' data are closer to number[][], {x: number, y: number}[] or {xs: number[], ys: number[]}.

Commented On 18 Sep 2022 at 12:08:49

Chenglou

Double hot path perf, halve payload size, maintain readability

Created On 18 Sep 2022 at 09:17:50
Create Branch

Chenglou

Draw perfect pressure-sensitive freehand lines.

On 18 Sep 2022 at 09:12:48

Chenglou

Double hot path perf, halve payload size, maintain readability

Created On 18 Sep 2022 at 09:12:48

Chenglou

Draw perfect pressure-sensitive freehand lines.

Forked On 18 Sep 2022 at 09:12:46

Chenglou

Rainbow theme

Pushed On 09 Sep 2022 at 10:28:04

Chenglou

More art

Pushed On 08 Sep 2022 at 10:08:53