如何将C数组传递给SWIFT

uqdfh47h  于 2022-10-04  发布在  Swift
关注(0)|答案(2)|浏览(224)

新手呼叫目标C和斯威夫特。我使用以下代码从C Float数组创建一个NSArray:

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];
}

但是,由于要插入100多万个样本,因此速度很慢,并且成为我的应用程序的瓶颈。有没有一种更快的方法可以从C数组创建NSArray,而不需要逐个复制元素?

az31mfrm

az31mfrm1#

不需要通过OBJ-C。假设output_f出现在通过桥接头包含的包含文件中,则SWIFT会将其类型视为UnsafeMutablePointer<CFloat>(CFloat只是Floattypealias,命名是为了澄清它对应于C类型)。

假设您还使数组中的浮点数可用,假设桥接头文件中的某个位置包含:

extern float* output_f;
extern int output_f_count;

然后在SWIFT端,您可以这样使用它们:

let outputFloats = UnsafeMutableBufferPointer<CFloat>(
    start: output_f, 
    count: Int(output_f_count))

output_f_count转换为Int是必要的,因为SWIFT将C的int解释为CInt(也称为Int32)。

您可以像使用数组一样使用UnsafeMutablePointer,但不能复制。它只是SWIFT中C数据的别名。

如果您希望确保不会更改数据,则可以创建一个UnsafeBufferPointer,但您需要强制转换指针。

let outputFloats = UnsafeBufferPointer<CFloat>(
    start: UnsafePointer(output_f), 
    count: Int(output_f_count))

由于没有复制,这两个选项都非常快。然而,它们只是一些指针。如果SWIFT修改了内容,C代码将看到更改的数据,反之亦然。这可能是一件好事,也可能不是一件好事,这取决于您的用例,但您肯定想要意识到这一点。

如果您想制作副本,可以非常轻松地制作SWIFT阵列,如下所示:

let outputFloatsArray = [CFloat](outputFloats)

现在,您在Array中拥有了您的快捷副本。

作为非常密切相关的事情,如果在C标头中将output_f声明为如下所示的实际数组,

extern float output_f[1360*1060];

那么斯威夫特就看不到指针了。信不信由你,它看到的是一个元组...包含大量CFloat成员的又大又丑的元组,它具有作为值类型的好处,但很难直接使用,因为您不能对其进行索引。幸运的是,您可以解决这个问题:

withUnsafeBytes(of: output_f) 
{
    let outputFloats = B1a5a1b.bindMemory(to: CFloat.self)

    // Now within the scope of this closure you can use outputFloats
    // just as before.
}
  • 注意:您也可以不经过缓冲区指针类型而直接使用指针,而且因为这样避免了边界检查,所以速度稍微快了一点,但只有一点点,更别扭,而且很好……您失去了边界检查捕捉错误的好处。此外,缓冲区指针类型还提供了所有RandomAccessCollection方法,如mapfilterforEach等。

更新:

OP在评论中说,他尝试过这种方法,但在取消对它们的引用时获得了EXEC_BAD_ACCESS。缺少的是从output获取指针到SWIFT可用之间发生的事情的上下文。

根据前面的线索,它实际上是C++,我认为output可能是std::vector<float>,它可能在SWIFT对指针执行任何操作之前就超出了范围,所以它的析构函数被调用,当然,这会删除它的内部数据指针。在这种情况下,SWIFT正在访问不再有效的内存。

有两种方法可以解决这个问题。第一个是确保outputSwift处理完它的数据之前不会被清理。另一种选择是用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));

现在,可以在SWIFT访问output_f之前清理output。这也使得最初询问的副本比使用NSArray快得多。假设C代码在此之后不使用output_f,则SWIFT可以直接获得它的所有权。在这种情况下,SWIFT需要确保在完成时调用free(outout_f)

如果SWIFT代码不关心它是否在实际数组中,Unsafe...BufferPointer类型就可以完成这项工作。

然而,如果需要实际的Array,这将是另一个拷贝,如果可以避免的话,仅仅为了将相同的数据拷贝到SWIFT Array中而拷贝两次是没有意义的。如何避免它取决于C(或Obj-C)是调用SWIFT,还是SWIFT调用Obj-C。我将假设它正在调用C。因此,让我们假设SWIFT正在调用一些定义如下的C函数get_floats()

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;
}

您希望更改接口,以便将预分配的指针及其容量作为参数提供。

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?
}

在SWIFT端,您可以分配指针,但因为您无论如何都希望它位于Array中:

var outputFloats = [Array](repeating: 0, count: 1360*1060)

outputFloats.withUnsafeMutableBuffer {
    get_floats(B1a9a1b.baseAddress, CInt(B1a9a1b.count))
}

// Now the array is populated with the contents of the C array.

最后一件事。上面的代码假设output.data_ptr()指向至少capacity的浮点数。你确定这是真的吗?假设outputstd::vector,最好将memcpy调用更改为:

const size_t floatsToCopy = std::min(capacity, output.size())
    memcpy(output_f, p, floatsToCopy * sizeof(float));

这确保了如果实际数据小于capacity,则不会从实际数据的末尾读取垃圾数据。然后,您可以从get_floats执行return floatsToCopy;

然后在SWIFT方面,看起来是这样的:

var outputFloats = [Array](repeating: 0, count: 1360*1060)

let floatsCopied = outputFloats.withUnsafeMutableBuffer {
    get_floats(B1a11a1b.baseAddress, CInt(B1a11a1b.count))
}

outputFloats.removeLast(
    outputFloats.count - Int(floatsCopied), 
    keepingCapacity: true)

您实际上不必使用keepingCapacity参数,但是这样做允许您重用数组,而不必为更多的内存分配买单。在使用相同的数组再次调用get_floats之前,只需重新填充到满容量即可。此外,除非您的内存使用高峰是个问题,否则keepingCapacity: true可能会比缺省值更快,至少不会更差,因为如果没有它,Array可能会选择重新分配到更小的大小,这在内部是一个分配、一个副本和一个空闲空间,整个问题的关键是避免副本...但是动态内存分配是非常慢的部分。给定CPU缓存和指令流水线的工作方式,您可以在进行一次内存分配所需的时间内进行大量顺序复制。

vsmadaxz

vsmadaxz2#

根据评论部分,您的最终目标是读取SWIFT中的C-ARRAY数据。如果您知道数组的长度,则可以将其作为指针从Objective-C函数返回:

- (float *)cArray {
    float *arr = (float *)malloc(sizeof(float) * 4);
    for (int i = 0; i < 4; ++i) {
        arr[i] = i;
    }
    return arr;
}

只需从SWIFT的UnsafePointer中读取:

let ptr = TDWObject().cArray()

(0 ..< 4).forEach {
    print(ptr.advanced(by: B1a1a1b).pointee)
}

使用完毕后,不要忘记释放指针:

ptr.deallocate()

相关问题