12

I'm trying to build a smooth 60fps animation browser javascript loop. I've noticed that the garbage collector kicks in and adds variable non-zero time to animation frames. I started by tracking down allocations in my code and then isolated the loop its self. I was using requestAnimationFrame and discovered that on a supposedly 'empty' loop It still causes allocations each iteration and triggers the garbage collector. Frustratingly this seems to happen in other looping mechanisms setInterval and setTimeout as well.

Below I've put together some jsfiddles and screenshots demonstrating the sample 'empty loops'. All the samples are from ~5 second timelines.

At this point, I'm looking for the best solution to minimize garbage collection. From the samples below it looks like requestAnimationFrame is the worst option in this regard.

requestAnimationFrame

https://jsfiddle.net/kevzettler/e8stfjx9/

var frame = function(){
    window.requestAnimationFrame(frame);
};

window.requestAnimationFrame(frame);

enter image description here

setInterval

https://jsfiddle.net/kevzettler/p5LbL1am/

var frame = function(){
   //literally nothing
};

window.setInterval(frame, 0);

enter image description here

setTimeout

https://jsfiddle.net/kevzettler/9gcs6gqp/

var frame = function(){
    window.setTimeout(frame, 0);
}

window.setTimeout(frame, 0);

enter image description here

kevzettler
  • 4,783
  • 15
  • 58
  • 103

2 Answers2

2

I'm not actually certain, but I seem to remember that web workers have their own garbage collectors, and so the GC hit wouldn't affect FPS in the main thread (though it would still affect updates' ability to be sent to the main thread)

John Haugeland
  • 9,230
  • 3
  • 37
  • 40
  • 1
    If the main thread is simply calling a `render` method and nothing else. It's going to encounter the allocation issues provided in the examples above. – kevzettler Mar 10 '16 at 17:45
  • wait are you saying the `rAF` *alone* is causing the allocation thrash? – John Haugeland Mar 10 '16 at 17:52
  • Yes that's what the examples show. Its a known chrome 'bug' https://bugs.chromium.org/p/chromium/issues/detail?id=120186#c20 `rAF` allocates a `Number` object on each call – kevzettler Mar 10 '16 at 18:02
  • 1
    that's really gross. i just confirmed independently. looks like chrome's losing about 550k a minute in leaks. if you let it run longer, those sawtooths are going upwards. i wonder if it's just building an enormous call stack. at this rate, rAF alone will consume the average phone web browser's memory allocation in around 15 minutes :( http://imgur.com/atNu0cF – John Haugeland Mar 10 '16 at 23:04
  • 1
    this seems to be an adequate, albeit hella ugly, way to handle it: http://impactjs.com/forums/impact-engine/misuse-of-requestanimationframe/page/1 – John Haugeland Mar 10 '16 at 23:23
  • 2
    so, no. i tried a bunch of the approaches that page suggested, and they all had the same growth problem. then i tried postmessage, and it's not cycle bound, and it *still* had the same growth problem. at the same speed. even though it was doing 100x the "frames" per second. so, i tried ending it hard at a cycle count, and it still kept growing. so i tried an empty js - and that keeps growing too. i think the debugger's just measuring its own trace data. the gcs are real, but the runaway growth is not. :| – John Haugeland Mar 11 '16 at 00:19
1

I'm no expert, but from what I've been reading. I too came across the same bug report that you mentioned in your comments:

As suggested, allocating the Number object on each call, would tally with garbage being collected.

https://bugs.chromium.org/p/chromium/issues/detail?id=120186#c20

It also suggested that simply having the debugger open recording the stack traces could cause problems. I wonder if this is the same case when doing remote debugging?

This answer suggests flip flopping between animation frames, to reduce the garbage collection: https://stackoverflow.com/a/23129638/141022

Judging by the depth of question you have asked, I'm sure what I'm about to say is obvious to you, but it might be interest to refocus towards your goal in general (albeit perhaps doesn't help with your interesting observation of Chrome).

One thing we need to remember is that we're not aiming to avoid garbage collection completely as it's so fundamental to JS. Instead we are looking to reduce it as much as possible to fit into rendering our frames with 16ms (to get 60fps).

One of VelocityJs's approaches is to have a single global "tick" that handles all animation...

Timers are created when setInterval(), setTimeout(), and requestAnimationFrame() are used. There are two performance issues with timer creation: 1) too many timers firing at once reduces frame rates due to the browser’s overhead of maintaining them, and 2) improperly marking the time at which your animation begins results in dropped frames.

Velocity’s solution to the first problem is maintaining a single global tick loop that cycles through all active Velocity animations at once. Individual timers are not created for each Velocity animation. In short, Velocity prioritizes scheduling over interruption.

http://www.sitepoint.com/incredibly-fast-ui-animation-using-velocity-js/

This along with general practices on reducing garbage collection such as creating a recycling cache to reuse objects or even rewriting methods such as array slice to avoid garbage.

https://www.scirra.com/blog/76/how-to-write-low-garbage-real-time-javascript

Community
  • 1
  • 1
Alex KeySmith
  • 16,657
  • 11
  • 74
  • 152
  • none of this actually addresses the question in any way – John Haugeland Mar 14 '16 at 06:40
  • 1
    You can't reduce garbage collection that is caused by rAF, because it's being caused by rAF. Centralizing the tick won't change the problem the tick causes. Velocity doesn't have a solution; it has the problem. – John Haugeland Mar 14 '16 at 06:42
  • 1
    Thanks for the feedback John, I suppose what I was trying to illustrate was yes rAF is causing garbage collection. But as the likes of velocity can achieve very high framerates with the rAF problem, instead use other ways to compensate for it. Flip flopping for example. Or as eluded by the stacktrace issue, it's probable that simply debugging the issue is actually causing more garbage collection? – Alex KeySmith Mar 14 '16 at 07:32
  • 2
    This question is about eliminating the timing defects that rAF itself creates within the update loop, most likely by removing rAF. If you implement the flip flopping claim, you will find that it makes no difference at all: http://gist.github.com/StoneCypher/2cfb01cca9d89d1a28af You can see the problem visibly by setting a div's margin smoothly, allowing you to witness the problem without debugging at all: https://gist.github.com/StoneCypher/1f30dbe11bb836e9c6db – John Haugeland Mar 14 '16 at 21:45
  • 3
    Fundamentally, rAF appears to be unacceptably flawed for animation in current Chrome. A replacement seems necessary, and the two common sense choices (`setTimeout` and `setInterval`) seem to carry the same problem. I've independently tried some other stuff, too, like throttled `postMessage`; same problem. CSS `transitions` and `animations` appear to be smooth, but CSS tr/an are extremely limiting and difficult to synchronize, and Chrome's support is only partial. – John Haugeland Mar 14 '16 at 21:48
  • I think you're right @JohnHaugeland (+1 your comments) all options are pretty flawed, but I suppose it depends if with enough massaging it possible to get an option to be acceptable fps for the use case. – Alex KeySmith Mar 21 '16 at 08:47
  • 1
    @JohnHaugeland Have you made any headway with this issue? I am facing the same problem and would like to hear from others before embarking on my own research. – Jimmy Breck-McKye May 17 '17 at 09:53
  • 1
    I haven't tested this in more than a year. I'd start by verifying that this is even still a real problem. But to give a straight answer, no, I did not find a fix for this; I worked around it by making animations less jitter sensitive by keeping them on alternate code paths, instead. – John Haugeland May 17 '17 at 17:08
  • that is, if there are multiple different things lagging at different times, the total screen keeps moving, and you're less likely to notice that individual pieces didn't. it's super ultra gross, but it works – John Haugeland May 17 '17 at 17:10