**Zig における静的割り当て**

Zig のコンパイル時メモリ管理を使えば、実行時ではなくコンパイル時にストレージを確保できます。データ構造のサイズが事前に分かっている場合やヒープ割り当てを避けたいときに便利です。

### 重要概念

- **コンパイル時定数**  
  `const` や `comptime` の値を使い、コンパイラがコンパイル中に評価できるサイズを記述します。

- **固定長配列**  
  リテラルサイズで配列を宣言します。  
  ```zig
  const buf = [_]u8{0} ** 128;   // 128 バイト、すべてゼロ初期化
  ```

- **静的フィールドを持つ構造体**  
  固定長配列やその他コンパイル時に決まる型を含む構造体を定義します。

### 例

```zig
const std = @import("std");

// 静的サイズのバッファを持つ構造体
pub const Message = struct {
    id: u32,
    payload: [256]u8,          // 256 バイト、コンパイル時に確保
};

// 静的割り当てを使う関数
fn process(msg: *Message) void {
    // ヒープ割り当ては不要;msg はスタック上またはグローバルに存在
    std.debug.print("ID: {d}\n", .{msg.id});
}

pub fn main() !void {
    var msg = Message{
        .id = 42,
        .payload = [_]u8{0} ** 256, // すべてのバイトをゼロで初期化
    };
    process(&msg);
}
```

### 利点

- **決定的なメモリ使用量** – サイズはコンパイル時に分かる  
- **実行時割り当てオーバーヘッドがゼロ** – ヒープアロケータ呼び出しなし  
- **安全性** – コンパイラが境界と寿命を検証できる  

### 使うべき場面

- 固定長バッファ(例:ネットワークパケット、ファイルヘッダー)  
- 短時間しか存続しない小規模補助データ構造  
- 性能や決定的な動作が重要な状況  

---

コンパイル時定数・固定配列・構造体定義を活用することで、Zig は最小限のボイラープレートで最大の安全性を保ちつつメモリを静的に割り当てることができます。

2025/12/30 1:07

**Zig における静的割り当て** Zig のコンパイル時メモリ管理を使えば、実行時ではなくコンパイル時にストレージを確保できます。データ構造のサイズが事前に分かっている場合やヒープ割り当てを避けたいときに便利です。 ### 重要概念 - **コンパイル時定数** `const` や `comptime` の値を使い、コンパイラがコンパイル中に評価できるサイズを記述します。 - **固定長配列** リテラルサイズで配列を宣言します。 ```zig const buf = [_]u8{0} ** 128; // 128 バイト、すべてゼロ初期化 ``` - **静的フィールドを持つ構造体** 固定長配列やその他コンパイル時に決まる型を含む構造体を定義します。 ### 例 ```zig const std = @import("std"); // 静的サイズのバッファを持つ構造体 pub const Message = struct { id: u32, payload: [256]u8, // 256 バイト、コンパイル時に確保 }; // 静的割り当てを使う関数 fn process(msg: *Message) void { // ヒープ割り当ては不要;msg はスタック上またはグローバルに存在 std.debug.print("ID: {d}\n", .{msg.id}); } pub fn main() !void { var msg = Message{ .id = 42, .payload = [_]u8{0} ** 256, // すべてのバイトをゼロで初期化 }; process(&msg); } ``` ### 利点 - **決定的なメモリ使用量** – サイズはコンパイル時に分かる - **実行時割り当てオーバーヘッドがゼロ** – ヒープアロケータ呼び出しなし - **安全性** – コンパイラが境界と寿命を検証できる ### 使うべき場面 - 固定長バッファ(例:ネットワークパケット、ファイルヘッダー) - 短時間しか存続しない小規模補助データ構造 - 性能や決定的な動作が重要な状況 --- コンパイル時定数・固定配列・構造体定義を活用することで、Zig は最小限のボイラープレートで最大の安全性を保ちつつメモリを静的に割り当てることができます。

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

要約

Japanese Translation:

概要:
このプロジェクトは、Zigで書かれた軽量Redis互換のキー/バリューサーバー「kv」を構築し、最小限のコマンドセットで本番環境に適した設計を目指しています。コアデザインでは起動時にすべてのメモリを確保することで、実行中にダイナミックヒープを使用せず、レイテンシスパイクやユース・アフター・フリー(use‑after‑free)バグを回避します。接続は

io_uring
で非同期に処理され、システムは3つのプール(Connection、受信バッファプール、送信バッファプール)を事前確保し、デフォルトでは約1000件までの同時接続数をサポートします。各接続は設定パラメータから派生した固定サイズの受信/送信バッファを使用します。
コマンド解析はRedisのRESPプロトコルのサブセットに従い、Zigの
std.heap.FixedBufferAllocator
を用いてゼロコピーで解析し、各リクエスト後にアロケータをリセットします。バッファサイズは
list_length_max
val_size_max
に依存します。
ストレージは未管理型の
StringHashMapUnmanaged(Value)
を使用し、初期化時に
ensureTotalCapacity
で容量を確保します。キーと値は共有
ByteArrayPool
に格納され、マップはポインタのみを保持します。削除操作では墓石(tombstone)が残り、墓石数が増えると再ハッシュが必要になる場合があります。
設定構造体(
Config
)は
connections_max
key_count
key_size_max
val_size_max
list_length_max
などのフィールドを公開し、派生アロケーションで接続ごとのバッファサイズを決定します。デフォルト設定(総計約748 MB、2048エントリ)では
val_size_max
または
list_length_max
を倍増すると、割り当て量が約2.8 GBに上昇する可能性があります。
今後の作業としては、カスタム静的コンテキストマップ実装の改善、より良いメモリ利用を実現する代替アロケータの探索、境界検査(fuzz)テストの追加による限界確認、および墓石再ハッシュ処理への対応が挙げられます。

本文

Redis 互換キー/バリュー ストアにおける静的メモリアロケーション

私は kv という、Zig で書かれた小規模な Redis 互換キー/バリュースーバーの開発を行っています。
目的は、実運用レベルに近づけつつ、コマンドを極力少なく実装することです。

私が徹底している設計原則の一つが 静的メモリアロケーション です:サーバー起動時に OS から必要な全てのメモリを確保し、以降は再度割り当ても解放もしません。このパターンは use‑after‑free のような予測不能な挙動を排除し、性能解析を簡素化し、結果としてより効率的な設計へと導きます。


なぜ静的アロケーションなのか?

サーバーが起動した時点で、設定に基づいて必要メモリ量を正確に算出できます:

  • 最大同時接続数
  • キー・値・リストのサイズ上限
  • プロトコル要件(RESP)

事前に答えが分かれば、すべてを先に割り当てることができ、起動後に OS へメモリアロケーションを要求する必要がなくなります。


高レベルアーキテクチャ

kv
はリクエストを三段階で処理します:

  1. 接続ハンドリング – ソケット受け付け、I/O バッファリング
  2. コマンドパース – RESP を解釈
  3. キー/バリュー格納 – ハッシュマップ + リストバックエンド

各段階で専用のプール(事前割り当て済みオブジェクトやバッファ)を使用します。


接続ハンドリング

const Connection = struct {
    completion: Completion,
    client: posix.socket_t,

    recv_buffer: *ByteArray,
    send_buffer: *ByteArray,
};

初期化時に次のプールを作成します:

  • Connection
    構造体
  • 受信バッファ (
    recv_buffers
    )
  • 送信バッファ (
    send_buffers
    )
const ConnectionPool = struct {
    const Pool = std.heap.MemoryPoolExtra(Connection, .{ .growable = false });

    recv_buffers: ByteArrayPool,
    send_buffers: ByteArrayPool,

    connections: Pool,

    fn init(config: Config, gpa: std.mem.Allocator) !ConnectionPool {
        const allocation = config.allocation();
        const pool = try Pool.initPreheated(gpa, config.connections_max);
        const recv_buffers = try ByteArrayPool.init(gpa, config.connections_max,
                                                    allocation.connection_recv_size);
        const send_buffers = try ByteArrayPool.init(gpa, config.connections_max,
                                                    allocation.connection_send_size);

        return .{
            .recv_buffers = recv_buffers,
            .send_buffers = send_buffers,
            .connections   = pool,
        };
    }
};

クライアントが接続すると、プールから

Connection
を取り出し、新しいバッファを割り当てます。
利用可能な接続が無ければリクエストは拒否されます。


コマンドパース

Redis のコマンドは RESP 形式で届きます。例:

*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n

パースには入力バッファのゼロコピースライスだけが必要です。
各リクエスト後にリセットされる

std.heap.FixedBufferAllocator
を使用します。

pub const Runner = struct {
    config: Config,
    fba: std.heap.FixedBufferAllocator,
    kv: *Store,

    pub fn init(config: Config, gpa: std.mem.Allocator, kv: *Store) !Runner {
        const L = config.list_length_max;
        const V = config.val_size_max;

        // 最大コマンドサイズの容量
        const parse_cap  = (1 + 1 + L);
        const parse_size = parse_cap * @sizeOf([]const u8);

        // 応答で値をコピーするためのスペース
        const copy_size   = L * @sizeOf([]const u8);
        const copy_data   = L * V;
        const fba_size    = parse_size + copy_size + copy_data;

        const buffer = try gpa.alloc(u8, fba_size);
        const fba    = std.heap.FixedBufferAllocator.init(buffer);

        return .{ .config = config, .fba = fba, .kv = kv };
    }
};

このアロケータはすべてのリクエストで再利用され、応答を書き込んだ後に

fba
をリセットします。


キー/バリュー格納

ハッシュマップにキーと値を保存します。静的割り当てが必要なので、unmanaged マップを使用します:

var map: std.StringHashMapUnmanaged(Value) = .empty;
try map.ensureTotalCapacity(gpa, capacity);   // 内部テーブルのみ確保

// 後で挿入する際は
map.putAssumeCapacity(key, value);

ensureTotalCapacity
は内部表だけを割り当てます。
ユーザーデータ(キーと値)は専用の
ByteArrayPool
から取得します。各キーは最大
list_length_max
要素まで保持できるように、最悪ケース分のスペースを確保しています。


削除と墓石

unmanaged マップは線形探索(linear probing)でオープンアドレスリングを行います。キー削除時に検索を壊さないために tombstone を使って論理的に削除済みとマークします。静的環境では、墓石が集まる前に十分なスペースを確保しておけば再ハッシュは不要です。


割り当てサイズの計算

Config
構造体にユーザー設定可能な上限値を保持し、派生値は
allocation()
で算出します:

pub const Config = struct {
    pub const Allocation = struct {
        connection_recv_size: u64,
        connection_send_size: u64,
    };

    connections_max: u32,
    key_count:      u32,
    key_size_max:   u32,
    val_size_max:   u32,
    list_length_max: u32,

    pub fn allocation(config: Config) Allocation {
        // RESP プロトコルと設定に基づくサイズ計算
        return .{ .connection_recv_size = ..., .connection_send_size = ... };
    }
};

典型的な設定で実行すると:

$ zig build run
config
  connections_max = 1000
  key_count       = 1000
  key_size_max    = 1024
  val_size_max    = 4096
  list_length_max = 50

allocation
  connection_recv_size = 206299
  connection_send_size = 205255

map capacity = 2048, map size = 0, available = 1638
total_requested_bytes = 748213015
ready!

750 MB が一連の上限で事前確保されます。

val_size_max
list_length_max
を増やすと線形に拡張し、両方を倍にすると約 2.8 GB になります。


最後に

静的アロケーションは推論を簡素化し、決定論的性能を保証します。
サイズ調整が必要ですが、動的割り当てのオーバーヘッドや断片化を避けたいサーバーには十分価値があります。

今後の課題:

  • 静的利用に最適化したハッシュマップ(墓石処理の改善)
  • メモリ効率向上のための代替アロケータ検討
  • エッジケースを洗い出し、限界値を検証するファズテスト追加

ソースコードは GitHub で公開しています。ぜひ別設定で

total_requested_bytes
がどう変わるか試してみてください!

同じ日のほかのニュース

一覧に戻る →

2025/12/30 6:46

USPS(米国郵便公社)が切手印日付システムの変更を発表しました。

## Japanese Translation: > **概要:** > USPSは最終規則(FR Doc. 2025‑20740)を発行し、国内郵便マニュアルに「セクション 608.11 —『切手印と郵便保有』」を追加しました。この規則では、切手印の定義が正式に示され、該当する印記がリストアップされています。切手印は印付け日でUSPSがその物件を保有していることを確認しますが、必ずしもアイテムの最初の受理日と同一ではありません。USPSは通常業務で全ての郵便に切手印を貼らないため、切手印が欠落していても、その物件が未処理だったとは限りません。機械による自動切手印は、施設内で最初に行われた自動処理操作の日付(「date of the first automated processing operation」)を表示し、投函日ではなく、地域輸送最適化(RTO)や路線ベースのサービス基準により受理日より遅くなることがあります。切手印は小売ユニットからの輸送後やカレンダー日がまたがる場合に付けられることが多いため、郵送日を示す信頼できる指標ではありません。同一日の切手印を確保するには、小売窓口で手動(ローカル)切手印を依頼できます。小売窓口で料金を支払うと「Postage Validation Imprint(PVI)」が付与され、受理日が記録されます。また、郵便証明書、登録メール、または認定メールは提示日を裏付ける領収書として機能します。この規則の影響は税務申告において重要です。IRC §7502 は、文書が期限までに物理的に届けられなかった場合に、提出の適時性を判断する際に切手印の日付を使用しています。

2025/12/27 20:30

**フレームグラフ 対 ツリーマップ 対 サンバースト(2017)**

## Japanese Translation: **概要:** Flame グラフ(SVG)はディスク使用量を高レベルで明確に示します。たとえば、Linux 4.9‑rc5 では `drivers` ディレクトリが全容量の50%以上を占め、`drivers/net` サブディレクトリは約15%です。Tree マップ(macOS の GrandPerspective、Linux の Baobab)は非常に大きなファイルを素早く検出できますが、高レベルのラベルが欠けています;Baobab のツリー表示では各ディレクトリの横にミニバーグラフが表示されます。Sunburst(Baobab の極座標図)は視覚的に印象的ですが、角度で大きさを判断するため長さや面積よりも誤解しやすいです。他のツール―`ncdu` の ASCII バーと `du -hs * | sort -hr` ―はテキストベースで迅速なサマリーを提供しますが、同時に一階層のみ表示されます。 提案されたユーティリティは、これら三つの可視化(Flame グラフ(デフォルト)、Tree マップ、Sunburst)すべてを組み合わせるものです。Flame グラフは読みやすさ・印刷性・最小スペース使用量が優れているため、多数のサンプルファイルシステムでテストした後にデフォルトとして採用されます。このアプローチは、ディスク使用量を簡潔かつ印刷可能なスナップショットとして提供し、ユーザーや開発者がスペースを占有する項目をより効率的に検出できるよう支援します。アイデアは ACMQ の「The Flame Graph」記事と「A Tour through the Visualization Zoo」に引用された既存の研究に基づいています。 **反映された主なポイント:** flame グラフの高レベルビュー、Tree マップの大きなファイルを素早く検出できるがラベルが欠けている点、Sunburst の視覚的魅力とサイズ認識の問題、他ツールの制限、および提案ツールの三つのビュー(デフォルトは flame グラフ)と引用元への参照。

2025/12/30 4:12

**「ラスト・ビハインド:未来派フェチズム、備蓄主義と地球放棄(2019)」**

## Japanese Translation: --- ## 要約 この記事は、エリート主導の「プレッパー」プロジェクト―人気文化フランチャイズから高名な企業・政治イニシアチブまで―が、特権階級の脱出を優先しつつ社会全体の修復を無視する加速化されたサバイバリズムであると主張しています。 まず、ベストセラーのクリスチャン・ファンダメンタリストSFフランチャイズ「レフト バック(Left Behind)」シリーズ(LaHaye & Jenkins, 1995)を取り上げます。この作品は捕集(Rapture)を世界的災害として描き、ハイテク装備(銃、SUV、グルフストリームジェットなど)を紹介しています。フランチャイズはビデオゲーム・映画・商品展開へと拡大し、ニコラエ・カルパチアなどのキャラクターが登場します。 次に、9/11後に復活した現代米国プレッパー文化に移ります。バンカー建設や備蓄を含み、この傾向はリアリティ番組「ドゥームズデイ・プレッパーズ(Doomsday Preppers, 2011–2014)」によって広まりました。 右派民兵についても取り上げられ、ジョージア州のIII %セキュリティフォース―「三パーセント」の植民者神話に根ざし、銃所有と反政府感情を強調しています。記事はまた、「米国で最も武装した人」と称されるメル・バーンスタイン(Mel Bernstein)を紹介し、260エーカーのコロラドスプリングス拠点に4,000以上の兵器、ペイントボールパーク、モトクロスパーク、銃店、射撃場があると述べています。 その後、テック・エリートプロジェクトが同様のマインドセットを反映していることに焦点を当てます。イーロン・マスクのSpaceX、ビオスフィア 2(1990年代初頭のアーコロジー実験でエドワード・バースによって資金提供され、後にスティーブ・バノンが救済)、ハワイのHI‑SEAS(火星植民地シミュレーションは望遠鏡設置を巡る先住民抗議を無視したとして批判)などです。Appleの「スペースシップ」本社、Google/Facebook従業員住宅、NSAのスター・トレック風制御室も挙げられます。これらのプロジェクトは気候変動、資源枯渇、戦争に対処するために地球から逃れる試みと位置づけられています。 記事は、これらすべての例を「右派加速主義」という広範なイデオロギー的トレンドに結び付けます。加速主義は資本主義崩壊を促進し、「世俗捕集」またはポスト・キャピタリズム未来を実現することを提唱します。著者らは、エリート主導のプレッパー計画が少数特権層に対して生存優先を行い、多くの人々を置き去りにすると結論付けています。そして、このような自己完結型脱出プロジェクトへの継続的投資は、社会格差を深め、公的災害緩和からリソースを逸らし、閉鎖的な宇宙思考企業文化を強化し、社会崩壊を不可避とみなす過激派イデオロギーを正当化する恐れがあると警告しています。

**Zig における静的割り当て** Zig のコンパイル時メモリ管理を使えば、実行時ではなくコンパイル時にストレージを確保できます。データ構造のサイズが事前に分かっている場合やヒープ割り当てを避けたいときに便利です。 ### 重要概念 - **コンパイル時定数** `const` や `comptime` の値を使い、コンパイラがコンパイル中に評価できるサイズを記述します。 - **固定長配列** リテラルサイズで配列を宣言します。 ```zig const buf = [_]u8{0} ** 128; // 128 バイト、すべてゼロ初期化 ``` - **静的フィールドを持つ構造体** 固定長配列やその他コンパイル時に決まる型を含む構造体を定義します。 ### 例 ```zig const std = @import("std"); // 静的サイズのバッファを持つ構造体 pub const Message = struct { id: u32, payload: [256]u8, // 256 バイト、コンパイル時に確保 }; // 静的割り当てを使う関数 fn process(msg: *Message) void { // ヒープ割り当ては不要;msg はスタック上またはグローバルに存在 std.debug.print("ID: {d}\n", .{msg.id}); } pub fn main() !void { var msg = Message{ .id = 42, .payload = [_]u8{0} ** 256, // すべてのバイトをゼロで初期化 }; process(&msg); } ``` ### 利点 - **決定的なメモリ使用量** – サイズはコンパイル時に分かる - **実行時割り当てオーバーヘッドがゼロ** – ヒープアロケータ呼び出しなし - **安全性** – コンパイラが境界と寿命を検証できる ### 使うべき場面 - 固定長バッファ(例:ネットワークパケット、ファイルヘッダー) - 短時間しか存続しない小規模補助データ構造 - 性能や決定的な動作が重要な状況 --- コンパイル時定数・固定配列・構造体定義を活用することで、Zig は最小限のボイラープレートで最大の安全性を保ちつつメモリを静的に割り当てることができます。 | そっか~ニュース