git pull 发现 zmalloc.c更新了

写拆分,读累加

Avoid used_memory contention when update from multiple threads

commit log 里 The solution介绍,都不用看代码改了什么,作为资深javaer ,老八股之jdk8 LongAdder 就条件反射出来了,会心一笑

LongAdder 的基本思路就是分散热点,将 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回

相似之处

核心思想相同

  • Redis的实现:将全局的used_memory变量分解为多个线程本地计数器

  • Java LongAdder:将全局的long计数器分解为多个Cell数组

解决相同的问题

  • 问题:多线程同时更新同一个计数器时的高竞争

  • 解决方案:使用分片计数(sharded counting)来减少锁竞争

具体实现对比

Redis

// 每个线程有自己的内存计数器
typedef struct used_memory_entry {
    redisAtomic long long used_memory;  // 原子long long
    char padding[CACHE_LINE_SIZE - sizeof(long long)]; // 缓存行填充
} used_memory_entry;

static used_memory_entry used_memory[MAX_THREADS]; // 16个线程的计数器数组

Java LongAdder

// 每个线程有自己的Cell
static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
}
Cell[] cells; // 动态增长的Cell数组

线程分配策略

  • Redis:使用my_thread_index &= THREAD_MASK(位运算取模)

  • Java LongAdder:使用ThreadLocalRandom.getProbe()哈希分配

内存布局优化

  • Redis:使用CACHE_LINE_SIZE填充避免伪共享

  • Java LongAdder:使用@sun.misc.Contended注解避免伪共享

聚合计算

  • Redis:遍历所有活跃线程的计数器并求和

  • Java LongAdder:遍历所有Cell并求和

主要差异

  • 动态性:LongAdder的Cell数组是动态增长的,而Redis固定16个线程

  • 内存管理:Redis专门针对内存分配统计,LongAdder是通用计数器