本稿では、Rust においてメモリ使用量を削減するために「Box(ボックスタイプ)」をどのように活用するか解説します。
- RUST ヒント:`Box<T>` はスプラッシュメモリの削減や大型構造体の配置制御に有効です。

2026/04/24 2:02

本稿では、Rust においてメモリ使用量を削減するために「Box(ボックスタイプ)」をどのように活用するか解説します。 - RUST ヒント:`Box<T>` はスプラッシュメモリの削減や大型構造体の配置制御に有効です。

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

要約

Japanese Translation:

このテキストの主なメッセージは、Rust の構造体レイアウトを最適化することでメモリ消費量を大幅に削減できる点であり、AWS SDK プログラムで 475 MB の削減を達成したことです。これは、AWS モデルリポジトリから数千の構造体を非同期化(シリアライズ)する際に、標準的な

Option
フィールドを空の場合にはボックス化されたバリエント
Option<Box<T>>
に置き換えることで実現されました。通常、非同期化ではこれらのオプションに対してインラインでポインタが格納され、データが存在しなくても 64 ビットシステム上では不要なメモリを消費します(例えば、
Option<String>
None
でも 24 バイト必要であり、空ではない構造体の場合は 140 バイトを超えることもあります)。空の場合にボックス化された構造体を使用することで、メモリフットプリントは 144 バイト以上からわずか 32 バイトに低下しました。開発者は
tikv-jemallocator
やプロファイリング機能といった専門ツールを利用し、Serde ライブラリを使ってこの特定のスナップショットのメモリフットプリントを測定しました。多くのボックスによるヒープのフラグメンテーションへの理論的な懸念はありますが、この具体的な実装ではそのような問題は見られず、むしろ低いメモリ圧力により高価なメモリの検索が回避されたため、パフォーマンスも向上しました。結局のところ、ユーザーは著しく小さなメモリフットプリントと高速化を得ることができ、これはメモリ制限の厳しい環境で大規模なオプション構造体を管理する Rust 開発者にとって実用的なパターンとなります。

Text to translate:

The text's primary message is that optimizing Rust struct layouts can drastically reduce memory consumption, achieving a 475 MB reduction in an AWS SDK program. This was accomplished by replacing standard

Option
fields with boxed variants (
Option<Box<T>>
) for optional or empty structs when deserializing thousands of structures from the AWS models repository. Normally, deserialization stores inline pointers for these options, consuming unnecessary memory even when data is absent on 64-bit systems (e.g., an
Option<String>
takes 24 bytes regardless of being
None
, while an empty non-empty struct can exceed 140 bytes). By switching to boxed structures for empty cases, the memory footprint dropped from over 144 bytes to just 32 bytes. Developers utilized specialized tools like
tikv-jemallocator
and profiling features to measure this specific footprint using the Serde library. While there are theoretical concerns about heap fragmentation with many boxes, this specific implementation saw no such issues; in fact, performance improved because lower memory pressure eliminated expensive memory searches. Ultimately, users gain a significantly smaller memory footprint and better speed, offering a practical pattern for Rust developers managing large optional structures in memory-constrained environments.

本文

現実世界の Rust プログラムで 895 MB を使用していたメモリ容量を、一部の構造体(struct)の配置や JSON ファイルのデシリアライズ方法を変更するだけで 475 MB も節約することができました。

実際のユースケース

このプログラムは、https://github.com/awslabs/aws-sdk-rust/tree/main/aws-models にあるすべての JSON ファイルを、「Smithy Shape」構造体に変換してデシリアライズします。

これらのファイルには、以下の例に似た数千人規模の構造体が含まれています:

"com.amazonaws.iam#EnableOrganizationsRootSessionsResponse": {
    "type": "structure",
    "members": {
        "OrganizationId": {
            "target": "com.amazonaws.iam#OrganizationIdType",
            "traits": {
                "smithy.api#documentation": "<p>The unique identifier (ID) of an organization.</p>"
            }
        },
        "EnabledFeatures": {
            "target": "com.amazonaws.iam#FeaturesListType",
            "traits": {
                "smithy.api#documentation": "<p>The features you have enabled for centralized root access.</p>"
            }
        }
    },
    "traits": {
        "smithy.api#output": {}
    }
}

Rust の慣習に従い、非常に便利な

serde
ライブラリを使用しています。詳細な解説は割愛しますが、明確にするために構造体の一部を示しておきます。全体を読まなくてよいので、ただいくつかの構造体が相互にネストしており、一部のフィールドが可視化(optional)で
serde
属性が付与されていることを覚えておいてください:

#[derive(Clone, Deserialize, Serialize)]
pub struct SmithyShape {
    #[serde(rename = "type")]
    pub shape_type: SmithyShapeType,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub operations: Vec<SmithyReference>,
    #[serde(default)]
    pub members: FxHashMap<String, SmithyReference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub key: Option<SmithyReference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<SmithyReference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub member: Option<SmithyReference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub input: Option<SmithyReference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub output: Option<SmithyReference>,
    #[serde(default)]
    pub traits: SmithyTraits,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SmithyReference {
    pub target: ShortShapeId,
    #[serde(default)]
    pub traits: SmithyTraits,
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct SmithyTraits {
    #[serde(rename = "smithy.api#title", skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(rename = "aws.api#service", skip_serializing_if = "Option::is_none")]
    pub service: Option<SmithyServiceTrait>,
    #[serde(
        rename = "smithy.api#sensitive",
        skip_serializing_if = "Option::is_none"
    )]
    pub sensitive: Option<SmithySensitiveTrait>,
    #[serde(
        rename = "smithy.api#documentation",
        skip_serializing_if = "Option::is_none"
    )]
    pub documentation: Option<String>,
    #[serde(rename = "smithy.api#pattern", skip_serializing_if = "Option::is_none")]
    pub pattern: Option<String>,
    #[serde(rename = "aws.iam#iamAction", skip_serializing_if = "Option::is_none")]
    pub iam_action: Option<SmithyIamAction>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SmithyServiceTrait {
    pub sdk_id: Option<String>,
    pub arn_namespace: Option<String>,
    pub cloud_formation_name: Option<String>,
    pub cloud_trail_event_source: Option<String>,
    pub endpoint_prefix: Option<String>,
}

これは標準的な見映えをしたコードであり、現在の慣行ですが、私たちはこれを「 naïve(天真的)」とも呼ぶこともできます。このようにデシリアライズすることで、構造体が 895 MB のメモリを消費してしまいます。分析の結果、大部分の可視化された文字列が欠落していることが判明し、そこで大幅なメモリフットプリント削減を実現しました。

しかし、これには Rust に特有のいくつかのポイントを知る必要があるため、少し寄り道が必要です:

Rust の構造体とメモリについて

64 ビットのプラットフォームでは、1 ワードは 8 バイトで構成されています(例え

usize
を格納する場合でも)。
String
は、文字列へのアドレス、割当サイズ、容量の 3 ワードが必要であり、これに加えて文字節そのものを格納するためのヒープ上の空間も必要です。つまり、実際の文字コンテンツを含まずとも、
String
のサイズは 24 バイトになります(
dbg!(std::mem::size_of::<String>());
で確認できます)。

niche compiler optimization(ニッチなコンパイラ最適化)により、

Option<String>
は同じ大きさになります(基本的にポインタ型を option にする場合、ポインタがゼロであれば
None
であるかどうかを確認するために追加のバイトは不要です)。

したがって、以下の構造体は、すべての文字列が欠落している(

None
)場合、正確に 120 バイト(5×24)のメモリを使用します:

pub struct SmithyServiceTrait {
    pub sdk_id: Option<String>,
    pub arn_namespace: Option<String>,
    pub cloud_formation_name: Option<String>,
    pub cloud_trail_event_source: Option<String>,
    pub endpoint_prefix: Option<String>,
}

さて、構造体の合成についてです。別の構造体を「含む」ような構造体を見てみましょう。ここでは単純化のために、当方の

SmithyServiceTrait
と他のフィールドを含むものだけを想定します:

pub struct Container1 {
    pub some_string: Option<String>,
    #[serde(default)]
    pub trait: SmithyServiceTrait,
}

最小サイズは、予想通り 24 + 120 = 144 バイトとなります。

しかし、我々の

SmithyShape
は可視化された構造体だけを内包しています。
Container
構造体を
Option<SmithyServiceTrait>
を使用するように変更するとどうなるでしょうか?

pub struct Container2 {
    pub some_string: Option<String>,
    #[serde(default)]
    pub trait: Option<SmithyServiceTrait>,
}

両方の

some_string
trait
None
の場合、コンテナのサイズは何でしょうか?それは
Container1
と同じであり、option を持ってもメモリの利点は得られません(実際には、
SmithyServiceTrait
内にのみ含まれる
Option<String>
だけがあるため、コンパイラが追加のバイトを省略できることに幸運でした)。

これを我々の

SmithyTraits
に適用すると、標準的な実装がメモリ上膨れ上がる理由がわかります。これは Java や Python、JavaScript などの言語におけるクラス合成とは根本的に異なります。

これらの言語では、以下の場合:

class Container {
    String someString;
    SmithyServiceTrait trait;
}

null トリート(trait)はメモリ上でポインタサイズのワードのみを使用します。

我々の Rust の

Container
も何も格納するものがない場合にオプションの内容に対してのみ 1 ワードをとるようにするためには、基本的に模倣したい言語で行われていることをする必要あります:つまり、このコンテンツをコンテナの外、ヒープ上に置く必要があるのです:

pub struct Container3 {
    pub some_string: Option<String>,
    pub trait: Option<Box<SmithyServiceTrait>>,
}

今、両方の

some_string
trait
None
の場合、コンテナはメモリ上で 32 バイト(
Option<String>
用の 3 ワード、
Option<Box<...>>
用 1 ワード)のみを使用します。前に述べた niche optimization も
Option<Box<...>>
に適用されます:それは単純な
Box<...>
よりも多く消費しません。

メモリを回復させた変更

基本的には、変化は以下の点に集約されます:

  1. 構造体が不要である何时を検出する(すなわち、すべてのフィールドが
    None
    の時)
  2. その親構造体内でそれを可視化にし、ヒープ上に移動させる
  3. 空の不要な構造体を保存しないようにカスタムデシリアライザを実装する

したがって:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SmithyReference {
    pub target: ShortShapeId,
    #[serde(default)]
    pub traits: SmithyTraits,
}

は以下のように変更されます:

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SmithyReference {
    pub target: ShortShapeId,
    #[serde(
        default,
        deserialize_with = "deserialize_boxed_traits",
        serialize_with = "serialize_boxed_traits"
    )]
    pub traits: Option<Box<SmithyTraits>>,
}

fn deserialize_boxed_traits<'de, D: Deserializer<'de>>(
    deserializer: D
) -> Result<Option<Box<SmithyTraits>>, D::Error> {
    let traits = SmithyTraits::deserialize(deserializer)?;
    if traits.is_empty() { // すなわち、すべての可視化された文字列が none の場合
        Ok(None)
    } else {
        Ok(Some(Box::new(traits)))
    }
}

同様にして、

SmithyShape
においてすべての
Option<SmithyReference>
Option<Box<SmithyReference>>
に置き換え、アクセサいくつかはオプションのwegen(手段)のために修正されただけで、これで完了です。こうして、デシリアライズされた全 AWS shape を格納するために必要なメモリ容量を倍減させ、475 MB の節約に成功しました。

いくつかのノート:

  • このデシリアライゼーションは、オブジェクトが廃棄される前にデシリアライズされるため、CPU でより多くのコストがかかります。しかし、メモリの検索を必要としなくなったことで、この追加の手順にもかかわらずタスク全体が早まったという点において、トレードオフは完全な勝利となりました。
  • 多くのボックスは破片化されたヒープを意味します。このような場合の問題ではありませんが、これは覚えておく価値があります。

検証:影響の実証

経験を持つと、どこでスペースを節約し、どれだけなのかへの直感を得ることができます。しかし、真剣に作業するためには、行ったことが機能したことを確認し、価値があったかを検証する必要があります。つまり、測定が必要です。Rust にて、すべてのポインターをたどって複合オブジェクトが消費する総空間を知る簡単な軽量な方法はありません。ここでは、割り当ての状態に関する情報を提供するアロケータを使用する解決策を選びました(標準アロケータは内部統計について限られた可視性しか提供しないため、

jemalloc
を使用しました)。デシリアライゼーション前のメモリ使用量と後のメモリ使用量を比較しました。

常にこのアロケータを使用したいわけではないので、

Cargo.toml
に「プロファイル」機能 Feature を定義しました:

[features]
profile = ["tikv-jemallocator", "tikv-jemalloc-ctl"]

[dependencies]
tikv-jemallocator = { optional = true, version = "0.6", features = ["stats", "profiling"] }
tikv-jemalloc-ctl = { optional = true, version="0.6", features = ["stats"] }

そして、メインで使用することを宣言します:

#[cfg(feature = "profile")]
#[global_allocator]
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;

次に、それらの shape をすべてデシリアライズする関数において、測定を実行します:

#[cfg(feature = "profile")]
fn allocated_mb() -> usize {
    tikv_jemalloc_ctl::epoch::advance().unwrap();
    tikv_jemalloc_ctl::stats::allocated::read().unwrap_or(0) / (1024 * 1024)
}

#[cfg(feature = "profile")]
let base = allocated_mb();

... shape をすべて読み込む ...

#[cfg(feature = "profile")]
eprintln!(
    "Memory used for the shapes = {} MB (total)",
    allocated_mb() - base
);

ヒント:

tikv_jemalloc_ctl
は、サーバーアプリケーションを追跡する上で興味深いかもしれないより多くの詳細を公開しています。

結論:覚えておくべきことは何か(簡潔に)

総括すると、Rust 開発者が必要として理解し、覚えておくべきものは以下の通りです:

  • 複合構造体は有意なメモリを消費します。
  • コンテンツが重要でないことを検出することで、フィールド(
    BigStruct
    )を可視化にすることは費用対効果があります。
  • 可視化されたフィールド
    Option<BigStruct>
    は、
    None
    でも少なくとも
    BigStruct
    のスペースを取ります。
  • field: Option<Box<BigStruct>>
    でボックス化することで、この連鎖を断ち切る(その時、
    None
    は親構造体でワードのみをとる)。
  • これらの最適化は、Serde によるデシリアライゼーションにおいても可能にします。

同じ日のほかのニュース

一覧に戻る →

2026/04/27 5:41

「Friendster を 3 万ドルで購入しました。そこで私がどのような取り組みを行っていますか?」

## Japanese Translation: 最初のソーシャルネットワーク、Friendster は 2002 年 3 月 22 日に発売され、2015 年にオフラインになり、ビジネス上の圧力により 2018 年に会社は廃止されました。2023 年 10 月に、前所有者から 7,456 ドルで競標で購入した previously acquired のドメイン friendster.com は、Park.io の創始者によって再活性され、その額は Bitcoin で約 20,000 ドル(当初のオファーは 40,000 ドル)および年間の広告収益で約 9,000 ドルに合意されました。著者はユーザーデータを販売せず、トラッキングアルゴリズムを使用せず、広告を表示せずに Friendster を再構築しました。iOS アプリが作成され、友人を追加するには実際に携帯電話同士をタップさせる必要がありましたが、初期には App Store ガイドライン 4.2 に基づく拒否を受け、その後デザインを変更してコンタクト中心の接続方法を維持しつつオープンな登録を許可しました。改定版アプリは厳格な審査プロセスを経て現在 Apple App Store で公開されています。主な機能には「友達の友達」ビューや、1 年間アクティブでないユーザーとのリンクを徐々に弱める「Fading connections」が含まれます。この復活は、侵襲的な広告やデータによる収益化を行わなくともソーシャルネットワークが成功し、創始者が OkCupid を通じて家族と出会う自身の旅路から着想を得た本物の現実世界のつながりを育むことを示しています。

2026/04/27 5:18

FAS16:ス턱ネットより 5 年前に出現した高精度ソフトウェア・サボタージュ(悪意のある改ざん)ツール。

## Japanese Translation: 最重要な発見は、「Fast16」という高度なサイバーサボタージュフレームワークの発見であり、先進物理学、核研究、暗号学、構造工学(特に LS-DYNA 970 は衝突試験や核シミュレーション向けに、PKPM は設計向けに、MOHID は水動力学向け)で使用される高精度ソフトウェアを静かに破損させる能力を有しています。通常のウイルスとは異なり、Fast16 は計算エンジンに特化して結果の精度を低下させることで、国の科学プロジェクトに深刻な脅威をもたらします。2005 年頃開発された主要なキャリアバイナリ**svcmgmt.exe**(2005 年 8 月 30 日 компィル済み)は、ステクスネットなどの有名な攻撃から 5 年以上、フラムから 3 年以上前に存在しており、埋め込み型の Lua 仮想マシンと共に *fast16.sys* という独自のプロンプト起動カーネルドライバ(2005 年 7 月 19 日 compild)を使用しています。このドライバはシステムファイル操作を傍受し、悪意のある指示を直接メモリに注入することで、感染の明確な兆候なしに破損を引き起こすことを保証します。フレームワークは「wormlet」を配置して、SMB共有とデフォルトパスワードを使用して Windows 2000/XP ネットワーク内に蔓延させますが、十八種類のアンチウイルスシグネチャを確認してから破壊ペイロードを実行するなど、高度な回避戦術も備えています。SentinelLABS は、**fast16.sys**, **svcmgmt.exe**, **connotify.dll** および疑わしいパッチ対象に対する検出ツール、すなわち YARA ルールとハッシュ値(MD5, SHA1, SHA256)を発表しています。この開示は、レガシーコンピューティング環境の再評価を緊急に要求させ、数十年前に存在した休眠的なサボタージュメカニズムが、現代の研究インフラにおいて依然としてアクティブなリスクであるという事実を浮き彫りにしました。

2026/04/27 5:56

サウェーが、競技会でのマラソンタイムで2時間台突破者として初の快挙を成し遂げた。

## Japanese Translation: サベシアヌ・サーウェは、2 時間以内で公式に競技距離のマラソンを完走した初のアスリートとなり、ロンドンマラソンにおいて驚異的なタイムの1 時間59 分30 秒でゴールしました。この画期的な快挙により、ケルビン・キプ Tum が記録していた前歴代記録である 2 時間 00 分 35 秒は破られ、自身のアベレックベストタイムである 2 時間 02 分 27 秒を約 4 分短縮しました。驚くべきことに、サーウェはザ・モールを完走し、前半を 60 分 29 秒、後半を 59 分 01 秒というペースで走り切り、これは過去にハーフマラソンにおいて半世紀以上の記録を持つ男性アスリート計 63 名しか達成したことがありません。彼のパフォーマンスには、エネルギー還元の向上と効率化を目的として設計されたアダピスの最新のスーパースホーズが寄与しました。また、ベルリンでの勝利以前にロンドンに向けて実施された厳格なドーピング検査(独立した試行 25 回分)も安全にクリアしています。エリウド・キプチョゲ氏が 2019 年に 2 時間以内の走りを達成しましたが、環境条件が過度に制御されていたため公式記録の対象外とされました。本レースには他のチャンピオンも参戦しました:ヨミフ・ケジェラは 1 時間59 分41 秒のデビュータイムを記録し、第 2 位でゴールすることで史上 2 人目の 2 時間以内の走りを達成しました。ジャコブ・キプ Limo は 2 キープティム氏の前記録より速いタイム 2 時間 00 分 28 秒でゴールし、表彰台に上りました。エチオピア出身のティグスト・アセファは女子専用レースにおいて自身の世界記録を2 時間15 分41 秒に刷新し、ヘレン・オブイリ氏とジョイスライン・ジェポギェー氏の後に残してタイトルを守りました。マルセル・フック選手は6 年連続でエリート男子用車椅子マラソンを制し、1 時間24 分13 秒のタイムを記録。デイヴィッド・ワイア選手とのタイによりロンドンマラソンの勝利記録を更新しました。キャサリン・デブルナー選手はエリート女子用車椅子マラソンにおいて1 時間38 分29 秒のタイムでタイトルを守り、アメリカ人のタティアナ・マックファデン氏をわずか 5 秒差で下しました。モ・ファラー氏はサーウェ氏の成果を迎え撃した長い期待の milestones であると述べ、これはサーウェ氏一人のためではなく、ロンドンにいる皆のためであるとお礼を述べています。

本稿では、Rust においてメモリ使用量を削減するために「Box(ボックスタイプ)」をどのように活用するか解説します。 - RUST ヒント:`Box<T>` はスプラッシュメモリの削減や大型構造体の配置制御に有効です。 | そっか~ニュース