主页 GPU Memory Alignment Algorithm
Post
Cancel

GPU Memory Alignment Algorithm

Introduction

This article carries strong personal sentiment. If you feel uncomfortable reading it, please close it as soon as possible. This article is only used as a personal learning record, and you are also welcome to reprint or share it within the scope of the license agreement. Please respect the copyright and keep the original link. Thank you for your understanding and cooperation. If you think this site is helpful to you, you can subscribe to it via RSS. Thanks for your support!

Background

While studying the GPU shader programming chapter of “Metal”, I came across a section about the argument buffer of the GPU resource heap, which needs to send resources from the CPU to the GPU. There I encountered a piece of code that calculates the memory footprint of a resource. It is quite interesting, so I am recording it here.

1
2
3
4
5
6
7
8
9
10
11
let sizeAndAligns = descriptors.map { descriptor in
    Renderer.device.heapTextureSizeAndAlign(descriptor: descriptor)
}
heapDescriptor.size = sizeAndAligns.reduce(0) { total, sizeAndAlign in
    let size = sizeAndAlign.size
    let align = sizeAndAlign.align
    return total + size - (size & (align - 1)) + align   //这行代码
}
if heapDescriptor.size == 0 {
    return nil
}

The documentation explains it like this

You calculate the size of the heap using size and correct alignment within the heap. As long as align is a power of two, (size & (align - 1)) will give you the remainder when size is divided by alignment. For example, if you have a size of 129 bytes, and you want to align it to memory blocks of 128 bytes, this is the result of size - (size & (align - 1)) + align:

1
129 - (129 & (128 - 1)) + 128 = 256

The expression 129 & (128 - 1) is essentially an efficient modulo (remainder) operation, but it only holds when the divisor is a power of two. Here is a detailed explanation:

1. Mathematical Principle

When align is a power of two (e.g., 128 = 2⁷), the binary form of align - 1 is all 1s (for example, the binary of 127 is 01111111). The bitwise AND (&) operation clears the high bits of size, keeping only the value of the low log₂(align) bits, which is the remainder of size % align.

Example calculation For size = 129, align = 128:

\begin{align*}
\text{余数} &= 129 \ \% \ 128 \\
&= 129 \ \& \ (128 - 1) \\
&= 129 \ \& \ 127 \\
&= \text{二进制} \ 10000001 \ \& \ 01111111 \\
&= 00000001 \quad (\text{即十进制 1})
\end{align*}

2. Comparison with Traditional Modulo Operation

MethodExpressionResultApplicable Condition
Bitwise AND methodsize & (align - 1)1align must be a power of two
Traditional modulo methodsize % align1Any positive integer

Advantages

  • High performance: The hardware execution speed of bitwise operations (&) is usually an order of magnitude faster than modulo operations (%).
  • Concise code: Suitable for scenarios that require frequent alignment calculations (such as GPU memory management).

3. Practical Use Cases

When managing heap memory (MTLHeap) in Metal, this method is commonly used to calculate the aligned memory size. For example:

1
2
3
4
5
6
7
8
func alignedSize(size: Int, align: Int) -> Int {
    assert(align.isPowerOfTwo, "Alignment must be a power of two")
    let remainder = size & (align - 1)
    return remainder == 0 ? size : (size - remainder + align)
}

// Example: align 129 bytes to 128 bytes
let aligned = alignedSize(size: 129, align: 128) // 结果为 256

4. What If align Is Not a Power of Two?

If align is not a power of two (e.g., align = 100), the traditional modulo operation must be used:

1
2
3
4
5
6
7
func alignedSizeGeneric(size: Int, align: Int) -> Int {
    let remainder = size % align
    return remainder == 0 ? size : (size - remainder + align)
}

// Example: align 250 to 100
let aligned = alignedSizeGeneric(size: 250, align: 100) // 结果为 300

Yes, the expression 129 & (128 - 1) is essentially an efficient modulo (remainder) operation, but it only holds when the divisor is a power of two.

Summary

  • 129 & (128 - 1) is a modulo operation, but it only holds when align is a power of two.
  • Use cases: GPU memory alignment, optimizing modulo operations in high-performance computing.
  • Core formula:
  \[
\text{AlignedSize} = \text{size} - (\text{size} \ \% \ \text{align}) + \text{align}
\]  

Where % can be optimized with & (align - 1) (when align is a power of two).

该博客文章由作者通过 CC BY 4.0 进行授权。