我有以下稀疏矩阵乘以向量的代码。在稀疏矩阵中,存储每行非零元素的数量,以及该行第一个元素出现的元素数组中的索引。这些用于确保输出向量的任何元素不会被多个线程同时写入两个。在没有优化的情况下构建或在调试模式下构建时,此代码可以正常工作。然而,当我将优化级别设置为
-Ofast
时,我得到了双重免费错误。
关于我可能错过了什么有什么想法吗?
func parallelVectorMultiply(vector: Vector<T>) -> Vector<T> {
guard space == vector.space else {
fatalError("Number of columns in the matrix must match the size of the vector.")
}
var outputElements = [T](repeating: T(0), count: space.dimension)
DispatchQueue.concurrentPerform(iterations: space.dimension) { row in
let start_idx = row_first_element_offsets[row]
if start_idx == -1 { return }
let end_idx = start_idx + nonzero_elements_per_row[row]
var rowSum = T(0)
for i in start_idx..<end_idx {
rowSum = rowSum + values[i].value * vector[values[i].col]
}
outputElements[row] = rowSum
}
return Vector(elements: outputElements, in: space)
}
我希望没有错误。在矩阵
A
乘以向量 X
的方程中:
AX = Y
地点:
在我的代码中,我计算这个总和,然后将结果存储到输出数组中的相应元素。
不应该有两个线程同时释放数组的单个元素。
Array
不是线程安全的。因此,outputElements
不是线程安全的。我知道你说,“不应该有两个线程同时释放数组的单个元素”,但事实是 Array
根本就不是线程安全的。当您违反线程安全性时,您可能会表现出各种不同的问题,因此我不会详细说明您收到的任何错误/崩溃。
尝试暂时打开TSAN,可能会发现问题。 (虽然并不总是如此,但它比试图实际表现出与种族相关的问题要可靠得多。)
如果您要使用这个非线程安全的
Array
来收集结果,您必须同步对它的访问(例如,在高性能场景中,例如这样,我可能会获取锁,例如如 OSAllocatedUnfairLock
),例如:
import os.lock
还有
let lock = OSAllocatedUnfairLock()
DispatchQueue.concurrentPerform(iterations: space.dimension) { row in
…
lock.withLock {
outputElements[row] = rowSum
}
}
您还可以考虑利用已经优化了此类向量运算的库。它将使您摆脱自己进行这些低级计算的麻烦。例如,请参阅使用 vDSP 进行基于向量的算术。或者,更广泛的Accelerate框架也有各种其他选项,因此请查看文档。