三、内存层次结构
3.1 存储金字塔
┌─────────────────────────────────────────────────────────────────────────────┐
│ 存储层次 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 寄存器 容量: ~几百字节 延迟: ~1个时钟周期(0.3ns) 带宽: ~100GB/s │
│ ↑ │
│ L1缓存 容量: 32KB-64KB 延迟: ~4个时钟周期(1ns) 带宽: ~50GB/s │
│ ↑ │
│ L2缓存 容量: 256KB-1MB 延迟: ~12个时钟周期(3ns) 带宽: ~25GB/s │
│ ↑ │
│ L3缓存 容量: 8MB-32MB 延迟: ~40个时钟周期(10ns) 带宽: ~15GB/s │
│ ↑ │
│ 主存(DDR4) 容量: 16GB-256GB 延迟: ~200个时钟周期(80ns) 带宽: ~20GB/s │
│ ↑ │
│ SSD/NVMe 容量: 512GB-4TB 延迟: ~10万ns 带宽: ~3GB/s │
│ ↑ │
│ 机械硬盘 容量: 1TB-16TB 延迟: ~1000万ns 带宽: ~200MB/s │
│ │
│ 延迟差异:寄存器到主存延迟差200倍,主存到SSD差1000倍 │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 缓存原理与缓存行
/*
* 缓存行(Cache Line):CPU与内存交换数据的最小单位
* 典型大小:64字节
*
* 缓存映射方式:
* 1. 直接映射:每个内存块只能映射到固定的缓存行
* 2. 组相联:每个内存块可映射到一组缓存行(N路组相联)
* 3. 全相联:每个内存块可映射到任何缓存行
*/
// 查看缓存信息(Linux)
getconf -a | grep CACHE
// LEVEL1_ICACHE_SIZE: 32768
// LEVEL1_DCACHE_SIZE: 32768
// LEVEL2_CACHE_SIZE: 262144
// LEVEL3_CACHE_SIZE: 8388608
// 缓存友好的代码
// 坏例子:按列遍历(步长大,缓存命中率低)
int matrix[1024][1024];
int sum = 0;
for (int j = 0; j < 1024; j++) {
for (int i = 0; i < 1024; i++) {
sum += matrix[i][j]; // 跳跃访问
}
}
// 耗时:~10ms
// 好例子:按行遍历(步长1,缓存友好)
int sum = 0;
for (int i = 0; i < 1024; i++) {
for (int j = 0; j < 1024; j++) {
sum += matrix[i][j]; // 连续访问
}
}
// 耗时:~1ms(快10倍)
3.3 伪共享(False Sharing)问题
// Java中的伪共享示例
public class FalseSharingDemo {
// 问题代码:两个线程频繁修改的变量在同一个缓存行
static class Counter {
volatile long x; // 线程A修改
volatile long y; // 线程B修改
// x和y可能在同一个缓存行(64字节)
}
// 解决方案1:填充(Padding)
static class PaddedCounter {
volatile long x;
long p1, p2, p3, p4, p5, p6, p7; // 填充56字节
volatile long y;
}
// 解决方案2:@Contended(Java 8+)
// -XX:-RestrictContended
static class ContendedCounter {
@sun.misc.Contended
volatile long x;
@sun.misc.Contended
volatile long y;
}
}
// C/C++中的伪共享
// alignas(64) 确保变量在独立的缓存行
struct alignas(64) Counter {
std::atomic<long> value;
};
Counter counters[10]; // 每个Counter占用独立缓存行
3.4 内存一致性模型
/*
* 内存屏障(Memory Barrier)
*
* x86-64内存模型:强一致性(除了Store-Load需要屏障)
*/
// Linux内核内存屏障
#define smp_mb() asm volatile("mfence" ::: "memory") // 全屏障
#define smp_rmb() asm volatile("lfence" ::: "memory") // 读屏障
#define smp_wmb() asm volatile("sfence" ::: "memory") // 写屏障
// Java中的内存屏障
// Unsafe类提供的屏障方法
unsafe.loadFence(); // 读屏障
unsafe.storeFence(); // 写屏障
unsafe.fullFence(); // 全屏障
// volatile底层实现(x86-64)
// 汇编代码:lock addl $0x0,(%rsp)
// lock前缀保证可见性并禁止重排序
四、指令集与汇编
4.1 x86-64汇编基础
; x86-64 汇编示例(AT&T语法)
; 将两个整数相加的函数
; int add(int a, int b) { return a + b; }
; 汇编代码
add:
pushq %rbp ; 保存基址指针
movq %rsp, %rbp ; 设置栈帧
movl %edi, -4(%rbp) ; 第一个参数(a)存入栈
movl %esi, -8(%rbp) ; 第二个参数(b)存入栈
movl -4(%rbp), %edx ; a加载到edx
movl -8(%rbp), %eax ; b加载到eax
addl %edx, %eax ; eax = eax + edx
popq %rbp ; 恢复基址指针
retq ; 返回(eax是返回值)
; 简单版本(优化后)
add:
leal (%rdi,%rsi), %eax ; eax = edi + esi
retq
; 寄存器调用约定(System V AMD64 ABI):
; RDI: 第一个参数
; RSI: 第二个参数
; RDX: 第三个参数
; RCX: 第四个参数
; R8: 第五个参数
; R9: 第六个参数
; RAX: 返回值
4.2 C代码到汇编的映射
// C代码
int array[10];
for (int i = 0; i < 10; i++) {
array[i] = i * 2;
}
; 对应的汇编代码(简化)
movl $0, -4(%rbp) ; i = 0
jmp .L2 ; 跳转到条件判断
.L3:
movl -4(%rbp), %eax ; 加载i
leal (%rax,%rax), %edx ; edx = i * 2
movl -4(%rbp), %eax ; 加载i
cltq ; 扩展为64位
movl %edx, array(,%rax,4) ; array[i] = i*2
addl $1, -4(%rbp) ; i++
.L2:
cmpl $9, -4(%rbp) ; 比较i和9
jle .L3 ; i <= 9 则继续循环
4.3 在Java中调用汇编(JNI示例)
// Java代码
public class NativeAdd {
static {
System.loadLibrary("nativeadd");
}
// 声明native方法
public static native int add(int a, int b);
public static void main(String[] args) {
System.out.println(add(3, 5)); // 输出8
}
}
// C代码(nativeadd.c)
#include <jni.h>
#include "NativeAdd.h"
JNIEXPORT jint JNICALL Java_NativeAdd_add(JNIEnv *env, jclass cls, jint a, jint b) {
// 内联汇编
int result;
__asm__ volatile (
"movl %1, %%eax\n\t"
"addl %2, %%eax\n\t"
"movl %%eax, %0"
: "=r"(result) // 输出
: "r"(a), "r"(b) // 输入
: "%eax" // 破坏的寄存器
);
return result;
}