redis zmalloc

ddatsh

dev #redis redis

设计重要变化

zmalloc.h log from 2009/3/22

thread safe memory counter

2010/1/15 1.3

zcalloc

2010/07/25 2.2

memory fragmentation

2010/9/22 2.2

jemalloc

2011/6/2 2.6 forward-ported changes in zmalloc.c/h to support jemalloc build

后又 backported in 2.4

zmalloc_get_private_dirty

估算出子进程在持久化过程中实际消耗的物理内存大小

active memory defragmentation

2016/12/30 5.0

purge jemalloc after flush

2019/5/30 6.0

madvise(MADV_DONTNEED)

2021/08/05 7.0 Use madvise(MADV_DONTNEED) to release memory to reduce COW (#8974)…

extend_to_usable

2023/4/11 7.2 Use dummy allocator to make accesses defined as per standard (#11982)

解决 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 时报错

gcc Function Attributes alloc_size

static redisAtomic size_t used_memory = 0;

#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))

#define atomicIncr(var,count) atomic_fetch_add_explicit(&var,(count),memory_order_relaxed)

#define atomic_fetch_add_explicit(PTR, VAL, MO) 			\
			  __atomic_fetch_add ((PTR), (VAL), (MO))

源码历程

first commit

zmalloc.h

#ifndef _ZMALLOC_H
#define _ZMALLOC_H

void *zmalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void *zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);

#endif /* _ZMALLOC_H */

zmalloc.c

#include <stdlib.h>
#include <string.h>

static size_t used_memory = 0;

void *zmalloc(size_t size) {
    void *ptr = malloc(size+sizeof(size_t));

    *((size_t*)ptr) = size;
    used_memory += size+sizeof(size_t);
    return ptr+sizeof(size_t);
}

void *zrealloc(void *ptr, size_t size) {
    void *realptr;
    size_t oldsize;
    void *newptr;

    if (ptr == NULL) return zmalloc(size);
    realptr = ptr-sizeof(size_t);
    oldsize = *((size_t*)realptr);
    newptr = realloc(realptr,size+sizeof(size_t));
    if (!newptr) return NULL;

    *((size_t*)newptr) = size;
    used_memory -= oldsize;
    used_memory += size;
    return newptr+sizeof(size_t);
}

void zfree(void *ptr) {
    void *realptr;
    size_t oldsize;

    if (ptr == NULL) return;
    realptr = ptr-sizeof(size_t);
    oldsize = *((size_t*)realptr);
    used_memory -= oldsize+sizeof(size_t);
    free(realptr);
}

char *zstrdup(const char *s) {
    size_t l = strlen(s)+1;
    char *p = zmalloc(l);

    memcpy(p,s,l);
    return p;
}

size_t zmalloc_used_memory(void) {
    return used_memory;
}

ANSI-C compatibility changes

1

问 kimi redis zmalloc 里类似

void *zmalloc(size_t size) {
    void *ptr = malloc(size);  
    return (char*)ptr; 
} 

为什么返回转换成 (char*) 更符合 ansi c

  1. 类型安全char* 是一个具体的数据类型,可以被解引用并用于访问内存中的数据。在 ANSI C 中,char* 通常用于表示字符串,但也可以用于表示任意字节的内存块
  2. 兼容性:在某些情况下,可能需要将内存块作为字符串来处理,比如使用标准库函数如 strlenstrcpy 等。将 void* 转换为 char* 可以提供这种兼容性
  3. 编码风格:在某些编码风格或项目中,可能习惯于使用 char* 来表示内存块,这样可以在不改变其他代码的情况下重用现有的代码
  4. 避免警告:在某些编译器或严格的编译选项下,直接使用 void* 可能会引起警告,因为编译器无法确定内存块的大小。转换为 char* 可以消除这种警告
  5. 方便操作char* 可以方便地进行字节操作,比如通过索引来访问和修改内存块中的特定字节

实际使用中,应该根据上下文来决定是否需要这种转换。如果只是简单地分配内存,而不需要进行字节操作或与字符串相关的操作,那么这种转换可能是不必要的

zmalloc fix, return NULL or real malloc failure

2

macosx malloc_size

macosx specific zmalloc.c, uses malloc_size function in order to avoid to waste memory and time to put an additional header

config.h

#ifdef __APPLE__
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE
#define redis_malloc_size(p) malloc_size(p)
#endif

3

oom check

著名的 PREFIX_SIZE

zmalloc Solaris fixes

thread safe counter

void zmalloc_enable_thread_safeness(void);
static size_t used_memory = 0;
static int zmalloc_thread_safe = 0;
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;
}

redis.c 里

static void vmInit(void) {
	...
    if (server.vm_max_threads != 0)
        zmalloc_enable_thread_safeness(); /* we need thread safe zmalloc() */

vm 之后就废弃了

内存对齐

zcalloc

合适的场景使用时,calloc 可能 malloc+memset 更高效

commit log 里有段详述

内存碎片率

memory fragmentation ratio in INFO output

/* Fragmentation = RSS / allocated-bytes */
float zmalloc_get_fragmentation_ratio(void) {
#ifdef HAVE_PROCFS

   /proc/%d/stat

mac 内存碎片率

// linux
#if defined(HAVE_PROCFS)

#elif defined(HAVE_TASKINFO)
float zmalloc_get_fragmentation_ratio(void) {
    task_t task = MACH_PORT_NULL;
    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

    if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
        return 0;
    task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);

    return (float)t_info.resident_size/zmalloc_used_memory();
}

get rss,fragmentation拆分

float zmalloc_get_fragmentation_ratio(void) {
    return (float)zmalloc_get_rss()/zmalloc_used_memory();
}

开始引入jemalloc

since 2.6

buildin atomic

zmalloc_get_private_dirty

3.0

#if defined(HAVE_PROCFS)
size_t zmalloc_get_private_dirty(void) {
    char line[1024];
    size_t pd = 0;
    FILE *fp = fopen("/proc/self/smaps","r");

    if (!fp) return 0;
    while(fgets(line,sizeof(line),fp) != NULL) {
        if (strncmp(line,"Private_Dirty:",14) == 0) {
            char *p = strchr(line,'k');
            if (p) {
                *p = '\0';
                pd += strtol(line+14,NULL,10) * 1024;
            }
        }
    }
    fclose(fp);
    return pd;
}
#else
size_t zmalloc_get_private_dirty(void) {
    return 0;
}
#endif

cache RSS in serverCron()

3.2

C11 __atomic

__sync_add_and_fetch
__sync_sub_and_fetch

to

__atomic_add_fetch
__atomic_sub_fetch

active memory defragmentation

void zfree_no_tcache(void *ptr);
void *zmalloc_no_tcache(size_t size);