Search code examples
swiftmemcpy

memcpy to copy array with offset


I have a huge array that I want to copy only a middle portion of it. I'm trying to use memcpy for the performance, but can't figure out a way to do so. Below is an example. How do we provide an offset to the source?

var source: [UInt8] = [1,2,3,4,5,6,7,8,9]
var dest: [UInt8] = [0,0,0,0,0,0,0,0,0]

memcpy(&dest[2], source, 5)

print(dest) // [0, 0, 1, 2, 3, 4, 5, 0, 0]
// This works
var source: [UInt8] = [1,2,3,4,5,6,7,8,9]
var dest: [UInt8] = [0,0,0,0,0,0,0,0,0]

memcpy(&dest[2], &source[2], 5)

print(dest) // [0, 0, 3, 0, 0, 0, 0, 0, 0]
// Only `source[2]` is copied

Solution

  • You can use replaceSubrange() for that purpose:

    let source: [UInt8] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    var dest: [UInt8] =   [0, 0, 0, 0, 0, 0, 0, 0, 0]
    
    dest.replaceSubrange(2..<(2+5), with: source[2..<(2+5)])
    print(dest) // [0, 0, 3, 4, 5, 6, 7, 0, 0]
    

    or simply assign a source slice to a destination slice:

    dest[2..<(2+5)] = source[2..<(2+5)]
    print(dest) // [0, 0, 3, 4, 5, 6, 7, 0, 0]
    

    Benchmark

    on a MacBook Air M2, with the code compiled in Release configuration.

    import Foundation
    
    let N = 1_000_000 // Length of source and destination array
    let sourceStart = 500
    let destinationStart = 2000
    let length = 12345
    
    let source: [UInt8] = Array(repeating: 5, count: N)
    
    for _ in 1...10 {
        do {
            var dest: [UInt8] = Array(repeating: 0, count: N)
            let start = Date()
            
            dest[destinationStart..<(destinationStart+length)].withUnsafeMutableBufferPointer { p in
                source.copyBytes(to: p, from: sourceStart..<sourceStart + length)
            }
            let end = Date()
            print("copyBytes:      ", end.timeIntervalSince(start) * 1000, "ms")
        }
        do {
            var dest: [UInt8] = Array(repeating: 0, count: N)
            let start = Date()
            
            source.withUnsafeBytes { sourcePtr in
                _ = memcpy(&dest[destinationStart], sourcePtr.baseAddress! + sourceStart, length)
            }
            let end = Date()
            print("memcpy:         ", end.timeIntervalSince(start) * 1000, "ms")
        }
        do {
            var dest: [UInt8] = Array(repeating: 0, count: N)
            let start = Date()
            
            dest.replaceSubrange(destinationStart..<(destinationStart+length),
                                 with: source[sourceStart..<(sourceStart+length)])
            
            let end = Date()
            print("replaceSubrange:", end.timeIntervalSince(start) * 1000, "ms")
        }
        do {
            var dest: [UInt8] = Array(repeating: 0, count: N)
            let start = Date()
            
            dest[destinationStart..<(destinationStart+length)]
                = source[sourceStart..<(sourceStart+length)]
            
            let end = Date()
            print("slicing:        ", end.timeIntervalSince(start) * 1000, "ms")
        }
        print()
    }
    

    Results:

    copyBytes:       0.010013580322265625 ms
    memcpy:          0.0010728836059570312 ms
    replaceSubrange: 0.009059906005859375 ms
    slicing:         0.010013580322265625 ms