面试题答案
一键面试1. 缓存命中率与内存连续性基础
- 缓存命中率:CPU 从缓存中获取数据的次数与总数据获取次数的比率。缓存是高速存储区域,靠近 CPU,若数据能在缓存中找到(命中),则无需从较慢的内存获取,大大提高性能。
- 内存连续性:内存中数据按顺序紧密排列。连续内存访问效率高,因为现代 CPU 缓存通常以缓存行(cache line)为单位读取内存,连续内存可一次性加载多个数据到缓存。
2. 不同传参方式及性能优化分析
void func(int arr[M][N])
- 内存连续性:在 C++ 中,二维数组本质上是按行存储的一维数组。这种传参方式明确了数组的维度,编译器能更好地理解内存布局,在访问数组元素
arr[i][j]
时,可高效计算内存地址&arr[0][0] + i * N + j
,保证内存连续性访问。 - 缓存命中率:由于内存连续性好,访问数组元素时,同一缓存行可加载多个相邻元素,提高缓存命中率。例如,当访问
arr[i][0]
时,arr[i][1]
等相邻元素可能也被加载到缓存行,后续访问这些元素时缓存命中概率高。
- 内存连续性:在 C++ 中,二维数组本质上是按行存储的一维数组。这种传参方式明确了数组的维度,编译器能更好地理解内存布局,在访问数组元素
void func(int (*arr)[N])
- 内存连续性:此方式同样明确了第二维的大小
N
,编译器能有效计算元素内存地址,与void func(int arr[M][N])
类似,保证按行访问时的内存连续性。 - 缓存命中率:与前一种方式相近,因内存连续性好,缓存行能加载多个相邻元素,提高缓存命中率。
- 内存连续性:此方式同样明确了第二维的大小
3. 性能表现差异原因
- 两种方式在常规场景下差异不大:因为它们都清晰定义了数组第二维大小,编译器可高效处理内存布局和地址计算,在按行遍历二维数组等常规场景下,内存连续性和缓存命中率相近,性能差异不明显。
- 动态内存分配场景差异:若二维数组是通过动态内存分配创建(如
int **arr = new int*[M]; for(int i = 0; i < M; ++i) arr[i] = new int[N];
),此时使用void func(int (*arr)[N])
就不合适,因为动态分配的二维数组内存不连续(arr[i]
指向的内存块在堆上可能不相邻)。而若要传递动态分配且内存连续的二维数组(如int *arr = new int[M * N];
,通过arr[i * N + j]
访问元素),则可将其转换为int (*)[N]
形式传递,这种情况下,void func(int (*arr)[N])
可处理内存连续的动态数组,而void func(int arr[M][N])
要求静态数组,灵活性较差。但在静态数组场景下,两者性能相当。
综上所述,在处理静态二维数组传参时,void func(int arr[M][N])
和void func(int (*arr)[N])
在缓存命中率和内存连续性方面表现相近,性能差异不大;在动态内存分配场景下,需根据内存连续性和灵活性选择合适的传参方式。