I'm coding a generic Swift application (not for iOS, it will later run on raspbian) and i noticed a constant increase of memory. I checked for memory leaks also with the inspector, and there are none.
To dig deeper, I created a blank application for macOS, and I just wrote those lines of code, which are only for testing:
var array = [Decimal]()
while(true) {
array = [Decimal]()
for i in 0..<10000
{
array.append(Decimal(string: i.description)!)
}
sleep(1)
}
As I know, at beginning of every cycle of the while loop the entire array that was filled in the previous cycle should be deleted from memory. But seems that this is not happening, with those lines of code the process memory rises indefinitely.
I also tried the same code on an iOS project putting it on the application function (the one that is called at the beginning in the app delegate) and I noticed that in this case, the memory remains constant and do not rises up.
Am I missing something on the non iOS project?
The Decimal(string:) is creating autorelease objects. Use an autoreleasepool to drain the pool periodically:
var array = [Decimal]()
while true {
autoreleasepool {
for i in 0..<10_000 {
array.append(Decimal(string: i.description)!)
}
sleep(1)
array = []
}
}
Normally the autorelease pool is drained when you yield back to the runloop. But in this case, this while loop never yields back to the OS, and therefore the pool is not getting drained. The use of your own autoreleasepool, like above, solves that problem.
FWIW, Apple has been slowly excising the use of autorelease objects in the Foundation/Cocoa classes. (We used to experience this problem with far more Foundation objects/APIs than we do now.) Clearly Decimal(string:) still is creating autorelease objects behind the scenes. In most practical cases, this isn't a problem, but in your example, you will need to introduce your own autoreleasepool to mitigate this behavior in this while loop.
Related
I try to to read a file on macOS with Swift using class FileHandle and function
#available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *)
public func read(upToCount count: Int) throws -> Data?
This works fine in principle. However, I'm faced with a memory leak. The data allocated in the returned Data buffer is not freed. Is this possibly a bug in the underlying Swift wrapper?
Here is my test function, which reads a file block by block (and otherwise does nothing with the data). (Yes, I know, the function is stupid and has the sole purpose to prove the memory leak issue.)
func readFullFile(filePath: String) throws {
let blockSize = 32 * 1024
guard let file = FileHandle(forReadingAtPath: filePath) else {
fatalError("Failed to open file")
}
while (true) {
guard let data = try file.read(upToCount: blockSize) else {
break
}
}
}
If I call the function in a loop, and watch the program's memory consumption, I can see that the memory goes up in each loop step by the size of the read file and is never released.
Can anybody confirm this behavior or knows how to fix it?
My environment:
macOS 11.3.1 Big Sur
XCode 12.5
Best,
Michael
The underlying Objective C implementation uses autorelease. Objects are kept alive by the thread's auto-release pool. Typically this pool is drained on every iteration of the run loop.
But in your context, since you have a tight loop that runs multiple times within a single iteration of the run loop, you're accumulating a bunch of objects that are kept around temporarily.
Rest assured, they will eventually be reallocated when the run-loop iteration completes, assuming your app doesn't crash from EOM before that.
If the build-up of objects is an issue, you can manually drain the autorelease pool by placing the allocations inside an autorelease pool block.
for _ in something {
autoreleasepool {
// Do your work here
}
}
Beware: if your work doesn't allocate much memory, this might actually make performance worse. It'll slow down your loop without much memory benefit.
Docs: ObjectiveC.autoreleasepool(invoking:)
To access this, you can import ObjectiveC, although more commonly you'll have it transitively imported when you import Foundation, AppKit, Cocoa, etc.
To cut a long story short, here is a code snippet that would easily eat as much memory as it can until it's stopped. But why? When I wrap the scope inside while in autoreleasepool, not a single byte is leaked. However it affects only current scope; if there are leaky function calls, leakage will continue. So the answer is to just wrap leak-prone operations in autoreleasepool? It looks kinda ridiculous and non-swifty.
import Foundation
while true {
let _ = "Foo Bar".data(using: .ascii)
usleep(100)
}
This is not unexpected. Until your while returns control to the run loop, the top-level autorelease pool will not be drained. Objects put into it will continue to accumulate.
I'm a little surprised that ARC doesn't destroy the Data instances immediately, however, since assigning them to the "cut" means that they are in effect never in scope. There's no name by which you can ever refer to them, and no reason to keep them alive.
I'm working on a Producer-consumer problem with an unbounded consumer. The producer can put as many tasks into the processing queue as it wants. When the queue is empty the consumer will block the thread.
while true {
do {
guard let job = try self.queue.dequeue() else { return }
job.perform()
} catch {
print(error)
}
}
Normally I would put everything in the loop in an autorelease pool, however, it's not available on Linux. It seems as though ARC is never releasing the objects in the loop. How should I go about controlling memory usage?
I don't believe memory spikes due to autorelease pools should be a thing on Linux. It's possible that something else could be holding onto a reference to one of your objects, though. Try setting a breakpoint in the middle of the loop, then click on "Debug Memory Graph" in the debugger to see what objects have references to the objects that are piling up. This can help determine the cause of objects that stick around longer than they ought to.
I am wondering if there's any benefit of using #autoreleasepool on an ARC code inside a method.
I mean this. Suppose I have a memory intensive method that is called several times in sequence. Something like
// this is my code
for (id oneObject in objects {
[self letsUseMemory];
}
and then
- (void) letsUseMemory {
// heavy use of memory here
}
and I do this
- (void) letsUseMemory {
#autoreleasepool {
// heavy use of memory here
}
}
Is there any benefit? I mean, the method variables will be deallocated anyway when the method finishes, so adding an autoreleasepool there, in theory, will do any benefit, right?
Or will autoreleasepool inside that method speed the deallocation?
thanks.
Is there any benefit? I mean, the method variables will be deallocated anyway when the method finishes, so adding an autoreleasepool there, in theory, will do any benefit, right?
It depends. Any autoreleased temporary objects will not be deallocated until the pool drains, regardless of whether you're using ARC. I.e.:
NSString* foo = [NSString stringWithFormat:#"Bar: %#", baz];
Without an enclosing #autoreleasepool, that object instance may hang around until you return to the run-loop. If that line of code exists within a loop, you may be accumulating a large number of these temporary objects.
The general rule of thumb is that if you have a potentially large loop that may create autoreleased objects, wrap the inside of the loop in with an #autoreleasepool.
It's less common and perhaps somewhat meaningless to wrap a single method in an #autoreleasepool because it would usually only have meaningful effect if the method was called many times in a loop. Putting the #autorelease pool in the loop makes the intent more clear.
There are a number of "it depends" things going on there, I believe.
Starting with the obvious, if there are no autoreleased objects, it doesn't matter. If your code goes right back to the run loop after your enumeration finishes, it doesn't matter.
That leaves the case where the method containing the enumeration is used to do a bunch of initialization and then continues on with more processing. For that one, you could benefit by getting rid of temporary objects that were marked for later release.
I ran a few stress tests on my Iphone app. The results are below. I am wondering if I should be concerned and, if so, what I might do about it.
I set up a timer to fire once a second. Whenever the timer fired, the app requested some XML data from the server. When the data arrived, the app then parsed the data and redisplayed the affected table view.On several trials, the app averaged about 500 times through the loop before crashing.
I then removed the parsing and redisplay steps from the above loop. Now it could go about 800 times.
I set up a loop to repeatedly redisplay the table view, without downloading anything. As soon as one redisplay was completed, the next one began. After 2601 loops, the app crashed.
All of the above numbers are larger than what a user is likely to do.
Also, my app never lasts long at all when I try to run it on the device under instruments. So I can't get useful data that way. (But without instruments it lasts quite a while, as detailed above.)
I would say you need to be very concerned. The first rule of programming is that the user will never do what you expect.
Things to consider:
Accessor methods. Use them. Set up
properties for all attributes and
always access them with the
appropriate getter/setter methods:
.
object.property = some_other_object; -OR-
[object setProperty:some_other_object];
and
object = some_other_object.some_property;
object = [some_other_object some_property];
Resist the temptation to do things like:
property = some_other_object;
[property retain];
Do you get output from ObjectAlloc?
There are 4 tools from memory leaks,
performance and object allocations.
Are none of them loading?
What do you get when the app crashes?
EXEC_BAD_ACCESS or some other error?
Balanced retain (either alloc or
copy) and release. It is a good idea
to keep every alloc/copy balanced
with a release/autorelease in the
same method. If you use your
accessors ALL OF THE TIME, the need
for doing manual releases is seldom.
Autorelease will often hide a real
problem. It is possible Autorelease
can mask some tricky allocation
issues. Double check your use of
autorelease.
EDITED (Added based on your fault code)
Based on your above answer of "Program received signal: 0". This indicates that you have run out of memory. I would start by looking for instances that your code does something like:
myObject = [[MyClass alloc] init];
[someMutableArray addObject:myObject];
and you do not have the "release" when you put the new object into the array. If this array then gets released, the object, myObject, will become an orphan but hang around in memory anyway. The easy way to do this is to grep for all of your "alloc"/"copy" messages. Except under exceedingly rare conditions, there should be a paired "release""/autorelease" in the same function. More often than not, the above should be:
myObject = [[[MyClass alloc] init] autorelease];
[someMutableArray addObject:myObject];