Skip to content
This repository has been archived by the owner on Nov 19, 2019. It is now read-only.

Ideas to improve perf on modern browsers #14

Closed
deviousasti opened this issue Jul 7, 2018 · 37 comments
Closed

Ideas to improve perf on modern browsers #14

deviousasti opened this issue Jul 7, 2018 · 37 comments

Comments

@deviousasti
Copy link

I noticed that a lot of measurement and subsequent layout recalculation is taking place in the animation frame. A lot of measured values can be cached, and perf can be improved by batching reads and writes.

For eg., the line 5563:
label.style.width = width + 'px'; // set width to prevent overflow

takes a whole 6ms (on a Ryzen 7). I'm combining this with a canvas, and its framerates end up being killed every time the timeline is moved.

Just started an issue to list ideas here.

@yotamberk
Copy link
Owner

Awesome! Thanks for starting this thread

@deviousasti
Copy link
Author

deviousasti commented Jul 8, 2018

Suggestions:

  1. Combine style.left = x and style.top = y into style.transform = translate(x, y) (this change singlehandedly makes rendering a lot smoother)
  2. Convert element.innerHTML to element.innerText
  3. Use native methods whenever possible:
  vis.util.addClassName = function (elem, classNames) {
        elem.classList.add(...classNames.split(' '));
    }

    vis.util.removeClassName = function (elem, classNames) {
        elem.classList.remove(...classNames.split(' '));
    }

    vis.util.forEach = function forEach(object, callback) {
        if (Array.isArray(object)) {
            // array
            object.forEach(callback);
        } else {
            // object
            Object.getOwnPropertyNames(object).forEach(i => callback(object[i], i, object));
        }
    }

I'll edit this list to add more. 😃

@yotamberk
Copy link
Owner

Hi @deviousasti ,
Let me comment about the different points:

  1. transform is indeed MUCH more efficient than left and top since it uses repaint without layout/redraw. BUT since all the code is based left and top, this will be extremely difficult to change.
    It might be possible to be applied in chunks starting with the most crucial components, such as the different Item types (RangeItem, BackgroundItem etc.) This was an old idea of mine a while back. I might give it a shot the next few weeks.
  2. converting this will have consequences since most of the uses of element.innerHTML is to allow the user to inject his own html elements. I will review these points carefully to see where this can be safely swapped to element.innerText
  3. Can you give more specific locations where you would use these?

Awesome points! Thanks! Keep up with the ideas!

@deviousasti
Copy link
Author

deviousasti commented Jul 8, 2018

Hi @yotamberk,

About #1, I've simply replaced all instances of setting the left property with transform and it works great.
About #3, setting the major, minor labels - css classes and the formats of the labels are user-definable anyway, and can safely be changed to innerText.

I modified the dist file with the above changes and it works fine.
(https://www.dropbox.com/s/dppedw1x47fa48n/timeline.js?dl=0)

  1. Avoid creating new Date() - getting the system date-time is very expensive and the date constructor has terrible perf. Even Date.now is 50% faster. (https://jsperf.com/date-now-vs-new-date)

Sections like these are abuse:

              function currentWeek(date) {
                return date.isSame(new Date(), 'week') ? ' timeline-current-week' : '';
              }

              /**
               *
               * @param {Date} date
               * @returns {String}
               */
              function currentMonth(date) {
                return date.isSame(new Date(), 'month') ? ' timeline-current-month' : '';
              }

              /**
               *
               * @param {Date} date
               * @returns {String}
               */
              function currentYear(date) {
                return date.isSame(new Date(), 'year') ? ' timeline-current-year' : '';
              }

@deviousasti
Copy link
Author

I think most importantly, to hit 60 fps - and it should be very feasible:

  1. Cache offsetWidth, offsetHeight, clientWidth and clientHeight.

  2. Refactor functions like:

     function getAbsoluteLeft(elem) {
       return elem.getBoundingClientRect().left;
     }
     function getAbsoluteTop(elem) {
       return elem.getBoundingClientRect().top;
     }
    

Lines like:
this.popup.setPosition(event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft, event.clientY - util.getAbsoluteTop(container) + container.offsetTop); are terrible offenders, calculating bounds twice.

@Sam-Sutherland
Copy link

This is great! The proposed changes have pretty good performance increases. There are a few issues with label stacking at the top then at the bottom if you set orientation: {axis: "top"} (Shown Below) but other than that it looks good.

image

I've had a brief play around with getting the RangeItem to use transform as well but weird stuff was happening to the backgrounds.

Looking forward to seeing this progress!

@deviousasti
Copy link
Author

Guys, need any help implementing this?

@yotamberk
Copy link
Owner

PRs are always welcomed! I don;t have the time to implement this right now.

@jarnovanrhijn
Copy link
Contributor

Guys, need any help implementing this?

@deviousasti, have you ever gotten to writing a pull request? otherwise i'll look into it myself :) and try to make a pull request.

@deviousasti
Copy link
Author

@jarnovanrhijn I wasn't able to build it before - I tested out those on the built version.
I'll try again.

@deviousasti
Copy link
Author

@jarnovanrhijn I just added a few changes to
https://github.com/deviousasti/timeline-plus/commits/master
Help me out?

@yotamberk
In BoxItem there's repositionX and repositionY - these should ideally be combined into one translate move - this is a breaking change. Thoughts?

@jarnovanrhijn
Copy link
Contributor

@jarnovanrhijn I just added a few changes to
https://github.com/deviousasti/timeline-plus/commits/master
Help me out?

@yotamberk
In BoxItem there's repositionX and repositionY - these should ideally be combined into one translate move - this is a breaking change. Thoughts?

@yotamberk,
I'm not sure if the performance will increase much if combined as this would only update the correct coords right? isn't translateX and translateY an option to avoid a major change for now? only caveat here is that we need to know the current translateX and translateY as they are used on the same property.

@deviousasti
Copy link
Author

It will be sort of hacky to have it separate. Can try.

@jarnovanrhijn
Copy link
Contributor

It will be sort of hacky to have it separate. Can try.

Let me know if i can do anything to help!!

@taavi-halkola
Copy link

taavi-halkola commented May 14, 2019

Most likely the easiest choice would be to check if both the top and left values are present when updating the translate value.

In repositionx check if this.top is already set. If not then only do translatex. If it is then do translate. The same goes to repositiony but other way around.

Also another big problem in redraw performance is the forced reflows from calculating bounds, offsetheights, offsetwidths or changing inline styles to early in the dom tree nodes.. anyone checked into those? One example is the cursor change when the timeline scrolling is started. I think that style change alone causes a reflow to all children. Mostly the problems are from getting offsetwidths, offsetheights, offsetlefts and offsettops for groups and items.

@deviousasti
Copy link
Author

deviousasti commented May 14, 2019 via email

@jarnovanrhijn
Copy link
Contributor

Made a pull request which fixes the multiple calculatings.

Functions like getAbsoluteTop and getAbsoluteLeft are changed to a single util function and cached when used.

Will look into the layout trashing as well!! Great to seee this alive again

@deviousasti
Copy link
Author

deviousasti commented May 14, 2019

Replaced the vis.util methods with native ones -
If anyone wants this to run on really old browsers they can relegate the compat to polyfills.

I merged @jarnovanrhijn 's changes as well. Good find.
I will try to tackle BoxItem next.

@jarnovanrhijn
Copy link
Contributor

tried to replace new Date() with Date.now() and tried to cache some heights and widths.. pretty sure i'm not there yet so i'll finish the rest tomorrow!!

@jarnovanrhijn
Copy link
Contributor

@deviousasti & @taavi-halkola
need some help with caching the heights and widths. i think i have the quick wins covered but i think there are plenty improvements to be made regarding layout trashing.

@deviousasti
Copy link
Author

@jarnovanrhijn I rewrote the positioning code for BoxItem. Please have a look. (We now have transforms!)
Merged your code in.
We somehow need to batch all reads and writes to the dom- we should move in that direction.

@jarnovanrhijn
Copy link
Contributor

@jarnovanrhijn I rewrote the positioning code for BoxItem. Please have a look. (We now have transforms!)
Merged your code in.
We somehow need to batch all reads and writes to the dom- we should move in that direction.

The transforms alone will improve the performance allot!! Thank you for that 😄

regarding the batch read / write i think fastdom will do the trick.. although i'm not familiar with the API it shouldn't be that hard implementing it!

@deviousasti
Copy link
Author

I'll have a look at removing nodes and GC as well.
image

And the throttle function could use another look.

@deviousasti
Copy link
Author

image
All those small blue blocks at the bottom are HTML parses - I think we should limit it to text for tick labels. Most effects custom can be achieved with CSS if need be.

@jarnovanrhijn
Copy link
Contributor

jarnovanrhijn commented May 20, 2019

I've replaced left (Core.js) with translate. this will be used when verticalScroll and maxHeight are present

@deviousasti
Copy link
Author

deviousasti commented May 20, 2019 via email

@jarnovanrhijn
Copy link
Contributor

jarnovanrhijn commented May 22, 2019

@deviousasti & @yotamberk

I've added support for RTL transforms. Could you review the changes as i'm not 100% confident of the changes.

Also, locally i've tried to use fastdom for some parts of the timeline and it indeed increases performance allot. although i think everything is too tightly coupled to do it properly.

Any ideas on this topic?

@deviousasti
Copy link
Author

deviousasti commented May 22, 2019

@jarnovanrhijn Nice. RangeItem has repositionY which needs to be combined to one single translate.

The rtl support, I'm not sure. The reason why translateX works is because it's already referenced to a left-top origin, and translate moves it relative to the absolute position of that element (which by default is 0, 0).
For rtl to be translated, you will need to take the size of the relative container into account.
I made this example to explain the differences:
https://codepen.io/anon/pen/byaMYm

I hope this makes it clearer.

@jarnovanrhijn
Copy link
Contributor

@deviousasti, Yeah i thought so as well. did some research and found https://rtl-css.net/tutorial/quick-tip-rtling-css3-transform-functions-translate-and-translatex which basically says make the X value a negative number. did some tests and it seems to work in some places but i can find myself in your explanation!

Will revert it for now till we figure out a permanent solution!

@deviousasti
Copy link
Author

deviousasti commented May 23, 2019 via email

@jarnovanrhijn
Copy link
Contributor

@deviousasti, Thanks for all the work man!! really happy these improvements are being made!

@jarnovanrhijn
Copy link
Contributor

jarnovanrhijn commented May 24, 2019

@deviousasti @yotamberk,

I had a first attempt in including fastdom in the Timeline and even though i i'm sure i did it incorrectly (hasty) i've seen major improvement in performance! this is just a test so i don't expect it to be production ready but just seeing the performance improve made me happy :D!

i've created a branch in my fork which you can try and see for yourself. just run the stressPerformance.html in there and compare it with the master branch! Without fastdom my browser crashes (5000 items and 400 groups) whilst with fastdom it runs smooth as butter!

@yotamberk
Copy link
Owner

Closed and implemented with the great work of @jarnovanrhijn with #168

@jarnovanrhijn
Copy link
Contributor

I’m sorry for the late reply but I’m on Holiday and don’t have my laptop with me!!

most of the credits go to @deviousasti though, he has made most of the improvements possible 😁👌🏻

Thanks for completing the final steps needed for this merge

@MonasteryJohn
Copy link

@yotamberk very excited about these changes! Any idea when an update will be available in NPM?

@yotamberk
Copy link
Owner

This weekend \ tomorrow

@yotamberk
Copy link
Owner

Version has been updated to v2.4.0

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants