Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-5361] Table View Scroll Extreme Garbage Collection Starvation

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionFixed
Resolution Date2011-11-03T15:09:33.000+0000
Affected Version/sRelease 1.8.0
Fix Version/sSprint 2011-40, Release 1.8.0
ComponentsiOS
Labelsbranch-5062
ReporterDawson Toth
AssigneeStephen Tramer
Created2011-09-26T10:13:40.000+0000
Updated2014-06-19T12:46:19.000+0000

Description

Problem

When scrolling a table view, memory rapidly diminishes until you stop scrolling. Once you stop scrolling AND LIFT UP YOUR FINGER the memory releases.

How To Reproduce

* Drop the following in an app.js * Scroll down and watch the memory fall * Touch and hold down with your finger to interrupt the table's deceleration and freeze it in place (figuratively speaking) * Watch the memory stay where it is * Let go with your finger * Watch the memory go back up
var PLEASE_BLEED_MEMORY = true;
var win = Ti.UI.createWindow();

var rows = [];
for (var i = 0; i < 2000; i++) {
    var row = Ti.UI.createTableViewRow();
    row.add(row.label = Ti.UI.createLabel());
    if (PLEASE_BLEED_MEMORY) {
        row.label.text = 'blood and guts MU HA HA HA';
    }
    rows.push(row);    
}
win.add(Ti.UI.createTableView({ data: rows, bottom: 30 }));

win.add(win.freeMemory = Ti.UI.createLabel({
    text: 'Free Memory: Loading...', textAlign: 'center',
    bottom: 0, height: 30, color: '#fff', backgroundColor: '#000'
}));
setInterval(function() {
    win.freeMemory.text = 'Free Memory: ' + Ti.Platform.availableMemory;
}, 500);

win.open();

Comments

  1. Stephen Tramer 2011-09-26

    Issue is likely: TiUITableviewRowProxy : DeepScanForProxyOfViewContainingPoint(). The issue here is that as part of the fast enumeration, [targetView subviews] is (possibly) called multiple times in a row, on the main thread, which cannot autorelease as long as presses are ongoing. We need some judicious use of autorelease pools in our hit detection (and to make sure we don't call this method more than once per scan anyway).
  2. Dawson Toth 2011-09-26

    I have isolated this down to TiViewProxy.m's line 1025 (probably 1025...): [[view retain] performSelector:@selector(autorelease) withObject:nil afterDelay:0.5]; If I comment this out, the memory doesn't rapidly drop.
  3. Dawson Toth 2011-09-26

    Introduced by [TIMOB-5326].
  4. Blain Hamon 2011-09-26

    D'oh. I should have checked comments earlier. I've arrived to the same agreement, and it's well explained in the performSelector:...:afterDelay:'s docs- "The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode." While the tableview is under finger, the run loop mode is tracking, not default. Thus, it all makes sense. The solution, however, hrm...
  5. Dawson Toth 2011-09-27

    While the fix does free up memory earlier, memory still fluctuates way more than it should. Let me quantify that: 1. Use the app.js in the original report 2. Memory began at 45mb 3. Scroll to the bottom; notice the scrolling gets choppier and choppier 4. Memory dropped from 45mb to 30mb 5. Memory quickly jumps up to where it started Now comment out the delayed view autorelease, and perform the same test. 1. Same app.js, only difference is no delayed view autorelease 2. Memory began at 45mb 3. Scroll to the bottom; scrolling does get choppy, but it does not "skip" as much as it did previously 4. Memory dropped from 45mb to 42mb 5. Memory quickly jumps up to where it started What I want to communicate is that the fix that was introduced for one, small scenario in [TIMOB-5326] has a big performance impact on the table view. The app that I am working on is very scroll heavy, and we need as much help as we can get. Our memory usage is already sensitive, and 15 mb swings aren't going to help us stay stable.
  6. Dawson Toth 2011-10-04

    Based on experimentation in the customer's app, one way around this could be to add a method to TiViewProxy that doesn't delay the autorelease of the view. In situations where it is safe to do so and we need the performance, we could call this method instead: Such as in TiUITableViewRowProxy.m, line 636:
       [child detachViewImmediately];
       
    The method would be the same as the existing detachView, but without the delayed autorelease:
       -(void)detachViewImmediately
       {
       	[destroyLock lock];
       	if (view!=nil)
       	{
       		[self viewWillDetach];
       		view.proxy = nil;
       		if (self.modelDelegate!=nil && [self.modelDelegate respondsToSelector:@selector(detachProxy)])
       		{
       			[self.modelDelegate detachProxy];
       		}
       		self.modelDelegate = nil;
       		[view removeFromSuperview];
       		RELEASE_TO_NIL(view);
       		[self viewDidDetach];
       	}
           
           pthread_rwlock_rdlock(&childrenLock);
           [[self children] makeObjectsPerformSelector:@selector(detachView)];
           pthread_rwlock_unlock(&childrenLock);
       	[destroyLock unlock];
       }
       
    Declare that in the TiViewProxy.h, and we should be good to go. Of course, y'all know the implications of this change way better than I do. I'm only basing this on behavior from one app.
  7. Stephen Tramer 2011-10-05

    This issue was introduced by the 5326 fix, and so the associated bug should be re-tested and resolved in conjunction with this one.
  8. Alan Vaghti 2011-10-06

    Closing. Fix verified on iPod running 4.3.3 with SDK 1.8.0.v20111006001414. Free memory stays stable throughout scrolling.
  9. Alan Vaghti 2011-11-02

    Reopening. Issue is reproducible in build 1.8.0.v20111101161253 with an iPod 4.3.3. Free memory starts at around 70, but as I scrolled down it went to less then 2.
  10. Reggie Seagraves 2011-11-03

    This issue was resolved for a custom branch only and the test case does not apply to the 1.8 release. Should be reclosed as resolved.

JSON Source