← 中文资源

简体中文版本

内存分配器设计

内存分配器——malloc 实现、运行时、arena——决定分配延迟、碎片与缓存局部性;把 glibc malloc 换成 jemalloc 或 mimalloc 能显著改变服务行为。

阅读英文原文

概念定义

程序以不可预测的尺寸与生命周期要内存,内核按大块给页。分配器是中间层,它的数据结构——尺寸类、空闲链表、arena——为程序创建的每个对象定下成本曲线。

工作机制

小请求映射到尺寸类并走每线程缓存获得无锁快路径;大请求直接 mmap。arena 把堆分片以约束锁争用,代价是 arena 间碎片。slab 分配器按固定形状雕刻并把空闲链表嵌在对象里。加固模式为可利用性防护加守卫页、隔离区与随机化。

使用场景

高线程数且分配密集的服务、碎片会累积的长寿命进程、解析不可信输入的安全敏感路径。切换前后要测 RSS 增长对存活字节的比值、以及分配延迟分布。

常见误区

“malloc 处处一样”不成立:ptmalloc、jemalloc、tcmalloc、mimalloc 在线程缓存、arena 数量与碎片行为上就是设计不同。碎片是“活分配之间的不可用空洞”,泄漏是“无界增长”——不同 bug 不同修法。每线程缓存持有分配统计看不见的内存,线程抖动会推高 RSS;arena 数与 NUMA 拓扑错配会加跨节点流量。换分配器会改变内存布局并唤醒潜伏 bug——运气好的双重释放变崩溃,那是伪装成事故的诊断价值。

代码示例

import os
from typing import BinaryIO, Iterator, Tuple


class ChunkedReadError(ValueError):
    """Raised when chunk parameters are invalid."""


def iter_chunks(path: str, chunk_size: int = 65536, offset: int = 0) -> Iterator[bytes]:
    """Yield the file at path in fixed-size byte chunks starting at offset."""
    if chunk_size < 1:
        raise ChunkedReadError("chunk_size must be >= 1")
    if offset < 0:
        raise ChunkedReadError("offset must be >= 0")
    if not os.path.isfile(path):
        raise FileNotFoundError(f"file not found: {path}")
    if offset > os.path.getsize(path):
        raise ChunkedReadError("offset is beyond the end of the file")

    with open(path, "rb") as handle:
        handle.seek(offset)
        while True:
            chunk = handle.read(chunk_size)
            if not chunk:
                return
            yield chunk


def read_all_chunks(path: str, chunk_size: int = 65536) -> Tuple[bytes, int]:
    """Return the whole file content and the total number of bytes read."""
    parts = []
    total = 0
    for chunk in iter_chunks(path, chunk_size=chunk_size):
        parts.append(chunk)
        total += len(chunk)
    return b"".join(parts), total
export class CircularBuffer<T> {
  private readonly items: Array<T | undefined>;
  private head = 0;
  private count = 0;

  public constructor(public readonly capacity: number) {
    if (!Number.isInteger(capacity) || capacity < 1) {
      throw new RangeError("capacity must be a positive integer");
    }
    this.items = new Array<T | undefined>(capacity);
  }

  public get size(): number {
    return this.count;
  }

  public get full(): boolean {
    return this.count === this.capacity;
  }

  public push(value: T): T | undefined {
    const evicted =
      this.count === this.capacity ? this.items[this.head] : undefined;
    this.items[this.head] = value;
    this.head = (this.head + 1) % this.capacity;
    if (this.count < this.capacity) this.count += 1;
    return evicted;
  }

  public at(index: number): T {
    if (!Number.isInteger(index) || index < 0 || index >= this.count) {
      throw new RangeError(`index out of range: ${index}`);
    }
    const start =
      (this.head - this.count + this.capacity) % this.capacity;
    return this.items[(start + index) % this.capacity] as T;
  }

  public toArray(): T[] {
    const output: T[] = [];
    for (let index = 0; index < this.count; index += 1) {
      output.push(this.at(index));
    }
    return output;
  }

  public clear(): void {
    this.items.fill(undefined);
    this.head = 0;
    this.count = 0;
  }

  public *[Symbol.iterator](): IterableIterator<T> {
    for (let index = 0; index < this.count; index += 1) {
      yield this.at(index);
    }
  }
}

const buffer = new CircularBuffer<number>(3);
console.log(buffer.push(1), buffer.push(2), buffer.push(3));
console.log(buffer.push(4));
console.log(buffer.toArray(), buffer.at(0), buffer.at(2), buffer.full);
console.log([...buffer]);

buffer.clear();
console.log(buffer.size, buffer.toArray());
try {
  buffer.at(0);
} catch (error) {
  console.log(error instanceof Error ? error.message : String(error));
}