Rust で Google Chrome のメモリ使用量を減らす

## 動機
こんなポンコツ PC でプログラミングしています。VSCode とブラウザ開いただけでこの有様だよ！
![クリップボード一時ファイル01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/03748eeb-e0f1-20c8-4649-7bca0462d63a.png)
VSCode にはメモリを一杯使ってサクサク動いて欲しいので、「Chrome オメーに喰わせるメモリはねぇ！」ということを実践してみたいと思います。Rust で。~~（いつもは Firefox 使ってますが、Chrome の方が受けがいいかなと思って Chrome にしてます）~~

## 結果
先に結果です。

↓減らす前
![クリップボード一時ファイル04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/295e07e8-b0fc-80b3-7ff1-b86dacba6eab.png)
↓減らした後
![クリップボード一時ファイル03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/932975f3-ab4e-d69a-d45f-127aaab6ab74.png)
わーい、Chrome のメモリ使用量減ったー。

## どうやるか
[EmptyWorkingSet](https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-emptyworkingset) 関数を呼んでるだけです。おわり。

## 解説
ワーキングセットとは？
>ワーキングセットとは、物理メモリー領域の中でも、アプリケーションが現在使用している状態の領域のことである。言い換えれば、アプリケーションが使用しているメモリーでもあまり使用されていないデータをOSがハードディスクに退避（スワップ）させる際に、退避の対象とならずに物理メモリー内に留められる部分のこと。

なのでワーキングセットを空にすることはアプリケーションのパフォーマンスに重大な影響を及ぼします。HDD のころは`EmptyWorkingSet`関数を呼ぶと、カリカリ言って実用に耐えないレベルでした。最近の比較的高速なストレージならあまり気にならないかもしれません。

## コード
ここでの肝は main.rs の中の`impl Drop for Handle`です。`Drop`を実装した Rust の構造体に`HANDLE`を束縛することで、スコープを抜けたら自動的にリソースを開放するようにしています。

```toml:Cargo.toml
[package]
name = "chromempty"
version = "0.1.0"
authors = ["benki"]
edition = "2018"

[dependencies.winapi]
version = "0.3"
features = ["tlhelp32", "handleapi", "processthreadsapi", "psapi"]
```
```rust:main.rs
use std::ffi::CStr;
use std::mem;
use std::os::raw::c_char;
use std::thread;
use std::time::Duration;
use winapi::{
    shared::minwindef::{FALSE, TRUE},
    um::{
        handleapi::{CloseHandle, INVALID_HANDLE_VALUE},
        processthreadsapi::OpenProcess,
        psapi::EmptyWorkingSet,
        tlhelp32::{
            CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32,
            TH32CS_SNAPPROCESS,
        },
        winnt::{HANDLE, PROCESS_ALL_ACCESS},
    },
};

struct Handle(HANDLE);

impl Drop for Handle {
    fn drop(&mut self) {
        unsafe { CloseHandle(self.0); }
    }
}

fn main() {
    loop {
        chromempty();
        thread::sleep(Duration::from_secs(5));
    }
}

fn chromempty() {
    unsafe {
        let mut entry = mem::MaybeUninit::<PROCESSENTRY32>::uninit().assume_init();
        entry.dwSize = mem::size_of::<PROCESSENTRY32>() as u32;

        let snap_shot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if snap_shot == INVALID_HANDLE_VALUE {
            return;
        }
        // SNAPSHOT HANDLE を Rust の構造体に束縛
        let snap_shot = Handle(snap_shot);

        if Process32First(snap_shot.0, &mut entry) == TRUE {
            while Process32Next(snap_shot.0, &mut entry) == TRUE {
                if let Ok(exe) = CStr::from_ptr(&entry.szExeFile as *const c_char).to_str() {
                    if exe.eq("chrome.exe") {
                        let handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
                        if handle.is_null() {
                            continue;
                        }
                        // PROCESS HANDLE を束縛
                        let handle = Handle(handle);
                        EmptyWorkingSet(handle.0);
                        // PROCESS HANDLE はここで開放される
                    }
                }
            }
        }
        // SNAPSHOT HANDLE はここで開放される
    }
}
```

## まとめ
- `EmptyWorkingSet`関数を知ったよ
- ワーキングセットとは何かを知ったよ
- `Drop`による自動的なリソース開放を知ったよ
- ポンコツ PC つらい:sob: