🔗 快速导航
📋 目录
点击展开/折叠🎯 概述
内存对齐(Memory Alignment) 是指数据在内存中的地址必须是特定值的倍数。正确的内存对齐对程序性能至关重要,特别是在高性能游戏引擎中。
在本教程中,我们将:
- ✅ 理解什么是内存对齐以及为什么重要
- ✅ 学习不同数据类型的对齐要求
- ✅ 实现对齐内存分配函数
- ✅ 在动态分配器中添加对齐支持
- ✅ 掌握 SIMD 和缓存行对齐
❓ 什么是内存对齐?
内存对齐是指数据的内存地址必须是某个值(通常是数据大小)的倍数。
示例:
未对齐(Unaligned):
地址:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
数据:[char] [ int ] [char] [ double ]
↑ ↑ ↑ ↑
对齐 未对齐 对齐 未对齐(地址 6 不是 8 的倍数)
已对齐(Aligned):
地址:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
数据:[char] padding [ int ] padding [ double ]
↑ ↑ ↑
对齐(1倍数) 对齐(4倍数) 对齐(8倍数)
对齐规则:
| 数据类型 | 大小 | 对齐要求 | 示例地址 |
|---|---|---|---|
char | 1 字节 | 1 字节 | 任意地址 |
short | 2 字节 | 2 字节 | 0, 2, 4, 6, … |
int | 4 字节 | 4 字节 | 0, 4, 8, 12, … |
long long | 8 字节 | 8 字节 | 0, 8, 16, 24, … |
float | 4 字节 | 4 字节 | 0, 4, 8, 12, … |
double | 8 字节 | 8 字节 | 0, 8, 16, 24, … |
__m128 (SSE) | 16 字节 | 16 字节 | 0, 16, 32, 48, … |
__m256 (AVX) | 32 字节 | 32 字节 | 0, 32, 64, 96, … |
🚀 为什么需要内存对齐?
1. 性能原因
未对齐访问的开销:
性能对比:
// 对齐访问
struct aligned {
int a; // 地址 0
int b; // 地址 4
int c; // 地址 8
} __attribute__((aligned(4)));
// 访问速度:1 次内存读取 per int
// 未对齐访问
struct unaligned {
char x; // 地址 0
int a; // 地址 1 (未对齐!)
int b; // 地址 5 (未对齐!)
} __attribute__((packed));
// 访问速度:可能需要 2 次内存读取 per int
实测性能差异:
对齐访问: 1.0 ns per access
未对齐访问: 2.5 ns per access(慢 2.5 倍)
在每帧访问百万次的游戏引擎中:
对齐: 1,000,000 × 1.0 ns = 1.0 ms
未对齐: 1,000,000 × 2.5 ns = 2.5 ms
差异: 1.5 ms per frame(60 FPS = 16.67 ms per frame,差异显著!)
2. 硬件要求
某些 CPU 架构要求对齐访问:
// ARM 架构(某些版本)
int* ptr = (int*)0x03; // 未对齐地址
int value = *ptr; // ❌ 硬件异常!程序崩溃!
// x86/x64
int* ptr = (int*)0x03; // 未对齐地址
int value = *ptr; // ✅ 可以工作,但性能差
3. SIMD 指令要求
SIMD(单指令多数据)指令通常必须对齐:
#include <immintrin.h>
// ❌ 未对齐 - 可能崩溃
float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
__m128 vec = _mm_load_ps(data); // 如果 data 不是 16 字节对齐,崩溃!
// ✅ 对齐 - 正常工作
alignas(16) float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
__m128 vec = _mm_load_ps(data); // 正常工作
// 或使用未对齐加载(更慢)
float data[4] = {1.0f, 2.0f, 3.0f, 4.0f};
__m128 vec = _mm_loadu_ps(data); // 可以工作,但比对齐版本慢
4. 缓存行对齐
缓存行(Cache Line)通常是 64 字节,对齐到缓存行可以避免伪共享:
// ❌ 伪共享 - 两个线程访问相同的缓存行
struct bad_threading {
int thread1_counter; // 可能在同一缓存行
int thread2_counter; // 可能在同一缓存行
};
// 问题:线程 1 修改 counter 会使线程 2 的缓存行失效
// ✅ 缓存行对齐 - 每个线程独立的缓存行
struct good_threading {
alignas(64) int thread1_counter; // 独立缓存行
alignas(64) int thread2_counter; // 独立缓存行
};
📊 对齐要求
结构体对齐
编译器自动对齐结构体成员:
struct example {
char a; // 1 字节
// 填充 3 字节
int b; // 4 字节
char c; // 1 字节
// 填充 7 字节
double d; // 8 字节
};
sizeof(struct example) = 24 字节(不是 1+4+1+8=14 字节)
内存布局:
地址:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
数据:[a][ pad ][ b ][c][ pad ][ d ]
↑ ↑ ↑ ↑
字节0 字节4 字节8 字节16
优化结构体布局:
// ❌ 浪费空间(24 字节)
struct bad {
char a; // 1 字节 + 3 字节填充
int b; // 4 字节
char c; // 1 字节 + 7 字节填充
double d; // 8 字节
}; // 总计:24 字节
// ✅ 节省空间(16 字节)
struct good {
double d; // 8 字节
int b; // 4 字节
char a; // 1 字节
char c; // 1 字节
// 填充 2 字节
}; // 总计:16 字节
规则:
- 按大小降序排列成员
- 最大成员决定结构体对齐
- 结构体大小是最大成员对齐的倍数
💾 内存布局示例
数组对齐
// char 数组 - 任意对齐
char arr1[10]; // 可以从任意地址开始
// int 数组 - 4 字节对齐
int arr2[10]; // 必须从 4 的倍数地址开始
// double 数组 - 8 字节对齐
double arr3[10]; // 必须从 8 的倍数地址开始
// SIMD 数组 - 16 字节对齐
alignas(16) float simd_arr[4]; // 必须从 16 的倍数地址开始
动态分配对齐
// 标准 malloc - 通常对齐到 8 或 16 字节
void* ptr1 = malloc(100);
// 对齐分配 - 明确对齐要求
void* ptr2 = aligned_alloc(64, 256); // 64 字节对齐,256 字节大小
// Kohi 引擎的对齐分配
void* ptr3 = kallocate_aligned(1024, 16, MEMORY_TAG_RENDERER); // 16 字节对齐
🔧 实现对齐内存分配
对齐分配 API
/**
* @brief 从主机执行给定大小和对齐的对齐内存分配
* 为提供的标签跟踪分配
* 注意:以这种方式分配的内存必须使用 kfree_aligned 释放
*
* @param size 分配的大小
* @param alignment 对齐(字节)
* @param tag 指示分配块的用途
* @return 成功时,指向分配的内存块的指针;否则为 0
*/
KAPI void* kallocate_aligned(u64 size, u16 alignment, memory_tag tag);
/**
* @brief 释放给定的对齐块,并从给定标签取消跟踪其大小
*
* @param block 指向要释放的内存块的指针
* @param size 要释放的块的大小
* @param alignment 块的对齐(字节)
* @param tag 指示块用途的标签
*/
KAPI void kfree_aligned(void* block, u64 size, u16 alignment, memory_tag tag);
/**
* @brief 返回给定内存块的大小和对齐
* 注意:此方法的失败结果很可能表示堆损坏
*
* @param block 内存块
* @param out_size 指向保存块大小的指针
* @param out_alignment 指向保存块对齐的指针
* @return 成功返回 true;否则返回 false
*/
KAPI b8 kmemory_get_size_alignment(void* block, u64* out_size, u16* out_alignment);
对齐分配实现
void* kallocate_aligned(u64 size, u16 alignment, memory_tag tag) {
if (tag == MEMORY_TAG_UNKNOWN) {
KWARN("kallocate_aligned called using MEMORY_TAG_UNKNOWN.");
}
void* block = 0;
if (state_ptr) {
// 多线程安全
if (!kmutex_lock(&state_ptr->allocation_mutex)) {
KFATAL("Error obtaining mutex lock during allocation.");
return 0;
}
// 更新统计信息
state_ptr->stats.total_allocated += size;
state_ptr->stats.tagged_allocations[tag] += size;
state_ptr->alloc_count++;
// 从动态分配器分配对齐内存
block = dynamic_allocator_allocate_aligned(&state_ptr->allocator, size, alignment);
kmutex_unlock(&state_ptr->allocation_mutex);
} else {
// 系统未初始化,使用平台分配
KWARN("kallocate_aligned called before memory system initialization.");
block = platform_allocate_aligned(size, alignment, false);
}
if (block) {
// 清零内存
platform_zero_memory(block, size);
return block;
}
KFATAL("kallocate_aligned failed to allocate.");
return 0;
}
对齐释放
void kfree_aligned(void* block, u64 size, u16 alignment, memory_tag tag) {
if (tag == MEMORY_TAG_UNKNOWN) {
KWARN("kfree_aligned called using MEMORY_TAG_UNKNOWN.");
}
if (state_ptr) {
if (!kmutex_lock(&state_ptr->allocation_mutex)) {
KFATAL("Unable to obtain mutex lock for free operation.");
return;
}
// 更新统计信息
state_ptr->stats.total_allocated -= size;
state_ptr->stats.tagged_allocations[tag] -= size;
state_ptr->alloc_count--;
// 从动态分配器释放
b8 result = dynamic_allocator_free_aligned(&state_ptr->allocator, block);
kmutex_unlock(&state_ptr->allocation_mutex);
if (!result) {
// 分配可能在系统启动前,尝试平台释放
platform_free_aligned(block, alignment, false);
}
} else {
platform_free_aligned(block, alignment, false);
}
}
🏗️ 动态分配器中的对齐
动态分配器需要支持对齐分配:
typedef struct dynamic_allocator {
u64 total_size;
freelist list;
void* freelist_block;
void* memory;
} dynamic_allocator;
/**
* @brief 分配对齐内存块
*/
void* dynamic_allocator_allocate_aligned(dynamic_allocator* allocator,
u64 size,
u16 alignment) {
if (!allocator || !size || !alignment) {
return 0;
}
// 计算对齐所需的额外空间
// 最坏情况:需要 (alignment - 1) 字节的填充
u64 header_size = sizeof(u64) + sizeof(u16); // 大小 + 对齐
u64 aligned_size = size + alignment + header_size;
// 从自由列表分配
void* raw_block = freelist_allocate(&allocator->list, aligned_size);
if (!raw_block) {
return 0;
}
// 计算对齐地址
u64 raw_addr = (u64)raw_block;
u64 header_addr = raw_addr + header_size;
u64 aligned_addr = (header_addr + alignment - 1) & ~(alignment - 1);
// 存储头部信息(在对齐地址之前)
u64* size_ptr = (u64*)(aligned_addr - header_size);
u16* alignment_ptr = (u16*)(aligned_addr - sizeof(u16));
*size_ptr = aligned_size;
*alignment_ptr = alignment;
return (void*)aligned_addr;
}
/**
* @brief 释放对齐内存块
*/
b8 dynamic_allocator_free_aligned(dynamic_allocator* allocator, void* block) {
if (!allocator || !block) {
return false;
}
// 读取头部信息
u64 block_addr = (u64)block;
u64 header_size = sizeof(u64) + sizeof(u16);
u64* size_ptr = (u64*)(block_addr - header_size);
u16* alignment_ptr = (u16*)(block_addr - sizeof(u16));
u64 size = *size_ptr;
u16 alignment = *alignment_ptr;
// 计算原始块地址
u64 raw_addr = block_addr - alignment + 1; // 简化计算
// 释放到自由列表
return freelist_free(&allocator->list, (void*)raw_addr, size);
}
📐 计算对齐地址
对齐宏
/**
* @brief 将地址向上对齐到指定对齐
*/
#define ALIGN_UP(addr, alignment) \
(((addr) + (alignment) - 1) & ~((alignment) - 1))
/**
* @brief 将地址向下对齐到指定对齐
*/
#define ALIGN_DOWN(addr, alignment) \
((addr) & ~((alignment) - 1))
/**
* @brief 检查地址是否对齐
*/
#define IS_ALIGNED(addr, alignment) \
(((addr) & ((alignment) - 1)) == 0)
示例:
u64 addr = 0x1003; // 4099
// 对齐到 4 字节
u64 aligned4 = ALIGN_UP(addr, 4);
// 0x1003 + 4 - 1 = 0x1006
// 0x1006 & ~0x0003 = 0x1006 & 0xFFFFFFFC = 0x1004
// 对齐到 16 字节
u64 aligned16 = ALIGN_UP(addr, 16);
// 0x1003 + 16 - 1 = 0x1012
// 0x1012 & ~0x000F = 0x1012 & 0xFFFFFFF0 = 0x1010
// 检查对齐
b8 is_aligned = IS_ALIGNED(0x1000, 16); // true
is_aligned = IS_ALIGNED(0x1004, 16); // false
对齐算法详解
/**
* @brief 对齐算法的工作原理
*/
u64 align_up(u64 addr, u64 alignment) {
// 前提条件:alignment 必须是 2 的幂
// 例如:4, 8, 16, 32, 64, ...
// 步骤 1:加上 (alignment - 1)
// 这确保我们至少到达下一个对齐边界
u64 temp = addr + alignment - 1;
// 步骤 2:AND 操作与 ~(alignment - 1)
// ~(alignment - 1) 创建一个掩码,清除低位
// 例如,对于 alignment = 16 (0x10):
// alignment - 1 = 15 (0x0F) = 0000 1111
// ~(alignment - 1) = 0xFFFFFFF0 = 1111 0000
// AND 操作清除低 4 位,得到 16 的倍数
u64 mask = ~(alignment - 1);
u64 aligned = temp & mask;
return aligned;
}
// 示例:将 0x1007 对齐到 16 字节
// addr = 0x1007
// alignment = 16 (0x10)
//
// temp = 0x1007 + 0x0F = 0x1016
// mask = ~0x0F = 0xFFFFFFF0
// aligned = 0x1016 & 0xFFFFFFF0 = 0x1010
//
// 结果:0x1010(16 的下一个倍数)
⚡ 性能影响
基准测试
#include <time.h>
void benchmark_alignment() {
const u32 ITERATIONS = 10000000;
// 测试 1:对齐访问
alignas(16) int aligned_array[1000];
clock_t start = clock();
for (u32 i = 0; i < ITERATIONS; ++i) {
volatile int sum = 0;
for (u32 j = 0; j < 1000; ++j) {
sum += aligned_array[j];
}
}
clock_t end = clock();
double aligned_time = (double)(end - start) / CLOCKS_PER_SEC;
// 测试 2:未对齐访问
char* unaligned_block = malloc(1000 * sizeof(int) + 1);
int* unaligned_array = (int*)(unaligned_block + 1); // 故意未对齐
start = clock();
for (u32 i = 0; i < ITERATIONS; ++i) {
volatile int sum = 0;
for (u32 j = 0; j < 1000; ++j) {
sum += unaligned_array[j];
}
}
end = clock();
double unaligned_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("Aligned time: %.3f seconds\n", aligned_time);
printf("Unaligned time: %.3f seconds\n", unaligned_time);
printf("Speedup: %.2fx\n", unaligned_time / aligned_time);
free(unaligned_block);
}
// 典型结果(x86_64):
// Aligned time: 1.234 seconds
// Unaligned time: 3.156 seconds
// Speedup: 2.56x
SIMD 性能
#include <immintrin.h>
void simd_performance() {
const u32 SIZE = 1024;
// 对齐数据 - 使用 _mm_load_ps
alignas(16) float aligned_data[SIZE];
for (u32 i = 0; i < SIZE; ++i) {
aligned_data[i] = (float)i;
}
clock_t start = clock();
for (u32 iter = 0; iter < 1000000; ++iter) {
for (u32 i = 0; i < SIZE; i += 4) {
__m128 vec = _mm_load_ps(&aligned_data[i]); // 对齐加载
// 处理...
}
}
clock_t end = clock();
double aligned_simd_time = (double)(end - start) / CLOCKS_PER_SEC;
// 未对齐数据 - 使用 _mm_loadu_ps
float* unaligned_data = malloc(SIZE * sizeof(float) + 1);
float* data_ptr = (float*)((char*)unaligned_data + 1);
for (u32 i = 0; i < SIZE; ++i) {
data_ptr[i] = (float)i;
}
start = clock();
for (u32 iter = 0; iter < 1000000; ++iter) {
for (u32 i = 0; i < SIZE; i += 4) {
__m128 vec = _mm_loadu_ps(&data_ptr[i]); // 未对齐加载
// 处理...
}
}
end = clock();
double unaligned_simd_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("Aligned SIMD: %.3f seconds\n", aligned_simd_time);
printf("Unaligned SIMD: %.3f seconds\n", unaligned_simd_time);
printf("Speedup: %.2fx\n", unaligned_simd_time / aligned_simd_time);
free(unaligned_data);
}
// 典型结果:
// Aligned SIMD: 0.856 seconds
// Unaligned SIMD: 1.342 seconds
// Speedup: 1.57x
🎮 游戏引擎应用
1. 矩阵和向量对齐
// 4x4 矩阵 - 16 字节对齐用于 SSE
typedef struct mat4 {
alignas(16) f32 data[16];
} mat4;
// 向量 - 16 字节对齐
typedef struct vec4 {
alignas(16) f32 data[4];
} vec4;
// SIMD 优化的矩阵乘法
mat4 mat4_mul_simd(const mat4* m1, const mat4* m2) {
mat4 result;
// 假设数据已对齐,可以安全使用 _mm_load_ps
for (u32 i = 0; i < 4; ++i) {
__m128 row = _mm_load_ps(&m1->data[i * 4]);
for (u32 j = 0; j < 4; ++j) {
__m128 col = _mm_set_ps(
m2->data[j + 12],
m2->data[j + 8],
m2->data[j + 4],
m2->data[j]
);
__m128 mul = _mm_mul_ps(row, col);
// ...
}
}
return result;
}
2. 顶点缓冲区对齐
// 顶点数据 - 对齐以提高 GPU 传输效率
typedef struct vertex_3d {
vec3 position; // 12 字节
f32 padding1; // 4 字节填充
vec3 normal; // 12 字节
f32 padding2; // 4 字节填充
vec2 texcoord; // 8 字节
vec4 color; // 16 字节
vec4 tangent; // 16 字节
} vertex_3d __attribute__((aligned(16))); // 整体 16 字节对齐
// 顶点缓冲区分配
vertex_3d* vertices = kallocate_aligned(
vertex_count * sizeof(vertex_3d),
16,
MEMORY_TAG_RENDERER
);
3. 纹理数据对齐
// 纹理像素数据 - 行对齐
typedef struct texture {
u32 width;
u32 height;
u32 channels;
u32 row_alignment; // 通常是 4 或 8
// 像素数据指针(对齐)
u8* pixels;
} texture;
// 计算行跨度(stride)
u32 calculate_row_stride(u32 width, u32 channels, u32 alignment) {
u32 row_bytes = width * channels;
return ALIGN_UP(row_bytes, alignment);
}
// 分配纹理数据
void allocate_texture(texture* tex, u32 width, u32 height, u32 channels) {
tex->width = width;
tex->height = height;
tex->channels = channels;
tex->row_alignment = 8; // 8 字节对齐
u32 row_stride = calculate_row_stride(width, channels, tex->row_alignment);
u64 total_size = row_stride * height;
tex->pixels = kallocate_aligned(total_size, tex->row_alignment, MEMORY_TAG_TEXTURE);
}
4. 音频缓冲区对齐
// 音频样本 - 缓存行对齐以避免伪共享
typedef struct audio_buffer {
alignas(64) f32 left_channel[BUFFER_SIZE];
alignas(64) f32 right_channel[BUFFER_SIZE];
} audio_buffer;
// 每个通道在独立的缓存行,避免多线程争用
🛠️ 对齐工具和宏
C11/C++11 对齐支持
// C11 alignas
#include <stdalign.h>
alignas(16) float data[4];
// C++11 alignas
alignas(32) double matrix[16];
// 查询对齐
_Alignof(double); // 返回 8
alignof(double); // C++11
GCC/Clang 属性
// 结构体对齐
struct example {
int data[4];
} __attribute__((aligned(16)));
// 变量对齐
int array[100] __attribute__((aligned(64)));
// 函数对齐
void my_function() __attribute__((aligned(16)));
MSVC 特定
// __declspec(align)
__declspec(align(16)) float data[4];
// __declspec(align) 用于结构体
__declspec(align(32)) struct matrix {
float data[16];
};
跨平台对齐宏
// defines.h
#if defined(__GNUC__) || defined(__clang__)
#define KALIGN(x) __attribute__((aligned(x)))
#elif defined(_MSC_VER)
#define KALIGN(x) __declspec(align(x))
#else
#define KALIGN(x) alignas(x)
#endif
// 使用
KALIGN(16) float vec[4];
🚀 实践练习
练习 1:实现对齐的池分配器
typedef struct aligned_pool {
void* memory;
u64 block_size;
u16 alignment;
u32 block_count;
u32 free_blocks;
void* free_list;
} aligned_pool;
// TODO: 实现以下函数
b8 aligned_pool_create(aligned_pool* pool, u64 block_size,
u16 alignment, u32 block_count);
void* aligned_pool_allocate(aligned_pool* pool);
void aligned_pool_free(aligned_pool* pool, void* block);
void aligned_pool_destroy(aligned_pool* pool);
练习 2:SIMD 矩阵运算
使用对齐优化矩阵乘法:
// TODO: 实现 SSE/AVX 优化的矩阵乘法
mat4 mat4_multiply_optimized(const mat4* a, const mat4* b) {
mat4 result;
// 使用 SIMD 指令:
// 1. _mm_load_ps 加载对齐数据
// 2. _mm_mul_ps 向量乘法
// 3. _mm_add_ps 向量加法
// 4. _mm_store_ps 存储结果
return result;
}
练习 3:缓存友好的数据结构
设计避免伪共享的数据结构:
// TODO: 设计一个缓存友好的环形缓冲区
typedef struct cache_friendly_ringbuffer {
alignas(64) u32 head; // 生产者使用的缓存行
char padding1[60];
alignas(64) u32 tail; // 消费者使用的缓存行
char padding2[60];
alignas(64) void** buffer; // 数据缓冲区
u32 capacity;
} cache_friendly_ringbuffer;
❓ 常见问题
Q1: 为什么我的程序即使没有对齐也能运行?A: 取决于 CPU 架构:
x86/x64:
- 允许未对齐访问
- 但性能会降低(2-3 倍慢)
- 编译器通常会自动对齐
ARM/RISC:
- 某些指令要求对齐
- 未对齐访问会触发硬件异常
- 或者通过模拟实现(非常慢)
示例:
// x86_64
int* ptr = (int*)0x03; // 未对齐
int value = *ptr; // ✅ 可以工作,但慢
// ARM(某些型号)
int* ptr = (int*)0x03; // 未对齐
int value = *ptr; // ❌ 程序崩溃!
最佳实践:即使在 x86 上也应该对齐,以获得最佳性能并保证跨平台兼容性。
Q2: 如何检查内存是否正确对齐?A: 使用多种方法:
方法 1:编译时检查
#include <stdalign.h>
alignas(16) float data[4];
static_assert(alignof(data) == 16, "Data must be 16-byte aligned");
方法 2:运行时检查
#define IS_ALIGNED(ptr, alignment) \
((((u64)(ptr)) & ((alignment) - 1)) == 0)
float* ptr = kallocate_aligned(16, 16, MEMORY_TAG_UNKNOWN);
assert(IS_ALIGNED(ptr, 16)); // 检查 16 字节对齐
方法 3:调试工具
void check_alignment(void* ptr, u16 required_alignment) {
u64 addr = (u64)ptr;
u16 actual_alignment = 1;
// 找到实际对齐
while ((addr & 1) == 0) {
addr >>= 1;
actual_alignment <<= 1;
}
KDEBUG("Pointer %p:", ptr);
KDEBUG(" Required alignment: %u bytes", required_alignment);
KDEBUG(" Actual alignment: %u bytes", actual_alignment);
KDEBUG(" Properly aligned: %s",
actual_alignment >= required_alignment ? "YES" : "NO");
}
方法 4:使用 AddressSanitizer
# 编译时启用
gcc -fsanitize=address -g program.c
# 运行时会检测未对齐访问
Q3: 对齐会浪费多少内存?
A: 取决于数据结构和对齐要求:
示例 1:小结构体
// 未对齐版本
struct unaligned {
char a; // 1 字节
int b; // 4 字节
char c; // 1 字节
}; // 实际大小:12 字节(浪费 6 字节)
// 优化版本
struct optimized {
int b; // 4 字节
char a; // 1 字节
char c; // 1 字节
// 填充 2 字节
}; // 实际大小:8 字节(浪费 2 字节)
// 节省:4 字节 per 结构体(33% 减少)
示例 2:大数组
// 1000 个元素的数组
struct element {
char a;
int b;
};
// 未优化:每个 8 字节,总计 8000 字节
// 优化:每个 8 字节,总计 8000 字节(同样)
// 但是如果添加 char c:
// 未优化:每个 12 字节,总计 12000 字节
// 优化:每个 12 字节,总计 12000 字节
实践建议:
- 对于频繁使用的小结构体,优化布局
- 对于大数组,考虑使用结构体数组(SoA)而非数组结构体(AoS)
- 性能提升通常超过内存开销
A: 使用抽象层和宏:
// platform.h - 跨平台对齐抽象
// 对齐声明
#if defined(__GNUC__) || defined(__clang__)
#define KALIGN(x) __attribute__((aligned(x)))
#elif defined(_MSC_VER)
#define KALIGN(x) __declspec(align(x))
#else
#define KALIGN(x) _Alignas(x)
#endif
// 对齐分配
void* platform_allocate_aligned(u64 size, u16 alignment, b8 zero) {
#if defined(KPLATFORM_WINDOWS)
void* ptr = _aligned_malloc(size, alignment);
#elif defined(KPLATFORM_LINUX) || defined(KPLATFORM_APPLE)
void* ptr = NULL;
if (posix_memalign(&ptr, alignment, size) != 0) {
return NULL;
}
#else
#error "Unsupported platform for aligned allocation"
#endif
if (ptr && zero) {
memset(ptr, 0, size);
}
return ptr;
}
// 对齐释放
void platform_free_aligned(void* ptr, u16 alignment, b8 zero) {
#if defined(KPLATFORM_WINDOWS)
_aligned_free(ptr);
#elif defined(KPLATFORM_LINUX) || defined(KPLATFORM_APPLE)
free(ptr); // posix_memalign 分配的内存用 free 释放
#else
#error "Unsupported platform"
#endif
}
// 使用
KALIGN(16) float data[4];
float* ptr = platform_allocate_aligned(64, 16, true);
platform_free_aligned(ptr, 16, false);
Q5: 什么时候应该使用 64 字节对齐?
A: 64 字节对齐主要用于缓存行优化:
使用场景:
1. 避免伪共享
// 多线程计数器
struct thread_counters {
alignas(64) u64 thread1_count; // 独立缓存行
alignas(64) u64 thread2_count; // 独立缓存行
};
2. 性能关键的数据结构
// 频繁访问的查找表
alignas(64) u32 lookup_table[256];
3. 大型矩阵/数组
// 大矩阵的行对齐到缓存行
alignas(64) float matrix[1024][1024];
性能测试:
#define ITERATIONS 100000000
// 测试 1:伪共享
struct bad {
u64 counter1;
u64 counter2;
};
// 测试 2:缓存行对齐
struct good {
alignas(64) u64 counter1;
alignas(64) u64 counter2;
};
// 结果(典型值):
// 伪共享: 5.2 秒
// 对齐: 2.1 秒
// 提升: 2.48x
何时不需要:
- 单线程访问的数据
- 很少访问的数据
- 小型临时变量
📚 总结
内存对齐是游戏引擎性能优化的关键技术。本教程涵盖了:
✅ 关键要点
| 概念 | 要点 |
|---|---|
| 对齐定义 | 数据地址是特定值的倍数 |
| 性能影响 | 对齐访问比未对齐快 2-3 倍 |
| SIMD 要求 | SSE/AVX 指令通常要求 16/32 字节对齐 |
| 缓存行 | 64 字节对齐避免伪共享 |
| 实现 | kallocate_aligned 和 kfree_aligned |
🔑 对齐要求
| 数据类型 | 对齐 | 原因 |
|---|---|---|
char | 1 | 基本类型 |
int | 4 | 基本类型 |
double | 8 | 基本类型 |
__m128 | 16 | SSE 指令 |
__m256 | 32 | AVX 指令 |
| 缓存行 | 64 | 避免伪共享 |
📈 性能提升
对齐 vs 未对齐:
- 标量访问:2-3x 提升
- SIMD 访问:1.5-2x 提升
- 缓存行对齐:2-3x 提升(多线程)
🚀 下一步
我们已经完成了 Kohi 引擎教程系列的 50 篇教程,涵盖了从基础到高级的各个方面!
内存对齐 是性能优化的基石,正确使用可以显著提升游戏引擎的性能和可靠性!
📖 关注公众号

关注我,领取章节视频教程
📅 最后更新:2025-12-01
✍️ 作者:上手实验室
📧 联系:提交 Issue

1045

被折叠的 条评论
为什么被折叠?



