在 Swift 中转换 [UInt32] -> [UInt8] -> [[UInt8]]
Convert [UInt32] -> [UInt8] -> [[UInt8]] in Swift
我正在尝试加快我当前将 [UInt32] 转换为 [UInt8] 的函数的实现,后者又被拆分为 [[UInt8]],每个索引有 6 个数组。
我的实现:
extension Array {
func splitBy(subSize: Int) -> [[Element]] {
return 0.stride(to: self.count, by: subSize).map { startIndex in
let endIndex = startIndex.advancedBy(subSize, limit: self.count)
return Array(self[startIndex ..< endIndex])
}
}
}
func convertWordToBytes(fullW : [UInt32]) -> [[UInt8]] {
var combined8 = [UInt8]()
//Convert 17 [UInt32] to 68 [UInt8]
for i in 0...16{
_ = 24.stride(through: 0, by: -8).map {
combined8.append(UInt8(truncatingBitPattern: fullW[i] >> UInt32([=11=])))
}
}
//Split [UInt8] to [[UInt8]] with 6 values at each index.
let combined48 = combined8.splitBy(6)
return combined48
}
这个函数在我的程序中会被迭代数百万次,它的速度是一个巨大的负担。
有人有什么想法吗?
谢谢
如果您分析 (Cmd + I
) 您的代码,您会发现大部分时间都在处理各种 "copy to buffer" 函数。当您将一个新元素附加到数组但它的初始分配 space 中有 运行 时会发生这种情况,因此必须将其移动到堆 上的位置 有更多的内存。教训的教训:堆分配很慢,但对于数组来说是不可避免的。尽量少做几次。
试试这个:
func convertWordToBytes2(fullW: [UInt32]) -> [[UInt8]] {
let subSize = 6
// We allocate the array only once per run since allocation is so slow
// There will only be assignment to it after
var combined48 = [UInt8](count: fullW.count * 4, repeatedValue: 0).splitBy(subSize)
var row = 0
var col = 0
for i in 0...16 {
for j in 24.stride(through: 0, by: -8) {
let value = UInt8(truncatingBitPattern: fullW[i] >> UInt32(j))
combined48[row][col] = value
col += 1
if col >= subSize {
row += 1
col = 0
}
}
}
return combined48
}
基准代码:
let testCases = (0..<1_000_000).map { _ in
(0..<17).map { _ in arc4random() }
}
testCases.forEach {
convertWordToBytes([=11=])
convertWordToBytes2([=11=])
}
结果(在我的 2012 iMac 上)
Weight Self Weight Symbol Name
9.35 s 53.2% 412.00 ms specialized convertWordToBytes([UInt32]) -> [[UInt8]]
3.28 s 18.6% 344.00 ms specialized convertWordToBytes2([UInt32]) -> [[UInt8]]
通过消除多次分配,我们已经将 运行 时间减少了 60%。但是每个测试用例都是独立的,这非常适合当今多核 CPU 的并行处理。修改后的循环...:[=16=]
dispatch_apply(testCases.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { i in
convertWordToBytes2(testCases[i])
}
... 在我的具有 8 个线程的四核 i7 上执行时,将节省大约 1 秒的时间:
Weight Self Weight Symbol Name
2.28 s 6.4% 0 s _dispatch_worker_thread3 0x58467
2.24 s 6.3% 0 s _dispatch_worker_thread3 0x58463
2.22 s 6.2% 0 s _dispatch_worker_thread3 0x58464
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58466
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58465
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58461
2.18 s 6.1% 0 s _dispatch_worker_thread3 0x58462
节省的时间没有我希望的那么多。访问堆内存时显然存在一些争用。为了更快,您应该探索基于 C 的解决方案。
我正在尝试加快我当前将 [UInt32] 转换为 [UInt8] 的函数的实现,后者又被拆分为 [[UInt8]],每个索引有 6 个数组。
我的实现:
extension Array {
func splitBy(subSize: Int) -> [[Element]] {
return 0.stride(to: self.count, by: subSize).map { startIndex in
let endIndex = startIndex.advancedBy(subSize, limit: self.count)
return Array(self[startIndex ..< endIndex])
}
}
}
func convertWordToBytes(fullW : [UInt32]) -> [[UInt8]] {
var combined8 = [UInt8]()
//Convert 17 [UInt32] to 68 [UInt8]
for i in 0...16{
_ = 24.stride(through: 0, by: -8).map {
combined8.append(UInt8(truncatingBitPattern: fullW[i] >> UInt32([=11=])))
}
}
//Split [UInt8] to [[UInt8]] with 6 values at each index.
let combined48 = combined8.splitBy(6)
return combined48
}
这个函数在我的程序中会被迭代数百万次,它的速度是一个巨大的负担。
有人有什么想法吗? 谢谢
如果您分析 (Cmd + I
) 您的代码,您会发现大部分时间都在处理各种 "copy to buffer" 函数。当您将一个新元素附加到数组但它的初始分配 space 中有 运行 时会发生这种情况,因此必须将其移动到堆 上的位置 有更多的内存。教训的教训:堆分配很慢,但对于数组来说是不可避免的。尽量少做几次。
试试这个:
func convertWordToBytes2(fullW: [UInt32]) -> [[UInt8]] {
let subSize = 6
// We allocate the array only once per run since allocation is so slow
// There will only be assignment to it after
var combined48 = [UInt8](count: fullW.count * 4, repeatedValue: 0).splitBy(subSize)
var row = 0
var col = 0
for i in 0...16 {
for j in 24.stride(through: 0, by: -8) {
let value = UInt8(truncatingBitPattern: fullW[i] >> UInt32(j))
combined48[row][col] = value
col += 1
if col >= subSize {
row += 1
col = 0
}
}
}
return combined48
}
基准代码:
let testCases = (0..<1_000_000).map { _ in
(0..<17).map { _ in arc4random() }
}
testCases.forEach {
convertWordToBytes([=11=])
convertWordToBytes2([=11=])
}
结果(在我的 2012 iMac 上)
Weight Self Weight Symbol Name
9.35 s 53.2% 412.00 ms specialized convertWordToBytes([UInt32]) -> [[UInt8]]
3.28 s 18.6% 344.00 ms specialized convertWordToBytes2([UInt32]) -> [[UInt8]]
通过消除多次分配,我们已经将 运行 时间减少了 60%。但是每个测试用例都是独立的,这非常适合当今多核 CPU 的并行处理。修改后的循环...:[=16=]
dispatch_apply(testCases.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { i in
convertWordToBytes2(testCases[i])
}
... 在我的具有 8 个线程的四核 i7 上执行时,将节省大约 1 秒的时间:
Weight Self Weight Symbol Name
2.28 s 6.4% 0 s _dispatch_worker_thread3 0x58467
2.24 s 6.3% 0 s _dispatch_worker_thread3 0x58463
2.22 s 6.2% 0 s _dispatch_worker_thread3 0x58464
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58466
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58465
2.21 s 6.2% 0 s _dispatch_worker_thread3 0x58461
2.18 s 6.1% 0 s _dispatch_worker_thread3 0x58462
节省的时间没有我希望的那么多。访问堆内存时显然存在一些争用。为了更快,您应该探索基于 C 的解决方案。