7
votes

I'd like to createa Metal buffers with the newBufferWithBytesNoCopy function for letting CPU and GPU share memory and practicing zero-copy data transfer.

The newBufferWithBytesNoCopy function takes a UnsafeMutablePointer-type pointer, and the pointer needs to be aligned to 16K(16384) bytes.

Could anyone provide advice on how to create a aligned memory to a certain size in Swift?

3
I don't know anything about swift, but in general, you can allocate an extra 16k and round up as needed to find the next 16k boundary. If you know your system better you may be able to reduce the additional allocation size. - mah
@mah Thanks for the advice. I am looking for something like alignas in C. I am using Accelerate Framework in Metal and use some of vDSP's API which is Objective-C where I have UnsafeMutablePointer<Void>. I'd like to know how to use alignas or something similar to it to allocate memory and assign it to the UnsafeMutablePointer<Void>. With my best attempt so far, I have not been able to find a good example for that. - JustDoIt

3 Answers

6
votes

I believe this should work for you:

var memory:UnsafeMutablePointer<Void> = nil
var alignment:UInt = 0x4000 // 16K aligned
var size:UInt = bufferSize // bufferSize == your buffer size

posix_memalign(&memory, alignment, size)

For reference:

http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_memalign.html

2
votes

Swift PageAligned Array (just works solution)

PageAlignedArray is a project that handles the problem of memory allocation for you when memory that will be used with Metal.

More detail

4096 byte alignment

I'm not seeing your 16k byte requirement. The debugger says 4k. Maybe you can provide a reference? It should be based upon the system's page size.

Using posix_memalign

The original idea came from memkite I believe and goes like this:

private func setupSharedMemoryWithSize(byteCount: Int) ->
    (pointer: UnsafeMutablePointer<Void>,
    memoryWrapper: COpaquePointer)
{
    let memoryAlignment = 0x1000 // 4096 bytes, 0x4000 would be 16k
    var memory: UnsafeMutablePointer<Void> = nil
    posix_memalign(&memory, memoryAlignment, byteSizeWithAlignment(memoryAlignment, size: byteCount))

    let memoryWrapper = COpaquePointer(memory)

    return (memory, memoryWrapper)
}

Calculating the memory size

You'll want to calculate the correct amount of memory to allocate so that it lands on the byte boundary. Which means you'll probably need to allocate a bit more than you desired. Here you pass the size you need and alignment and it will return the amount you should allocate.

private func byteSizeWithAlignment(alignment: Int, size: Int) -> Int
{
    return Int(ceil(Float(size) / Float(alignment))) * alignment
}

Using the functions

let (pointer, memoryWrapper) = setupSharedMemoryWithSize(byteCount)
var unsafeVoidPointer: UnsafeMutablePointer<Void> = pointer

// Assuming your underlying data is unsigned 8-bit integers.
let unsafeMutablePointer = UnsafeMutablePointer<UInt8>(memoryWrapper)
let unsafeMutableBufferPointer = UnsafeMutableBufferPointer(start: unsafeMutablePointer, count: byteCount)

Don't forget to free the memory you allocated.

Allocating shared memory without posix_memalign.

These days you can allocate the memory without posix_memalign by just specifying .StorageModeShared.

let byteCount = 1000 * sizeof(Float)
let sharedMetalBuffer = self.metalDevice.newBufferWithLength(byteCount, options: .StorageModeShared)
1
votes

After experiencing some annoyance with this problem, I've decided to go ahead and create a simple solution that should make this a lot easier.

I've created a Swift array implementation called PageAlignedArray that matches the interface and functionality of the built-in Swift array, but always resides on page-aligned memory, and so can be very easily made into an MTLBuffer. I've also added a convenience method to directly convert PageAlignedArray into a Metal buffer.

Of course, you can continue to mutate your array afterwards and your updates will be automatically available to the GPU courtesy of the shared-memory architecture. However, keep in mind that you must regenerate your MTLBuffer object whenever the array's length changes.

Here's a quick code sample:

  var alignedArray : PageAlignedContiguousArray<matrix_double4x4> = [matrixTest, matrixTest]
  alignedArray.append(item)
  alignedArray.removeFirst() // Behaves just like a built-in array, with all convenience methods

  // When it's time to generate a Metal buffer:
  let device = MTLCreateSystemDefaultDevice()
  let testMetalBuffer = device?.makeBufferWithPageAlignedArray(alignedArray)

The sample uses matrix_double4x4, but the array should work for any Swift value types. Please note that if you use a reference type (such as any kind of class), the array will contain pointers to your elements and so won't be usable from your GPU code.

Please grab PageAlignedArray here: https://github.com/eldoogy/PageAlignedArray