カニゲーム攻略日記ブログ

beatmaniaIIDXやハースストーンなどのゲーム攻略日記。主にまったり勢。2016年にIIDX皆伝になった

プログラムはなぜ動くのか  第5章 メモリーとディスクの親密な関係

目次

モリーとディスク

モリーとディスクは協力している

  • お互いの利点を活かし合い
  • お互いの欠点を補いながら
メモリ ディスク
速度 高速 遅い
容量 小容量 大容量
永続性 電源を切るとデータ消失 電源を切ってもデータ保持

プログラムはメモリにないと実行できない

1章を参照

https://kanikanipeace.hatenadiary.jp/entry/2023/05/04/153704

ストアドプログラム方式(プログラム内蔵方式)

メモリに保存してプログラムを読むことをストアドプログラム方式と呼ぶ
コンピュータシステムの設計原則

昔はプログラムを変更するのにPCの配線を変えてた
めんどいから上記が生まれた

プログラムはディスクにあると実行できない

  • CPUがプログラムを実行する際、プログラムカウンタはディスク上のアドレスを直接指し示すことはできない
  • プログラムカウンタは、メモリ上のアドレスを指す

ディスクキャッシュと仮想記憶

項目 ディスクキャッシュ 仮想記憶
目的 ディスクアクセス速度の向上 メモリ容量の制約を緩和
機能 ディスクからのデータ読み込みを高速化 メモリとディスク間でデータをやり取り
保存場所 メモリ(RAM) ディスク(ページファイル/スワップ領域)
対象データ 頻繁にアクセスされるディスクデータ メモリ上のプログラムやデータ
動作原理 頻繁にアクセスされるデータをキャッシュに保存し、次回アクセス時に高速に読み込む メモリ不足時に一部データをディスクに退避させ、必要なデータをメモリに取り込む

ディスクキャッシュ

ディスクアクセスを高速化する

ディスクデータを保存するメモリ内 の領域

  1. データをディスクから読む(低速)
  2. データをメモリに保存
  3. データを再度読み出す場合はメモリから(高速)

ブラウザのキャッシュ

応用

  1. データをサーバ(インターネットなど)からダウンロード(低速)
  2. データをディスク(キャッシュ)に保存
  3. データを再度読み出す場合はディスク(キャッシュ)から(高速)

仮想記憶

ディスク をメモリの一部として使う

メモリに空きが不足しててもプログラムが実行できる

ページング方式の仮想記憶

  1. ディスクでプログラムを複数のページに分ける
  2. メモリがディスクからページ1を読む(ページイン)
  3. CPUがページ1を実行
  4. ページ1をディスクに返す(ページアウト)
  5. 以下同文

仮想記憶の問題点

ディスクを経由するので低速
問題点を解決するためにはメモリ不足をなんとかしないと

問題点の解決方法

  • メモリを増設:物理的に増やしちゃえばいい
  • アプリを小さくする
    • DLLで関数を共有
    • _stdcallで小さくする

DLLで関数を共有

複数のアプリが使う共通の関数を1つにまとめる

悪い例:まとめない場合

各アプリに関数がコピーされているので
メモリを無駄に消費する

  ┌─────────┐      ┌─────────┐      ┌─────────┐
  │アプリA  │      │アプリB  │      │アプリC   │
  ├─────────┤      ├─────────┤      ├─────────┤
  │ 関数1   │      │ 関数1   │      │ 関数1    │
  │ 関数2   │      │ 関数2   │      │ 関数2    │
  └─────────┘      └─────────┘      └─────────┘

良い例:まとめる場合

DLLで関数を共有するので
メモリの節約になる
更に関数の更新も簡単

  ┌─────────┐     ┌─────────┐     ┌─────────┐
  │アプリA   │────│アプリB   │────│アプリC   │
  └─────────┘     └─────────┘     └─────────┘   
        └──────────────┬───────────────┘
                       │      
                 ┌─────────┐
                 │   DLL   │
                 ├─────────┤
                 │ 関数1   │
                 │ 関数2   │
                 └─────────┘

_stdcallで小さくする

C言語でアプリを小さくする高度なテクニック
他の言語でも応用できる

テクニック:関数呼出 でスタックをクリーンアップする

動作例:

1. 関数Aを呼ぶ(スタックする)
2. 関数Aが終了する(スタックをクリーンアップする)
3. 色々する
4. また関数Aを呼ぶ(スタックする)
5. 関数Aが終了する(スタックをクリーンアップする)

悪い例:関数呼出 でクリーンアップする(デフォルト設定)

関数呼出元にクリーンアップ処理があるので
その分メモリを無駄に使っちゃう
以下の場合、メモリ消費:7

メモリ
関数A呼出
クリーンアップ処理
・・・
関数A呼出
クリーンアップ処理
・・・
関数A呼出
クリーンアップ処理
・・・
関数Aの本体

良い例:関数呼出 でクリーンアップする

関数呼出先にクリーンアップ処理があるので
その分メモリを節約
以下の場合、メモリ消費:5

メモリ
関数A呼出
・・・
関数A呼出
・・・
関数A呼出
・・・
関数Aの本体
クリーンアップ処理

_stdcallの使い方

関数の前に_stdcallを置く

#include <stdio.h>

// 関数の前に_stdcallを置く
int __stdcall add(int a, int b) {
    return a + b;
}

int main() {
    int num1 = 5;
    int num2 = 7;
    int sum = 0;

    // _stdcall関数の呼び出し
    sum = add(num1, num2);

    printf("The sum of %d and %d is: %d\n", num1, num2, sum);

    return 0;
}

このテクニック使って仕事している人ってかなり少ないよね

chatgptさんの意見:

はい、stdcallは高度なテクニックで、一般的なアプリケーション開発ではあまり使われません。
主にWindows APIやDLL関数の呼び出し規約を制御するために使用されることが多いです。通常のアプリケーション開発では、デフォルトの呼び出し規約(
cdecl)で十分な場合がほとんどです。
しかし、Windows APIを扱ったり、呼び出し規約を明示的に制御する必要がある場合には、stdcallを理解していることが役立ちます。また、パフォーマンスやメモリ効率を最適化する必要がある特定の状況では、stdcallの使用が適切な場合もあります。

俺が去年、面接受けたところの1つは
処理高速化のプロフェッショナル集団だったから
このテクニックも使ってるかも

ディスクの物理構造

物理も見ちゃう

  • セクター方式:固定長で区切る
  • バリアブル方式:可変長で区切る(説明は省略)

セクター方式

セクターは、円状のトラック上に等間隔で配置
一般的に512byte
図は以下参照

https://xtech.nikkei.com/it/pc/article/basic/20091109/1020320/

クラスタ

windowsが論理的にディスクを読み書きする単位はクラスタ

1クラスタ = 1セクターサイズ(512byte) * n

例:1クラスタ = 1セクターサイズ(512byte) * 1

  1. 1クラスタ = 1セクターサイズ * 1でフォーマット
  2. 何もないときは使用領域 = 0
  3. textファイルに1文字保存(1byte)
  4. 使用領域 = 1クラスタ = 512byte
  5. textファイルに2文字保存(2byte)
  6. 使用領域 = 1クラスタ = 512byte
  7. textファイルに513文字保存(513byte)
  8. 使用領域 = 2クラスタ = 1024byte

処理速度と記憶領域のバランスが大事

まとめ

メモリやディスクを節約する気持ちを忘れないでね

優れたプログラマ

  • 実行速度が早いだけではない
  • サイズも小さくする