malloc は実際になんの仕事をしているのでしょうか?

2026/11/09 20:31

malloc は実際になんの仕事をしているのでしょうか?

RSS: https://news.ycombinator.com/rss

要約▶

Japanese Translation:

GNU libc の核心的な優位点は、高度なメモリ割り当て管理にあり、これはオペレーティングシステムカーネルとの高価な相互作用を大幅に削減します。bump allocator などの単純な実装は、各リクエストに対して

sbrk
などの高価なシステムコールをトリガーするのに対し、glibc は内部で帳簿処理を行い、操作をバッチ化するとともにスレッドローカルキャッシュ(
tcache
)およびバインを利用します。この戦略により、アプリケーションはプログラムブレークから数ギバイトもの仮想アドレス空間を効率的に扱いつつ、デマンドペーシングを通じて物理 RAM を最小限に消費できます。さらに、glibc は
mmap
を使用して大規模なブロックを独立したマップとして割り当て、小規模な割り当ては 8 バイトの最適化されたチャンクヘッダーで管理します。これらの技術により、malloc の実行速度は直接システムコールを実装した場合と比較して約 22 倍向上し、厳格なメモリ制約下でもアプリケーションの起動加速および高スループット性能を実現可能となっています。

本文

メモリ管理とアロケータの仕組み:glibc から学ぶ最適化の道筋

1.
malloc
の魔法と実装の背景

malloc は極めてシンプルで使いやすいインターフェースを提供します。

  • 特定のサイズのメモリを要求するだけで、連続したバイト列が生成され、
    void*
    として返されます
    。
  • 使用終了時は、同じポインタを
    free()
    に渡すことで、メモリは「虚空(ether)」へ消滅します。

システムコールの overhead

ユーザー空間のプログラムから

malloc
を呼び出すと、実際には以下のプロセスを経ています。

  • システムコール(syscall)を通じてカーネルにメモリ割り当てを要求する。
  • 多数の小さなブロックを割り当てる試みを繰り返すと、システムコールが高コストになる(例:空の syscall は約 575 ns かかる)。

そのため、バックグラウンドで効率的なメモリの管理が行われます。GNU libc (glibc) における具体的な仕組みは以下のコード例で見られます。

static void *ptrs[1000];

int main(void) {
    for (int i = 0; i < 1000; i++) {
        ptrs[i] = malloc(16);
    }
}

strace
で確認する最適化

このループ全体で、ヒープ拡張を行う

brk
システムコールはわずか 2 回しか実行されていません。

  • 1 回目: プログラムブレーク(プロセスのヒープ領域の終了アドレス)を問い合わせる。
  • 2 回目: ヒープ領域を拡張させる(例:132 KiB)。

残りの 998 回の割り当ては、既に割り当てられたメモリ領域から確保されます。

$ strace -e trace=brk,mmap,munmap,write \
    ./malloc1000 > /dev/null
...
brk(NULL)              = 0x59faca231000
brk(0x59faca252000)    = 0x59faca252000
+++ exited with 0 +++

補足:

brk
と
sbrk
の関係

  • brk
    : 指定アドレスでプログラムブレークを設定するシステムコール。
  • sbrk
    : libc のラッパー関数で、増分(Delta)を受け取りプログラムブレークを増加させる。

2. プロセスのアドレス空間とメモリ領域

実行中のプロセスには独自のアドレス空間があり、以下の領域に分かれています。

領域名説明例
text実行可能コード用
main
関数
data初期化済みのグローバル変数
initialised
BSS未初期化のグローバル変数
uninitialised
heapヒープ領域(動的確保)
malloc
stackスタック領域ローカル変数
#define SHOW(label, p) \
    printf("%-22s %p\n", label, (void *)(p))

SHOW("main (text)", main);
SHOW("initialised (data)", &initialised);
SHOW("uninitialised (bss)", &uninitialised);
SHOW("malloc(16) (heap)", malloc(16));
SHOW("program break", sbrk(0));
SHOW("printf (libc)", printf);
SHOW("local (stack)", &local);

アドレス配置の特徴

  • アドレスは、バイナリコード領域からスタック領域に向かって上昇しています。
  • malloc
    でヒープを拡張すると、ブレークアドレス(program break)も上昇します。
  • 未マップ領域: ブレークと libc の間で数百テラバイトのギャップが存在する場合でも、物理メモリのコストは発生しません(マッピングされていないため)。

3. バンプアロケータ(Bump Allocator)とその限界

プログラムブレークを直接操作できるため、独自の小型アロケータを作成できます。これを「バンプアロケータ」と呼びます。

  • 仕組み: 各呼び出しで利用可能なメモリの先端を進めるだけ。
  • 欠点: 各リクエストごとにシステムコールが発生する(高コスト)。
  • 解放機能: 解放されたメモリの再利用手段を持っていない。

システムコールコストの比較

100 万回呼び出した際の平均時間を測定しました。

関数コスト (ns)解説
syscall(SYS_getppid)
575.2 ns空の syscall の基準
sbrk(16)
610.1 nsシステムコールによる割り当て
malloc(16)
26.9 nsglibc が内部で最適化しているため高速

結論:

malloc
はシステムコールを呼び出して大きなブロックを取得し、ユーザー空間内で細かく分割する方が、個別に
sbrk
を呼ぶよりも遥かに高速です。

メモリ断片化のリスク

バンプアロケータは解放したメモリの再利用を行いません。そのため、解放されたメモリ(例:

b
)があっても、その先の領域が埋まっていると、新しい割り当て(例:
d
)はその先頭に戻らず、連続したブロック確保ができなくなります。


4. チャンクとヘッダー

glibc の

malloc
は、要求する量より多くのメモリを割り当てます。これにより サイズヘッダー を持つことが可能になり、
free()
でサイズ情報を渡す必要がなくなります。

ヘッダー構造

  • チャンクサイズは常に 16 の倍数(最低 32 バイト)。
  • ヘッダーにはフラグ(Arena, Mapping, Pointer など)が格納されます。
  • メリット: ポインタが 16 バイト整列 を保たれ、あらゆる C の型に対応します。
要求サイズ割り当てチャンクサイズ
1 Byte32 Bytes
24 Bytes32 Bytes
25 Bytes48 Bytes
1000 Bytes1008 Bytes

フリーリスト(Free List)の仕組み

  • 解放時: チャンクはフリーリストに入れられます。
  • 再割り当て時: フリーリストから適合するチャンクを探し、大きくて不要な部分は再分割します。
  • 統合 (Coalescing): 隣接するフリーチャンクを結合して大きなブロックを作ります。
    • glibc はサイズの直後に次のチャンクへのポインタを格納し、ユーザーデータを再利用しています。

簡易的なアロケータでも同じ構造を持たせると、以下のようになります。

typedef struct chunk {
    size_t size;
    struct chunk *next;
} chunk_t;

5. 分割と統合(Splitting and Coalescing)の改善

より実用的なアロケータにするために、以下を導入します。

  • 丸め上げ: 要求量 + ヘッダーを 16 バイト倍数に調整。
  • 分割 (Splitting): 適当なチャンクを取り出し、余りをフリーリストに戻す。
  • 統合 (Coalescing): 隣接するフリーチャンクを結合して再利用性を高める。

my_malloc
と
my_free
の実装例

void *my_malloc(size_t size) {
    // フリーリストを検索またはヒープ拡張
}

void my_free(void *ptr) {
    // ヘッダーからチャンクを取得し、隣接フリーチャンクと統合
    // trim() を呼んで不要な領域を解放
}

動作確認

テストでは、

a
,
b
,
c
を割り当てて
a
,
b
を解放すると、これらが統合され、
d
を割り当てる際にその統合ブロックから分割されます。

ステップ状態free_list の変化
割り当て (a,b,c)分散配置
[free chunk]
my_free(a)
解放
[a], [bc...]
my_free(b)
統合 (a+b)
[ab], [c...]
d = malloc(50)
分割・割り当て
[ab の一部], [d の残りの分]

6. デマンドページング(Demand Paging)

my_free()
はメモリの再利用を行いますが、カーネルには物理メモリを返しません。

  • カーネルはアドレス空間を 4 KiB ページ単位で管理しています。
  • アドレス空間がマップされていても、書き込まれていないページは物理 RAM に割り当てられていません(空き状態)。
  • 仮想ページの最初の書き込み時に初めて物理メモリにマップされます(ページフォールト)。

検証プログラム

1 GiB のメモリを確保し、各ページに 1 バイト書き込むことで、デマンドページングの動作を確認しました。

状況仮想サイズRSS (レジデント)ページフォールト数
sbrk
の直後
> 1 GiB1308 KiB3 (初期化用)
256 MiB 書き込み後> 1 GiB263 MiB~6.5 万回

注意:

my_free()
で解放しても、そのページが書き込まれていれば RSS は維持されます。物理メモリのコストは削減されません。


7. メモリの返却(Giving Memory Back)

カーネルにメモリを返すには、ブレークを下げる必要があります。新しい関数

trim()
を導入します。

  • ヒープのトップにあるフリーチャンクが閾値(例:128 KiB)を超えると、余計な領域を
    sbrk(-N)
    で解放します。
  • ただし、解放しすぎると再び割り当て時にシステムコールが増えるため、**パディング(TOP_PAD)**を残して調整します。

断片化の問題

ヒープのトップしか返却できません。ライブなチャンクがある限り、その以下のフリーチャンクは物理メモリとして維持されます(レジデント)。

  • 長い実行により、ライブチャンクの間に多くのフリーギャップが生じます(断片化)。
  • これらはレジデント状態のため、RSS を増加させ、メモリ制限でプロセスを終了させる原因になります。
状態ブレーク位置RSS (物理使用)
解放後直後(トップに空きあり)トップから下がっている高い(解放されていない)
trim()
が実行される場合
トップまで戻す低下(物理メモリの削減)

8. 大規模ブロック用の
mmap

128 KiB 以上の大型ブロックを確保する際、

sbrk
よりも
mmap()
を使用するのが効率的です。

mmap
の利点

  • 即時解放:
    munmap()
    を呼び出すと、カーネルに即座に戻ります(隣接領域とは独立して)。
  • ゼロ初期化: 物理メモリへのマップはデマンドページングにより行われるため、初期コストが低い。
  • glibc の活用: glibc は大規模な割り当てやスレッド拡張時に、メインヒープのロック争いを避けるために
    mmap
    で別領域(アリーナ)を作成します。

改善後のアロケータ (
my-mmap-4
)

// 大きなブロックは mmap を使用
static void *map_chunk(size_t need) { ... }

// my_free は mapp ed チャンクを検知して munmap する
void my_free(void *ptr) {
    if (mapped_flag) {
        munmap(ptr); // 即座に物理メモリ解放
        return;
    }
    insert_free(ptr);
    trim();
}

パフォーマンス比較結果

アロケータRSS (解放後)ステップあたりの時間評価
my-bump-1
(最適化なし)
1425 MiB1.72 µsメモリ返却なし
my-freelist-2
17.8 MiBglibc より65 倍遅いリスト検索のコスト
my-trim-3
0.188 MiBglibc より142 倍遅い統合時の追加コスト
my-mmap-4
11.2 MiBglibc と同等大規模ブロックは mmap で最適化

結論:

malloc
はシステムコールを避けるために、大きなブロックをユーザー空間で分割・再結合する工夫をしています。また、glibc の
tcache
(スレッドローカルキャッシュ)や
mmap
しきい値の調整により、さらに高速化・低メモリ化が可能です。


9. リンクと参照

この記事を深く理解するために、以下のリソースを確認してください。

コードの再現性

  • 各プログラムのスニペットは再現可能にリンクされています。
  • チャンク、バイン(bin)、アリーナ、
    tcache
    について詳しく知りたい場合は、関連記事をご覧ください。

マニュアルページ

標準ライブラリの関数についての詳細は以下の man page を参照してください。

同じ日のほかのニュース

一覧に戻る →

2026/11/10 4:58

USPS が公式の郵便番号境界多角形を提供開始

## Japanese Translation: 米国郵便公社(USPS)が、実時間の配信住所および位置情報データを基に正確な ZIP コード境界を定義する新たな全国マップを発表しました。これは、人口統計情報や位置情報を整理するための不可欠な「地上の真実(ground truth)」を確立します。歴史的に配送ロジスティクスをルーティングするために主に配送場所の集約手段として用いられてきた ZIP コードとは異なり、これらの更新されたポリゴンには新規または変更された配信場所における実際の変動が反映されており、保険、不動産、マッピング、郵送、マーケティング、ナビゲーション、物流、輸送など、重要な業界に対してより優れた精度を提供します。ユーザーは USPS® ZIP コード境界 - 内部(Internal)を活用することで、更新されたポリゴンを自社のシステムに直接組み込むことができ、これを単なる共同機能として採用することも、既存の ZIP コードデータを修正することも可能です。データの最新性を確保するため、USPS は主要データセット(エンタープライズティアおよびパブリケーションティアを含む)を四半期ごとの 4 回の刷新を含め、3 ヶ月到月に更新しています。限定アクセスバージョンも特定の制約の下で配布されますが、同じくこの更新サイクルを共有します。郵送、マーケティング、輸送分野の企業は、この高解像度なデータを活用して運用を最適化でき、様々なセクターにおける人口統計情報の整理と運用作業効率を著しく向上させることができます。

2026/11/10 6:56

TPU

## Japanese Translation: Google は、Google Cloud Next で発表された 4 つの新しいシステム(TPU 8t、8i、8tx、および 8i Max)によって、第 8 世代 TPU ポートフォリオの開発を加速させました。これらのアップグレードは、最多 2.7 倍の高速なトレーニングと、大幅に向上した推論における価格パフォーマンスを実現し、ハードウェアレベルのセキュリティ(AMD EPYC ホストを通じたもの)および大規模なコンテキストスケーリングに対する緊急のニーズに対応します。エコシステムは TorchTPU、vLLM、SGLang などの PyTorch フレームワークとネイティブに統合されており、開発者がロジックを書き直すことなくコードを実行できるようにしています。主要な革新には、メモリ階層を統一しレイテンシを 90% 以上削減する TPU Sync と、アイドル中のセッションを自動的に停止してコストを削減する Agent Compute Engine が含まれます。さらに、GKE 上の Cloud Pathways を通じた動的スライシングによるデプロイの簡素化など、追加の進歩があり、ジョブ起動時間を最大 5 倍削減します。今後のアップデートには、インフラストラクチャ管理およびカーネルチューニングを自動化するためのオープンソース TPE アジェントスイートが含まれる予定です。

2026/11/10 5:04

人工分析(Artificial Analysis)にて Claude Fable 5.2 が最高得点の 65 を獲得、過去最高を更新

## Japanese Translation: 主な革新点は、API アクセスに依存せずに特定の代理タスクにおける高度な AI モデルを評価するように設計された固有のベンチマークスイート「人工分析知能インデックス v5.0.1」です。正確性とパフォーマンスは、9 つの異なる評価を通じて検証されています:AA-Briefcase v1.1(知識作業向け)、AutomationBench-AA、Terminal-Bench 4.0、FrontierCode 2.0、GDP.pdf、HLE-Diamond、CritPt v2(Hard Subset)、AA-Omniscience v1.1、および Terminal-Bench-Science 0.1。これらの評価は、代理による科学的研究ワークフロー、法律代理作業、医療向け長文脈推論、そして構造化された言語といった特定のカパビリティを対象としています。 AA-Briefcase Elo は、rubric 通過率、分析品質 Elo、およびプレゼンテーション Elo(0 でクランプ)を統合し、AA-Omniscience v1.1 は知識の信頼性とハルシネーションを測定し、-100 から 100 の範囲で Net Score を算出します。ここで、0 は正しい答えと間違いなりの答えが同等であることを示し、正しい答えには報酬を与えつつ、拒否に対してはペナルティを課さないハルシネーションのみを処罰します。パフォーマンスは、タスクあたりの所要時間(分)、出力速度(トークン/秒)、および最初の回答トークンまでの時間(秒)で測定されます。 コストは、入力トークン、キャッシュヒット/ライト、推論、および回答トークンの価格をタスク数で割った加重平均(USD)として計算され、キャッシュヒット、入力、および出力トークンに対して異なる価格体系が適用されます。キャッシュヒットの価格には大幅な割引が付与されています。運営効率は、ベンチマークを自動的に 6 時間ごりに実行することによって維持され、非推奨モデルはリソース節約のため 18 時間周期で動作します。モデルの重みの入手可能性は表示されており、「商用利用制限付き」または「非商用」というラベルがライセンス条件に基づいて付与されます。これにより、エコシステム内での責任ある使用が確保されます。

malloc は実際になんの仕事をしているのでしょうか? | そっか~ニュース