3

I have some performance sensitive code that needs to copy a range of values from one array to another at a specific offset. Can you perform a simple move memory operation in Swift? I've been reading on this all morning but they've made it so incredibly difficult to access memory in Swift I'm not sure if it's even possible (I'm new to Swift obviously).

I did try to use Array.replaceSubrange but it created a big nasty block of code with who knows how many memory copies under the hood getting "array slices" and the function itself is probably slow judging by how Swift is so far. A simple memmove() would slay the problem easily.

An example of what I think is possible.

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
22
  • Could you please paste your expected result too? Commented Apr 16, 2018 at 5:29
  • "Array.replaceSubrange ... is probably slow judging by how Swift is so far" – can you provide some concrete measurements confirming that hypothesis? Commented Apr 16, 2018 at 5:36
  • I edited the question to included the expected result. Really basic memory stuff you'd do in any other language. Commented Apr 16, 2018 at 5:44
  • in the best case scenario Array.replaceSubrange would call memmove() eventually. Even before I call the function I had to create a "slice" using the [0...0] syntax and I don't know what that is. It's could be something allocated on the heap and garbage collected later for all I know. Just the fact I need to call .withUnsafeXXX is bad enough for such a trivial operation. Commented Apr 16, 2018 at 5:48
  • Well [0...0] is range. [0...2] means from 0th to 2nd. Commented Apr 16, 2018 at 5:50

2 Answers 2

2

Assuming question is for needs to copy a range of values from one array to another at a specific offset.

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)

Output: [0, 0, 2, 3, 0, 0]

Would like to refer to this documentation for more detail.

Edit2: In case you want to replace the range instead of inserting.

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

Output: [0, 2, 3, 0]

Edit1: memmove

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

Output: [1, 0, 0, 0]

Edit3

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

Output: [1, 2, 0, 0]

Sign up to request clarification or add additional context in comments.

12 Comments

Yeah insert is not what I want because it grows the array. I didn't think to try that "pseudo" code I typed but it does compile. :) However something is wrong because MemoryLayout<UInt32>.stride returns 4 yet "memmove(&dest[0], &src[0], 4 * 2)" returns dest = [1, 16777216, 0, 0]. Something went wrong and I didn't know you can access memory that like in Swift so who knows what it was addressing.
If you're thinking that passing 8 in len param will get the next object into dest array then this is wrong here.
@GenericPtr See Edit2 of my answer. It will replace the range instead of inserting. You probably want the same.
That's not how memmove works. The last param is the number of bytes copied from the src pointer so 8 should be 2 elements of the array and produce [1, 2, 0, 0]. I'm new to Swift but I'm nearly positive you can't just get the address of variables like that and expect to actually get their location of the stack. We're addressing something with that code but it's not the stack probably. We need a withUnsafeXXX call I'm pretty sure.
But how would that copy it to the just next index? &dest[0], &src[0] make sense that src's 0th element is being copying into dest's 0th index. But how by passing the len to 8 copy the src[1] to dest[1]? This is not true.
|
1

I'm kind of answering my own question even though I don't think this is the best solution. Not knowing Swift well I would this may be the best we can do but there is a potential memory allocation with the cast to UnsafeMutableRawPointer(mutating:) for memmove(). There has to be a way to cast that, otherwise inserting that allocation in my code would certainly kill performance.

If anyone knows how to avoid the cast to UnsafeMutableRawPointer please let me know.

        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]

EDIT 1: Getting closer now. replaceSubrange() is obviously slower than memmove(), about 6-7x slower in fact. The smaller the byte count the faster replaceSubrange() is by comparison. In a real example you'd only get the arrays bytes once before performing all the memmove() calls so it's even faster than this in practice.

replaceSubrange: 0.750978946685791

memmove: 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)")    
}

EDIT 2: After all this stupidity just call memmove from c is fastest. Swift will pass pointers to the first element of the array to c function and you can use pointer arithmetic from c to handle the offsets which required .withUnsafeXXX calls in Swift (that probably allocated some classes wrappers).

The conclusion is that Swift is slow so patch out to c with any performance sensitive code.

BlockMove: 0.0957469940185547 replaceSubrange: 1.89903497695923 memmove: 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)")

}

2 Comments

UnsafeMutableRawPointer(mutating:) does not allocate memory. However, you can get rid of that conversion by using dest.withUnsafeMutableBytes, that gives you a mutable buffer pointer in the closure.
You don't need to get destBuffer and this calculation, please check Edit3 in my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.