Newbie to Objective-C and Swift here. I'm creating an NSArray from a C float array with the following code:
float* output_f = output.data_ptr<float>();
NSMutableArray *results = [NSMutableArray arrayWithCapacity: 1360*1060]
for (int i = 0; i < 1360 * 1016; i ) {
[results insertObject:@(output_f[i]) atIndex:i];
}
However since there are over a million samples to be inserted this is slow and is becoming a bottle-neck in my application. Is there a quicker way to create an NSArray from a C array without copying the elements one-by-one?
CodePudding user response:
There's no need to go through Obj-C. Assuming that output_f
appears in an include file that's included via your bridging header, Swift will see its type as UnsafeMutablePointer<CFloat>
(CFloat
is just a typealias
for Float
, named to clarify that it corresponds to the C type).
Assuming you also make the number of floats in the array available, lets say included somewhere in your bridged header files is:
extern float* output_f;
extern int output_f_count;
Then on the Swift-side, you can use them like this:
let outputFloats = UnsafeMutableBufferPointer<CFloat>(
start: output_f,
count: Int(output_f_count))
The cast of output_f_count
to Int
is necessary because Swift interprets C's int
as CInt
(aka Int32
).
You can use UnsafeMutablePointer
much like array, but there's no copying. It just aliases the C data in Swift.
If you want to make sure you don't mutate the data, you can create an UnsafeBufferPointer
instead, but you'll need to cast the pointer.
let outputFloats = UnsafeBufferPointer<CFloat>(
start: UnsafePointer(output_f),
count: Int(output_f_count))
Since there's no copying, both of those options are very fast. However, they are pointers. If Swift modifies the contents, the C code will see the changed data, and vice-versa. That may or may not be a good thing, depending on your use case, but you definitely want to be aware of it.
If you want to make a copy, you can make a Swift Array very easily like this:
let outputFloatsArray = [CFloat](outputFloats)
Now you have you Swift-side copy in an Array
.
As a very closely related thing, if in a C header, output_f
were declared as an actual array like this,
extern float output_f[1360*1060];
Then Swift doesn't see a pointer. It sees, believe it or not, a tuple... a great big ugly tuple with a crap-load of CFloat
members, which has the benefit of being a value type, but is hard to work with directly because you can't index into it. Fortunately you can work around that:
withUnsafeBytes(of: output_f)
{
let outputFloats = $0.bindMemory(to: CFloat.self)
// Now within the scope of this closure you can use outputFloats
// just as before.
}
- Note: You can also use the pointer directly without going through the buffer pointer types, and because you avoid bounds-checking that way, it is a tiny bit faster, but just a very tiny bit, it's more awkward, and well... you lose the error catching benefits of bounds-checking. Plus the buffer pointer types provide all the
RandomAccessCollection
methods likemap
,filter
,forEach
, etc...
Update:
In comments OP said that he had tried this approach but got EXEC_BAD_ACCESS while dereferencing them. Missing is the context of what is happening between obtaining the pointer from output
and its being available to Swift.
Given the clue from earlier that it's actually C , I think output
is probably std::vector<float>
, and its probably going out of scope before Swift does anything with the pointers, so its destructor is being called, which of course, deletes its internal data pointer. In that case Swift is accessing memory that is no longer valid.
There are two ways to address this. The first is to make sure that output
is not cleaned up until after Swift
is done with it's data. The other option, is to copy the data in C.
const int capacity = 1360*1060;
float* p = output.data_ptr<float>();
// static_cast because the above template syntax indicates
// this is actually C , not C.
float* output_f = static_cast<float*>(calloc(capacity, sizeof(float)));
memcpy(output_f, p, capacity * sizeof(float));
Now output
can be cleaned up before Swift accesses output_f
. Also this makes the copy that was originally asked about much faster that using NSArray
. Assuming the C code doesn't use output_f
after this, Swift can just take ownership of it. In that case, Swift needs to be sure to call free(outout_f)
when it's done.
If the Swift code doesn't care about it being in an actual array, the Unsafe...BufferPointer
types will do the job.
However, if an actual Array
is desired, this will be yet another copy, and copying the same data twice just to get it in a Swift Array
doesn't make sense if it can be avoided. How to avoid it depends on whether C (or Obj-C) is calling Swift, or Swift is calling Obj-C. I'm going to assume that it's Swift calling C. So let's assume that Swift is calling some C function get_floats()
defined like this:
extern "C" *float get_floats()
{
const int capacity = 1360*1060;
float* p = output.data_ptr<float>();
// static_cast because the above template syntax indicates
// this is actually C , not C.
float* output_f = static_cast<float*>(
calloc(capacity, sizeof(float))
);
memcpy(output_f, p, capacity * sizeof(float));
// Maybe do other work including disposing of `output`
return output_f;
}
You want to change the interface so that a pre-allocated pointer is provided as a parameter, along with its capacity.
extern "C" void get_floats(float *output_f, int capacity)
{
float* p = output.data_ptr<float>();
memcpy(output_f, p, capacity * sizeof(float));
// Maybe do other work including disposing of `output`
// can use return for something else now -- maybe error code?
}
On the Swift side, you could allocate pointers, but since you want it in an Array
anyway:
var outputFloats = [Array](repeating: 0, count: 1360*1060)
outputFloats.withUnsafeMutableBuffer {
get_floats($0.baseAddress, CInt($0.count))
}
// Now the array is populated with the contents of the C array.
One last thing. The above code makes an assumption that output.data_ptr()
points to at least capacity
number of floats. Are you sure this is true? Assuming output
is std::vector
, it would be better to change the memcpy
call to:
let floatsToCopy = std::min(capacity, output.size())
memcpy(output_f, p, floatsToCopy * sizeof(float));
That ensures that you're not reading garbage from the end of real data if it's actually less than capacity
. Then you can return floatsToCopy;
from get_floats
.
Then on the Swift side, it looks like this:
var outputFloats = [Array](repeating: 0, count: 1360*1060)
let floatsCopied = outputFloats.withUnsafeMutableBuffer {
get_floats($0.baseAddress, CInt($0.count))
}
outputFloats.removeLast(
outputFloats.count - Int(floatsCopied),
keepingCapacity: true)
You don't actually have to use the keepingCapacity
parameter, but doing so allows you to re-use the array without having to pay for more memory allocations. Just refill out to full capacity before calling get_floats
again with the same array. Plus unless your peak memory usage is an issue, keepingCapacity: true
is likely faster, and at least no worse, than the default, because without it, Array
might choose to reallocate to the smaller size, which internally is an allocation, a copy, and a free, and the whole point was to avoid a copy... but the dynamic memory allocation is the really slow part. Given CPU caches and the way instruction pipelines work, you can do a lot of sequential copying in the time it takes to do a single memory allocation.
CodePudding user response:
According to the comments section your final goal is to read C-array data in Swift. Provided you know the length of the array, you can return it from an Objective-C function as a pointer:
- (float *)cArray {
float *arr = (float *)malloc(sizeof(float) * 4);
for (int i = 0; i < 4; i) {
arr[i] = i;
}
return arr;
}
And just read it from an UnsafePointer
in Swift:
let ptr = TDWObject().cArray()
(0 ..< 4).forEach {
print(ptr.advanced(by: $0).pointee)
}
Don't forget to deallocate the pointer when you are done with it:
ptr.deallocate()