Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does FileManager.enumerator use an absurd amount of memory?

Does anybody have an idea why does the following code use an absurd 4.75 GB of memory?

Is there any better way to loop for all files in the filesystem? (I'm trying to find the largest files on the drive)

let filemanager:FileManager = FileManager()
let root = "/"
let files = filemanager.enumerator(atPath: root)
while let element = files?.nextObject() {
    // do nothing
}

Note: there are 400k files on my filesystem (nothing special). The code is sequential, so in theory it shouldn't even depend on the number of files.

like image 220
adamsfamily Avatar asked Sep 23 '17 18:09

adamsfamily


1 Answers

I paused it with the memory graph, and it shows an absurd number of NSConcreteData instances, allocated from fileSystemRepresentation(withPath:).

That documentation page notes that it's a pointer that's only good for the current autorelease pool. That suggests a solution: we just have to get the nextObject() call into its own autorelease pool.

This program, for example, stays steady at 11.0 MB:

var done = false
while !done {
    autoreleasepool {
        let element = files?.nextObject()
        done = (element == nil)

        // do nothing
    }
}

It looks like Swift's while let syntax puts the binding in the autorelease pool of the while loop's context, so each element remains retained until the entire loop completes.

By forcing the enumerator's object into our own autorelease pool, we can make sure it's not retained during subsequent iterations.

EDIT: Since autoreleasepool is just a func, I could have written this much more concisely:

while let element = autoreleasepool(invoking: { files?.nextObject() }) {
    // do nothing
}
like image 66
Ssswift Avatar answered Nov 16 '22 16:11

Ssswift