**プロジェクト概要**

私は、Macが熱的にスロットリング(温度上昇による性能低下)を開始した際に通知する macOS アプリケーションを開発しています。

2025/12/28 20:51

**プロジェクト概要** 私は、Macが熱的にスロットリング(温度上昇による性能低下)を開始した際に通知する macOS アプリケーションを開発しています。

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

要約

Japanese Translation:

記事は、Apple Silicon MacBook(特に4K 120 Hz外部ディスプレイを駆動するM2 Air、および熱放散が制限される14″ M4 Max)がサーマルスロットリングを経験する可能性があることを示しています。これは、iStat Menus が 100 % CPU 使用率とともに電力と周波数の低下を報告している点から明らかです。

ユーザーがこの圧力を検知できるように、著者は3つの検出方法を比較しています:

ProcessInfo.processInfo.thermalState
(値 
nominal
fair
serious
critical
) – 「fair」は実際には
powermetrics
の中で「moderate」と「heavy」の両レベルに対応します。
powermetrics -s thermal
CLI は、より細かい「Current pressure level」(moderate, heavy, trapping)を報告します。
• システムデーモン
thermald
によって公開される Darwin 通知
com.apple.system.thermalpressurelevel
は、
OSThermalNotification.h
で定義されており(nominal 0, moderate 1, heavy 2, trapping 3, sleeping 4)です。

通知チャネルを利用して、著者は MacThrottle を構築しました。これは SwiftUI のメニューバーアプリで、サーモメータアイコンとともに現在の熱状態を表示します。当初は

powermetrics
を呼び出すために特権ヘルパーを実行していましたが、現在は通知を直接読み取り、root 権限を不要にしています。
アプリはまた、SMC キー(古いシリコン向けには IOKit のフォールバック)を使用して温度とファン速度を取得し、10 分間のウィンドウでこれらのメトリクスをグラフ化します。状態遷移時に macOS 通知を送信し、
SMAppService.mainApp.register()
でログイン項目として追加することも可能です。Apple Developer アカウントが無い場合はバイナリを手動でインストールする必要があります。ソースビルドはノタリゼーション制限のある Mac 用に提供されています。

日常ユーザー向けに、MacThrottle は Mac がスロットリングしているタイミングをリアルタイムで把握できるため、ワークロードやディスプレイ設定を調整できます。開発者およびシステムインテグレーターは公開されている

com.apple.system.thermalpressurelevel
通知を利用してカスタム監視ツールを構築し、macOS デバイス全体の電力管理を改善する可能性があります。

本文

2025年12月27日 · 2204語・11分

MacThrottle を作った経緯についての物語です。

数年前から M2 MacBook Air に満足しています。
しかし外部ディスプレイ(特に 4K 120 Hz のような高負荷パネル)を接続すると、処理が遅くなりやすいことに気づきました。ファンがないため音はしませんが、すべての操作が遅くなったり応答がなくなる――これが熱スロットリングです。

iStat Menus で CPU 使用率が 100 % のままワット数が下がる様子を見ると、熱スロットリングだと分かります。
MX Power Gadget では、電力使用量とパフォーマンスコアの周波数が低下しているのに、利用率は 100 % を維持していることが一目で分かります。

同じ現象は、仕事用 MacBook Pro(14″ M4 Max)でも発生します。これは 14″ の熱設計が最大出力に対して小さく、最悪のバリアントです。以前使っていた 14″ M1 Pro MacBook Pro では、3 年間ファン音を聞いたことがありませんでした…
それでも Apple Silicon はパフォーマンスと電力使用量で Intel 時代より劇的に改善されているため、大好きです。


Apple Silicon SoC が熱スロットリングしているかどうかを判定する方法はある?

プログラムから thermal state を取得する

思ったよりも大変でした。macOS は複数の不揃いな手段でこの情報を公開しています。

Apple は Foundation の

ProcessInfo.thermalState
を推奨しています:

➜  ~ swift -e 'import Foundation; print(["nominal","fair","serious","critical"][ProcessInfo.processInfo.thermalState.rawValue])'
nominal

良さそうですが、別のツールも同じ情報を提供します(ただし root が必要です):

powermetrics
.

➜  ~ sudo powermetrics -s thermal
Password:
Machine model: Mac14,2
OS version: 25B78
...
*** Sampled system activity ... ***
**** Thermal pressure ****
Current pressure level: Nominal

両方とも「Nominal」と報告しますが、実際に負荷テスト(

stress-ng --cpu 0 -t 600
)を行うと二つの値が離れます。
ProcessInfo.thermalState
powermetrics
の granularity が異なり、状態数も違います。

経験上のマッピング:

ProcessInfo.thermalStatepowermetrics
nominalnominal
fairmoderate
seriousheavy
criticaltrapping

「sleeping」状態は一度も触れたことがないので、正確に一致するかは不明ですが、技術的には上記のように定義されています。

実際、Mac が熱くなると

powermetrics
moderate になり、スロットリングが始まると heavy になります。
ProcessInfo
では両方とも fair に該当し、正確なスロットリング時点を知るには不十分です。

iOS と macOS の違いかと思いましたが、macOS ドキュメントでも同様に参照されています。Intel Mac ではより一貫していた可能性があります。

その他の CLI ツール

Dave MacLachlan(2020)の記事を見つけました。他にも thermal データ取得用 CLI があるものの、Apple Silicon の MacBook では動作しないようです。

➜  ~ sudo thermal levels
Thermal levels are unsupported on this machine.

➜  ~ sudo pmset -g thermlog
Note: No thermal warning level has been recorded
...

最も興味深いのは、

powermetrics
が表示するデータが
thermald
から来ているということです。さらに
thermald
は現在の熱圧力を Darwin の通知システム(
notifyd
)に書き込みます。

➜  ~ notifyutil -g com.apple.system.thermalpressurelevel
com.apple.system.thermalpressurelevel 0

各レベルは

OSThermalNotification.h
に定義されています(Apple のヘッダ)。列挙体は次の通りです。

typedef enum {
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
    kOSThermalPressureLevelNominal = 0,
    kOSThermalPressureLevelModerate,
    kOSThermalPressureLevelHeavy,
    kOSThermalPressureLevelTrapping,
    kOSThermalPressureLevelSleeping
#endif
} OSThermalPressureLevel;

面白いことに

OSThermalNotification.h
はほとんど参照されておらず、Google 検索でも数件しかヒットしません。Bazel などで使われているようです。

通知システムを利用する(root不要)

この方法は root を必要とせず、

com.apple.system.thermalpressurelevel
イベントにサブスクライブして 正確な熱状態 を取得できます。以下は Swift のサンプルです。

import Foundation

@_silgen_name("notify_register_check")
private func notify_register_check(_ name: UnsafePointer<CChar>, _ token: UnsafeMutablePointer<Int32>) -> UInt32
@_silgen_name("notify_get_state")
private func notify_get_state(_ token: Int32, _ state: UnsafeMutablePointer<UInt64>) -> UInt32
@_silgen_name("notify_cancel")
private func notify_cancel(_ token: Int32) -> UInt32

let notifyOK: UInt32 = 0
let name = "com.apple.system.thermalpressurelevel"

var token: Int32 = 0
guard notify_register_check(name.withCString { $0 }, &token) == notifyOK else {
    fatalError("notify_register_check failed")
}
defer { _ = notify_cancel(token) }

var state: UInt64 = 0
guard notify_get_state(token, &state) == notifyOK else {
    fatalError("notify_get_state failed")
}

let label = switch state {
case 0: "nominal"
case 1: "moderate"
case 2: "heavy"
case 3: "trapping"
case 4: "sleeping"
default: "unknown(\(state))"
}
print("\(state) \(label)")

実行すると:

➜  ~ swift thermal.swift
0 nominal

MacThrottle の構築

概要

Opus 4.5 を使って、Apple Silicon のダイが 110 °C を超えないように警告する小さなメニューバーアプリを作ることにしました。MacThrottle と名付けました。

単純な SwiftUI アプリでメニューバーに温度状態を表示し、オリジナルのサーモメータアイコン(色は緑から赤へ変化)で可視化します。全体で 20 種類のモノクロメタルアイコンがあり、サーモメータ内の色はシンプルにしています。

アプリは SwiftUI の

MenuBarExtra
シーンを使用し、Dock アイコンは不要(
Info.plist
LSUIElement=true
を設定)。予想よりも簡単でした!

初期案:root ヘルパーで
powermetrics

最初は

powermetrics
が必要だと考え、root で動くヘルパー(launchd デーモン)を設置する方針にしました。アプリ自体は権限を上げずに実行し、バックグラウンドで bash スクリプトを走らせます。

Launchd plist

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
  <key>Label</key><string>com.macthrottle.thermal-monitor</string>
  <key>ProgramArguments</key>
  <array><string>/usr/local/bin/mac-throttle-thermal-monitor</string></array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict>
</plist>

Bash スクリプト

#!/bin/bash
OUTPUT_FILE="/tmp/mac-throttle-thermal-state"

while true; do
    THERMAL_OUTPUT=$(powermetrics -s thermal -n 1 -i 1 2>/dev/null | grep -i "Current pressure level")

    if echo "$THERMAL_OUTPUT" | grep -qi "sleeping"; then
        PRESSURE="sleeping"
    elif echo "$THERMAL_OUTPUT" | grep -qi "trapping"; then
        PRESSURE="trapping"
    elif echo "$THERMAL_OUTPUT" | grep -qi "heavy"; then
        PRESSURE="heavy"
    elif echo "$THERMAL_OUTPUT" | grep -qi "moderate"; then
        PRESSURE="moderate"
    elif echo "$THERMAL_OUTPUT" | grep -qi "nominal"; then
        PRESSURE="nominal"
    else
        PRESSURE="unknown"
    fi

    echo "{\"pressure\":\"$PRESSURE\",\"timestamp\":$(date +%s)}" > "$OUTPUT_FILE"
    chmod 644 "$OUTPUT_FILE"
    sleep 10
done

スクリプトは数秒ごとに熱状態を書き込み、アプリが読み取ります。

ヘルパーを通知で置き換える

notifyd
を root なしで利用できることが分かったため、ヘルパーを完全に廃止しました。アプリは直接通知システムにサブスクライブします。これで実装は大幅に簡素化されました 🎉


温度とファン速度の表示

熱状態と温度・ファン速度の相関を確認するため、メニューバーアプリにグラフを追加しました。

温度読み取り

  • 非公開 IOKit API:最大約 80 °C と報告。iStat Menus / MX Power Gadget は 100 °C 超。
  • SMC(オープンソース版 iStat Menus):より正確だが、各 SoC にキーが異なるため不安定。
private let m1Keys = ["Tp01","Tp05","Tp09","Tp0D","Tp0H","Tp0L","Tp0P","Tp0X","Tp0b"]
private let m2Keys = ["Tp01","Tp05","Tp09","Tp0D","Tp0X","Tp0b","Tp0f","Tp0j"]
private let m3Keys = ["Tf04","Tf09","Tf0A","Tf0B","Tf0D","Tf0E","Tf44","Tf49","Tf4A","Tf4B"]

M3 キーは M4 Max MacBook Pro でも動作するため、SMC を優先し失敗した場合に IOKit にフォールバックします。

グラフデザイン

一目で熱履歴を確認できるコンパクトな可視化を目指しました。グラフは三層構成です:

  1. 背景セグメント:緑(nominal)、黄(moderate)、橙(heavy)、赤(critical)。
  2. 実線:CPU 温度、Y 軸は動的。
  3. 破線のシアン:ファン速度(ファン付き Mac でのみ)。

アプリは 2 秒ごとにポーリングし、10 分分だけデータを保持して clutter を防ぎます。

.onContinuousHover
によるホバーツールチップも追加しました。120 Hz ディスプレイでは
.drawingGroup
を SwiftUI canvas に設定するまで滑らかでなく、GPU レンダリングに切り替えると再びスムーズになりました。


macOS 通知

熱状態が変化した際(例:スロットリング開始)にシステム通知を送る機能も実装しました。VS Code のインスタンスや Docker コンテナを停止するタイミングを把握でき、MacBook Air が過熱しているときはノイズになるかもしれませんが、温度上昇のサインを見逃しません。


ログイン時にアプリを起動

別途 plist を作成せずに

SMAppService
を利用しました:

SMAppService.mainApp.register()    // 自動起動有効化
SMAppService.mainApp.unregister()  // 無効化
SMAppService.mainApp.status == .enabled  // 現在の状態確認

利用方法

Apple Developer アカウントが無いため、リリースされたバイナリは notarization を受けていません。リリースからインストールする際には「プライバシーとセキュリティ」設定で追加の手順が必要です。
Mac が署名済みアプリを許可しない場合は、Xcode でソースコードからビルドしてください(README に手順があります)。


誰かに役立つ情報になれば幸いです!

同じ日のほかのニュース

一覧に戻る →

2025/12/29 7:35

未処理の写真は、実際にどのような姿になるのでしょうか。

## Japanese Translation: --- ### 改良された要約 この記事は、カメラのRAWファイルが鈍く緑色がかった見た目になる理由を説明し、その原因をセンサーのADC出力、カラー・フィルタリング、およびその後の処理ステップに追跡しています。 1. **ADC 出力とコントラスト** – 14ビット ADC は理論上 0–16382 の値を出力しますが、実際のデータは約 2110–136000 の範囲にしかわかりません。これらの限界(黒レベル ≈ 2110、白点 ≈ 136000)を \[ V_{\text{new}} = \frac{V_{\text{old}} - \text{Black}}{\text{White} - \text{Black}} \] で再マッピングするとコントラストが向上します。 2. **カラーキャプチャ** – センサーは光の強度を記録し、色ではありません。ベイヤーフィルタグリッドは各ピクセルに単一の RGB コンポーネントを割り当てるため、初期画像にはピクセルあたり真の RGB の 1/3 のみが含まれます。 3. **デモザイキングとダイナミックレンジ** – デモザイキングは隣接ピクセルを平均化してフルカラー画像を作成しますが、依然として動的範囲が限定されます。線形 RAW データは、環境光や画面ガンマを考慮しないため、典型的なディスプレイ上で非常に暗く見えます。 4. **知覚とデータ** – 人間の明るさ知覚は非線形です。したがって、線形 ADC 値はガンマ補正や sRGB カーブを適用しない限り、過度に暗く見えることがあります。 5. **緑色キャストの起源** – 緑色のチントは、センサーの緑光への高感度、ベイヤーピクセルの 2/3 が緑を捕捉している事実、および単純なデモザイキングから生じます。 6. **ホワイトバランスとガンマ** – ホワイトバランスのスケーリングは線形データに対してガンマ補正より先に適用する必要があります。各チャネルに別々にガンマカーブを適用すると、ハイライトが減色(例えば星が黄色くなる)する可能性があります。 7. **最終画像の現在状態** – 著者の最終画像は未加工であり、カラーキャリブレーションも残留ノイズや完璧なホワイトバランスもありません。これにより、カメラ処理がすでにかなりの数学を行っていることが示されています。 8. **写真家とメーカーへの影響** – これらのステップを理解することで、写真家は RAW ファイルをより効果的に処理でき、メーカーはデフォルト設定、デモザイキングアルゴリズム、およびガンマ処理を改善する潜在的な領域を特定できます。 --- このバージョンは主要なポイントすべてを保持し、不必要な推測を避け、メインメッセージを明確に保ちつつ曖昧な表現を排除しています。

2025/12/29 5:14

ミトロリ―(Mockito)のメンテナとして10年後に退任します

## Japanese Translation: 著者は、10年間にわたるMockitoの長期メンテナとしての任務を辞める意向を表明し、2026年3月に引き継ぎが予定されていると述べています。彼は主に三つの懸念点を挙げています: 1. **JVMエージェントへの急激な移行**(Mockito 5で実装された変更は協議もなく、代替案も提示されず)によるエネルギー消耗。 2. **Kotlinとの非互換性**—特にsuspend関数に関連する問題が重複APIやスパゲッティコードを生み出し、Mockitoのアーキテクチャと整合しない点。 3. 彼自身の興味がServoなど他のオープンソースプロジェクトへ移りつつあること。 著者は、志願者が十分なサポートなしに圧力を感じる中で、Mockitoのメンテナンスが楽しみよりも「やらなければならない仕事」になっていると指摘しています。プロジェクトは新しいメンテナーによる方が最善だと考えており、他者にオープンソースの役割へ参加するよう奨励し、その名誉と特権を強調しています。 --- **(元文を保持したい場合)** > 著者は10年後にMockitoのメンテナとして退任すると発表し、2026年3月に移行が予定されていると述べています。彼はこの決定を、最近の変更—特にMockito 5でのJVMエージェントへの切替えや人気が高まるKotlinとの統合困難—による疲労感の増大に結び付けています。これらの変化は複雑さを増し、APIの重複を生じさせ、メンテナンスを楽しい活動よりも「やらなければならない仕事」に感じさせました。また、彼自身の関心がServoなど他のプロジェクトへ移っていることも述べており、これがハンドオーバーへの動機付けとなっています。著者は新たな志願者にメンテナシップを担ってもらうことで、Mockitoが新しいリーダーシップの下で進化し続けることを促しています。この変更は、新しい視点をもたらし、Kotlin統合問題を解決する可能性があり、オープンソースコミュニティにおける堅牢な志願者支援の必要性を強調すると期待されています。

2025/12/29 6:41

## Unity の Mono に関する問題 **C# コードが想定よりも遅く動作する理由** --- ### 1. 背景 - Unity は C# スクリプトの実行に **Mono**(または IL2CPP)をランタイムとして使用しています。 - 開発者は、ネイティブ C++ コードと比べてパフォーマンスが低下することに気づくことが多いです。 ### 2. 遅延の一般的な原因 | カテゴリ | よくある問題 | 発生理由 | |----------|--------------|----------| | **ガベージコレクション (GC)** | ゲームプレイ中に頻繁にメモリ確保 | GC の停止がゲームスレッドを止め、フレームレートの乱れを引き起こします。 | | **Boxing/Unboxing** | 値型をオブジェクトへキャスト | 一時的なヒープオブジェクトが生成され、収集対象になります。 | | **リフレクション** | 実行時に `System.Reflection` を使用 | 動的型解決のため、リフレクションは遅いです。 | | **文字列連結** | ループ内で `+` を繰り返し使用 | 多くの中間文字列が生成され、GC の負荷が増大します。 | | **大型 MonoBehaviour** | 一つのスクリプトに多くの責務を持たせる | フレームごとの作業量が増え、キャッシュミスにつながります。 | ### 3. プロファイリングのヒント 1. **Unity Profiler → CPU Usage を開く** - 「Managed」と「Native」の時間差に注目します。 2. **Memory タブを使用** - ゲームプレイ中に急増する割り当てを探ります。 3. **Profiler: Mono Runtime を有効化** - GC、JIT、メソッド呼び出しの詳細が確認できます。 ### 4. 最適化戦略 - **割り当てを最小限に抑える** - オブジェクトを再利用;頻繁に使うインスタンスはプールします。 - ループ内で文字列を作る場合は `StringBuilder` を使用。 - **Boxing を避ける** - 値型はそのまま保持し、`object` へのキャストは控えます。 - **リフレクション結果をキャッシュ** - 最初の検索後に `MethodInfo` や `FieldInfo` を保存します。 - **MonoBehaviour の複雑さを減らす** - 大きなスクリプトは機能ごとに分割し、専念型コンポーネントへ移行。 - **ホットパスにはネイティブプラグインを使用** - 性能重視のコードは C++ プラグインへオフロードします。 ### 5. ベストプラクティス | 実践 | 実装例 | |------|--------| | **早期にプロファイル** | 開発初期から頻繁にプロファイラを走らせます。 | | **クリーンコードを書く** | 可読性重視だが、割り当てには注意します。 | | **Update ループは軽量化** | 重いロジックは Coroutine やバックグラウンドスレッドへ移行可能です。 | ### 6. リソース - Unity Manual: [Performance Profiling](https://docs.unity3d.com/Manual/Profiler.html) - Unity Blog: 「Reducing GC Allocations in Unity」 - Stack Overflow の Mono vs. IL2CPP パフォーマンスに関する議論 --- **結論:** Mono がメモリと実行を管理する仕組みを理解し、効果的にプロファイルしてターゲット最適化を施すことで、Unity における C# スクリプトのランタイムオーバーヘッドを大幅に削減できます。

## Japanese Translation: Unity の現在の Mono ランタイムは、モダンな .NET と比べて約 2–3 倍遅く、同一ハードウェア上で実行するとベンチマークで最大 ~15 倍の速度向上が確認されています。このギャップは、Mono の JIT コンパイラが高度に最適化されていないアセンブリを生成する一方、.NET の JIT がスカラー化やレジスタベース演算などの高度な最適化を行うためです。 2006 年に導入以来、Mono は Unity のデフォルト C# ランタイムでした。Microsoft は 2014 年に .NET Core をオープンソース化し、2016 年 6 月にクロスプラットフォームサポートをリリースしました。2018 年、Unity はエンジンを Microsoft の CoreCLR(.NET Core 背後の CLR)へ移植する計画を発表し、パフォーマンス向上とプラットフォーム間の差異を縮小するとともに、一部ワークロードで 2–5 倍のブーストが期待できるとしました。 主なベンチマーク結果は次の通りです: - Mono ベースのエディタ起動時間:約 100 秒 - 同等の .NET 単体テスト:約 38 秒 - リリースモードスタンドアロンビルド:Mono 約 30 秒、.NET 約 12 秒 - 4k×4k マップ生成:.NET 約 3 秒 - int.MaxValue イテレーションの緊密ループテスト:Mono 約 11.5 秒、.NET 約 0.75 秒(約 15 倍遅い) - デバッグモード同じループ:約 67 秒(追加チェックが原因) モダンな .NET の JIT は小さな値型をスカラー化し、不変計算をループ外に持ち出し、レジスタベース演算を使用するなど、Mono が適用できない最適化を実行します。CoreCLR は Span<T>、ハードウェアイントリンシック、SIMD パスといった高度な機能も公開し、特定のコード(例:シンプルノイズ)でパフォーマンスが倍増する可能性があります。 Unity の Burst コンパイラは選択された C# メソッドを LLVM 生成ネイティブアセンブリに変換できますが、適用範囲が限定されています。CoreCLR の JIT はこれらの制約なしで同等かそれ以上の性能を提供できる可能性があります。 CoreCLR への移行は Unity 6.x を対象としており、本番稼働準備は 2026 年またはそれ以降になる予定です。採用されれば、開発者は高速なエディタ起動、短縮されたビルド時間、および Just‑In‑Time コンパイルを許可するプラットフォーム上でより効率的なランタイムコードを体験できます。ただし、Ahead‑Of‑Time (AOT) コンパイルが必要なデバイスは引き続き IL2CPP に依存するため、性能向上はターゲットプラットフォームによって異なる可能性があります。

**プロジェクト概要** 私は、Macが熱的にスロットリング(温度上昇による性能低下)を開始した際に通知する macOS アプリケーションを開発しています。 | そっか~ニュース