5

The current iOS app that we have has to download more than a million objects from the server and we keep it in an array for certain purposes. When the user is done with this functionality and the app takes a while to go back to the previous screen (~15 sec) Its because the 1 million objects are being released. We can see from Instruments that the count going from 1 million to 0 during this time (15 sec). Is there any way to speed up the deallocation of these 1 mn objects that are there in the array?

Vinny
  • 227
  • 2
  • 10
  • I hope each of these objects is only a couple of bytes. Have you tested this on a real device yet? – rmaddy Apr 09 '16 at 02:17
  • Yes, its been tested on a device. The array of objects by itself takes around 15 MB of memory – Vinny Apr 09 '16 at 02:44
  • You have not provided any code or information how this is being executed so its hard to track what is going on. You have the tag Automatic-Ref-Counting so I am assuming you are using arc. Are you executing code when you go back to the previous screen? Do you have code in the dealloc method? – Jaybit Apr 12 '16 at 18:06
  • How many times are you allocating? Freeing 15MB of memory should not take 15 seconds unless you've allocated once per object. Can you pre-allocate the space for these objects? – Alex Reinking Apr 14 '16 at 20:15
  • Have you tried with `@autorelease {}` pool. iOS automatically manage the deallocation of objects. Once your VC is no longer in use, that will automatically remove references of created objects. – Vatsal K Apr 18 '16 at 07:03
  • What are these million objects and why do you need them all on a mobile device? – Wain Apr 18 '16 at 07:04
  • @Wain: Ours is a B2B app and was meant for smaller deployments. As a result of attempts to scale the solution for a cross country customer, we discovered that more than a million objects were being downloaded and hence causing the slowness – Vinny Apr 19 '16 at 09:22
  • but you're not looking for a solution to prevent all the downloads, or even to delete progressively during usage, only to be able to delete them faster at the end? – Wain Apr 19 '16 at 09:35
  • Preventing the downloads is not something that we can afford to do at this point. It affects the workflow. As part of a temporary solution, we have made the decision to allow it to be slow (which is annoying for the user of course) but as you rightly mentioned its one of the options for a long term solution, in addition to evaluating other storage options – Vinny Apr 19 '16 at 09:44

3 Answers3

3

Instead of trying to dealloc these objects faster, I would recommend that you dealloc them slower.

  1. Keep all these objects somewhere else. Create some singleton [ObjectsManager sharedInstance], which will be responsible for downloading and holding all these objects in some NSMutableArray.
  2. Use these objects whenever you want in other VCs.
  3. When you are finished, tell your [[ObjectsManager sharedInstance] removeAllDownloadedObjects], which would slowly remove all of them from your NSMutableArray.
  4. Instead of writing [myArray removeAllObjects], do it part by part -- delete 20000 objects every second, using NSOperation or GCD.
OlDor
  • 1,460
  • 12
  • 18
2

I freed 1 milion instances of ~600 MB (picked expensive UIView) in somewhere about one and half of sec on iPhone 6s. When tried with modest class (~50 MB), it took ~0.2 sec. Still, if you can hold your data in struct, you are going close to zero. I believe, you are losing your time somewhere else...

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    class C: UIView {
        static var c = 0
        deinit { C.c -= 1 }
        init() { C.c += 1; super.init(frame: CGRect.zero) }
        required init?(coder aDecoder: NSCoder) { fatalError() }
    }

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        var a: [C]? = []
        for i in 0..<1_000_000 {
            a!.insert(C(), atIndex: i)
        }

        print(C.c) // We have 1_000_000 allocations.

        let t1 = CFAbsoluteTimeGetCurrent()
        a = nil
        let t2 = CFAbsoluteTimeGetCurrent()

        print(t2 - t1) // 1 milion instances of ~600 MB freed in ~1.5 seconds.
        print(C.c) // ARC works!

        return true
    }
}
Stanislav Smida
  • 1,565
  • 17
  • 23
  • The current approach has the array set to nil in the viewwilldisappear method. With instruments running, it can be observed that setting nil does not force the deallocation immediately. Though control goes to the next line, the deallocation continues to happen parallely and the size of the array becomes smaller and smaller. – Vinny Apr 19 '16 at 09:32
  • Are you sure `viewwilldisappear` is a good place for you to perform such an expensive operation? Think that you may want show / present it again and your data are lost at that time. Unless you have backed it with well design, I would let it go implicitly at `deinit`. – Stanislav Smida Apr 19 '16 at 10:32
  • The reason why deallocation is not immediate may be that there are other owners of objects (another array, cells, ...). Also distinguish between deinitialization and deallocation. Once you forced deinitialization (ARC found reference count of object zero), you are good and let system do dealloc (this won't be immediate). And... here I strongly recommend considering `struct`s. – Stanislav Smida Apr 19 '16 at 10:40
  • Well, setting it in the ViewWillDisappear was the earliest place where the deallocation could begin. There's no issue with that as the VC gets popped out and the underlying VC is shown and during this time, the deallocation continue to happen – Vinny Apr 19 '16 at 10:42
  • You don't want to do it early, but smoothly. If you have no reason to do it in `viewwilldisappear`, don't do it. Also be cautious when considering @OlDor`s solution. You may get into racing problems when you need to insert new objects before you finish "slow removal". Designing such a pattern is a bit more tricky. "delete 20000 objects every second" is IMO pretty deficient design. – Stanislav Smida Apr 19 '16 at 10:50
1

The problem is heap fragmentation. Deallocating a contiguous block of memory in language like ie ObjC/C/C++ etc is fast. Deallocating fragmented memory in comparison is slow. It's hard to test because the allocation and deallocation pattern quite often effects the amount of fragmentation you see. An example might help.

A lot of allocators use mixed sized bins of linked lists to allocate from. When you allocate a million of these in a loop you're likely to have a a lot of contiguous pieces of memory which when deallocated in a loop takes advantage of the caches etc ie it looks fast. When you test it in production it's not so fast.

The only way to avoid it is to avoid Heap fragmentation and that means..

  1. Allocate blocks that hold N objects and deallocate each block in one go.
  2. Ideally allocate one contiguous memory region for the entire set of N objects and deallocate it in one call.
Harry
  • 11,298
  • 1
  • 29
  • 43