【问题标题】:Swift: copy bytes from array to arraySwift:将字节从数组复制到数组
【发布时间】:2018-04-16 05:22:19
【问题描述】:

我有一些性能敏感的代码需要将一系列值从一个数组复制到另一个数组的特定偏移量。你能在 Swift 中执行一个简单的移动内存操作吗?我整个上午都在阅读这篇文章,但它们让在 Swift 中访问内存变得异常困难,我不确定这是否可能(显然我是 Swift 新手)。

我确实尝试使用 Array.replaceSubrange,但它创建了一大段令人讨厌的代码,谁知道引擎盖下有多少内存副本获得“数组切片”,而且从 Swift 到目前为止的表现来看,函数本身可能很慢。一个简单的 memmove() 可以轻松解决问题。

一个我认为可行的例子。

var src: [UInt32] = [1, 2, 3, 4]
var dest: [UInt32] = [0, 0, 0, 0]

dest.withUnsafeMutableBytes { destBytes in
    src.withUnsafeBytes { srcBytes in
       // for example copy 4 bytes starting at the address of destBytes[1]
       // from the address of srcBytes[1]
       movemem(&destBytes[1], &srcBytes[1], 4)
    }
}
// dest now should be [0, 2, 3, 0] assuming UInt32 is 2 bytes

【问题讨论】:

  • 能否请您也粘贴您的预期结果?
  • "Array.replaceSubrange ... 可能从 Swift 到目前为止的表现来看很慢" – 你能提供一些具体的测量结果来证实这个假设吗?
  • 我编辑了问题以包含预期的结果。用任何其他语言都可以做的真正的基本记忆。
  • 在最好的情况下,Array.replaceSubrange 最终会调用 memmove()。甚至在我调用该函数之前,我必须使用 [0...0] 语法创建一个“切片”,但我不知道那是什么。据我所知,这可能是在堆上分配的东西和稍后收集的垃圾。只是我需要调用 .withUnsafeXXX 的事实对于这样一个微不足道的操作来说已经足够糟糕了。
  • 好吧[0...0] 是range。 [0...2] 表示从第 0 到第 2。

标签: arrays swift


【解决方案1】:

假设问题是针对 需要将一系列值从一个数组复制到另一个数组的特定偏移量。

var src: [UInt32] = [1, 2, 3, 4]
var dest: [UInt32] = [0, 0, 0, 0]

let rangeOfSrc = [1...2] /// Will get from 1st to 2nd so that 2, 3
dest.insert(contentsOf: rangeOfSrc, at: 2) /// Will insert this range at 2nd position of the dest array
print(dest)

输出: [0, 0, 2, 3, 0, 0]

想了解更多详情,请参阅此documentation。

Edit2:如果您想替换范围而不是插入。

dest.replaceSubrange(1...2, with: src[1...2])
print(dest)

输出: [0, 2, 3, 0]

编辑1:memmove

memmove(&dest[0], &src[0], 4)
print(dest)

输出: [1, 0, 0, 0]

编辑3

src.withUnsafeBufferPointer {(result) in
     memmove(&dest[0], result.baseAddress, 8)
     print(dest)
}

输出: [1, 2, 0, 0]

【讨论】:

  • 是的,插入不是我想要的,因为它会增加数组。我没想过要尝试我输入的“伪”代码,但它确实可以编译。 :) 但是有些问题是因为 MemoryLayout.stride 返回 4 而“memmove(&dest[0], &src[0], 4 * 2)” 返回 dest = [1, 16777216, 0, 0]。出了点问题,我不知道你可以像在 Swift 中那样访问内存,所以谁知道它在处理什么。
  • 如果您认为在len 参数中传递8 将使下一个对象进入dest 数组,那么这里是错误的。
  • @GenericPtr 请参阅我的回答中的Edit2。它将替换范围而不是插入。你可能想要同样的。
  • 这不是 memmove 的工作方式。最后一个参数是从 src 指针复制的字节数,因此 8 应该是数组的 2 个元素并产生 [1, 2, 0, 0]。我是 Swift 的新手,但我几乎可以肯定,你不能只获得这样的变量的地址并期望实际获得它们的堆栈位置。我们正在使用该代码解决某些问题,但它可能不是堆栈。我很确定我们需要一个 withUnsafeXXX 调用。
  • 但是如何将它复制到下一个索引? &dest[0], &src[0] 使 src's 0th 元素被复制到 dest's 0th 索引中是有意义的。但是如何通过将len 传递给8 将src[1] 复制到dest[1]?这不是真的。
【解决方案2】:

我有点回答我自己的问题,尽管我认为这不是最好的解决方案。不太了解 Swift,我认为这可能是我们能做的最好的事情,但是对于 memmove(),强制转换为 UnsafeMutableRawPointer(mutating:) 可能会导致内存分配。必须有一种方法来转换它,否则在我的代码中插入该分配肯定会降低性能。

如果有人知道如何避免转换为 UnsafeMutableRawPointer,请告诉我。

        var src: [UInt32] = [1, 2, 3, 4]
        var dest: [UInt32] = [0, 0, 0, 0]
        let elemSize = MemoryLayout<UInt32>.stride

        dest.withUnsafeBytes { (destBuffer: UnsafeRawBufferPointer) in
            src.withUnsafeBytes { (srcBuffer: UnsafeRawBufferPointer) in

                // memmove requires UnsafeMutableRawPointer but how do we avoid this allocation?
                // maybe Swift optimizes this somehow but it looks really bad from here
                let destPtr = UnsafeMutableRawPointer(mutating: destBuffer.baseAddress)
                let destOffset = destPtr! + elemSize
                let srcOffset = srcBuffer.baseAddress! + 0

                // copy 2 elements from src[0] to dest[1]
                memmove(destOffset, srcOffset, elemSize * 2)
            }
        }

        print(dest) // [0, 1, 2, 0]

编辑 1: 现在越来越近了。 replaceSubrange() 显然比 memmove() 慢,实际上慢了大约 6-7 倍。相比之下,字节数越小,replaceSubrange() 就越快。在一个真实的例子中,在执行所有 memmove() 调用之前,您只需获取一次数组字节,因此在实践中它甚至比这更快。

替换子范围:0.750978946685791

内存移动:0.139282941818237

func TestMemmove() {
    var src: [UInt32] = Array(repeating: 1, count: 1000)
    var dest: [UInt32] = Array(repeating: 0, count: 1000)

    let elemSize = MemoryLayout<UInt32>.stride
    let testCycles = 100000
    let rows = 200

    var startTime = CFAbsoluteTimeGetCurrent()
    for _ in 0..<testCycles {
        dest.replaceSubrange(1...1+rows, with: src[0...rows])
    }
    var endTime = CFAbsoluteTimeGetCurrent()
    print("replaceSubrange:  \(endTime - startTime)")

    startTime = CFAbsoluteTimeGetCurrent()
    for _ in 0..<testCycles {
        dest.withUnsafeMutableBytes { destBytes in
            src.withUnsafeMutableBytes { srcBytes in
                let destOffset = destBytes.baseAddress! + elemSize
                let srcOffset = srcBytes.baseAddress! + 0
                memmove(destOffset, srcOffset, elemSize * rows)
            }
        }
    }
    endTime = CFAbsoluteTimeGetCurrent()
    print("memmove:  \(endTime - startTime)")    
}

编辑 2: 毕竟这种愚蠢只是从 c 调用 memmove 是最快的。 Swift 会将指向数组第一个元素的指针传递给 c 函数,您可以使用 c 中的指针算法来处理在 Swift 中需要 .withUnsafeXXX 调用的偏移量(这可能分配了一些类包装器)。

结论是 Swift 很慢,所以用任何对性能敏感的代码来修补 c。

方块移动:0.0957469940185547 替换子范围:1.89903497695923 内存移动:0.136561989784241

// from .c file bridged to Swift
void BlockMove (void* dest, int destOffset, const void* src, int srcOffset, size_t count) {
    memmove(dest + destOffset, src + srcOffset, count);
}

func TestMemmove() {
var src: [UInt32] = Array(repeating: 1, count: 1000)
var dest: [UInt32] = Array(repeating: 0, count: 1000)

let elemSize = MemoryLayout<UInt32>.stride
let testCycles = 100000
let rows = 500
var startTime: CFAbsoluteTime = 0
var endTime: CFAbsoluteTime = 0

// BlockMove (from c)
startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<testCycles {
    BlockMove(&dest, Int32(elemSize), &src, 0, Int32(elemSize * rows))
}
endTime = CFAbsoluteTimeGetCurrent()
print("BlockMove:  \(endTime - startTime)")

// replaceSubrange
startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<testCycles {
    dest.replaceSubrange(1...1+rows, with: src[0...rows])
}
endTime = CFAbsoluteTimeGetCurrent()
print("replaceSubrange:  \(endTime - startTime)")

// memmove
startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<testCycles {
    dest.withUnsafeMutableBytes { destBytes in
        src.withUnsafeMutableBytes { srcBytes in
            let destOffset = destBytes.baseAddress! + elemSize
            let srcOffset = srcBytes.baseAddress! + 0
            memmove(destOffset, srcOffset, elemSize * rows)
        }
    }
}
endTime = CFAbsoluteTimeGetCurrent()
print("memmove:  \(endTime - startTime)")

}

【讨论】:

  • UnsafeMutableRawPointer(mutating:) 确实不分配内存。但是,您可以通过使用 dest.withUnsafeMutableBytes 来摆脱这种转换,这会在闭包中为您提供一个 可变 缓冲区指针。
  • 你不需要得到destBuffer和这个计算,请在我的回答中查看Edit3。
猜你喜欢
  • 2015-03-19
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多