I'm trying to understand Swift's unsafe pointer API for the purpose of manipulating audio samples.
The non-mutable pointer variants (UnsafePointer, UnsafeRawPointer, UnsafeBufferPointer) make sense to me, they are all used to reference previously allocated regions of memory on a read-only basis. There is no type method "allocate" for these variants
The mutable variants (UnsafeMutablePointer, UnsafeMutableRawPointer), however, are documented as actually allocating the underlying memory. Example from the documentation for UnsafeMutablePointer (here):
static func allocate(capacity: Int)
Allocates uninitialized memory for the specified number of instances of type Pointee
However, there is no mention that the UnsafeMutablePointer.allocate(size) can fail so it cannot be actually allocating memory. Conversely, if it does allocate actual memory, how can you tell if it failed?
Any insights would be appreciated.
I decided to test this. I ran this program in CodeRunner:
import Foundation
sleep(10)
While the sleep
function was executing, CodeRunner reported that this was taking 5.6 MB of RAM on my machine, making our baseline.
I then tried this program:
import Foundation
for _ in 0..<1000000 {
let ptr = UnsafeMutablePointer<Float>.allocate(capacity: 1)
}
sleep(10)
Now, CodeRunner reports 5.8 MB of RAM usage. A little more than before, but certainly not the extra 4 MB that this should have taken up.
Finally, I assigned something to the pointer:
import Foundation
for _ in 0..<1000000 {
let ptr = UnsafeMutablePointer<Float>.allocate(capacity: 1)
ptr.pointee = 0
}
sleep(10)
Suddenly, the program is taking up 21.5 MB of RAM, finally giving us our expected RAM usage increase, although by a larger amount than what I was expecting.
Making a profile in CodeRunner to compile with the optimizations turned on did not seem to make a difference in the behavior I was seeing.
So, surprisingly enough, it does appear that the call to UnsafeMutablePointer.allocate
actually does not immediately allocate memory.
Operating systems can cheat a lot when it comes to memory allocations. If you request a block of memory of size N and don't actually put anything in it, the operating system can very well go "sure you can have a block of memory, here you go" and not really do anything with it. It's really more a promise that the memory will be available when used by the program.
Even with a very simple C program like the one below, the macOS's Activity Monitor will report 945 kB first, then 961 kB after calling malloc
(which allocates the memory), and finally 257.1 MB after filling the allocated memory with zeroes.
From the point of view of the program, all 256 MB needed for the array of integers is available immediately after calling malloc
, but that's actually a lie.
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int count = 64*1024*1024;
printf("Step 1: No memory allocated yet. Check memory usage for baseline, then press enter to continue (1/3)");
getchar();
/* Allocate big block of memory */
int *p = malloc(count*sizeof(int));
if (p == NULL) return 1; // failed to allocate
printf("Step 2: Memory allocated. Check memory usage, then press any key to continue (2/3)");
getchar();
/* Fill with zeroes */
for (int i=0; i < count; i++) {
p[i] = 0;
}
printf("Step 3: Memory filled with zeroes. Check memory usage, then press any key to continue (3/3)");
getchar();
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With