Android
 Computer >> コンピューター >  >> システム >> Android

Androidの省電力を支える技術:AOSP「低電力アイランド」とBluetoothSocketSettingsの仕組み

こんなシーンを想像してみてください。カフェに座ってノートパソコンを開き、テーブルの上にはスマートフォン。スマートウォッチは数分おきに振動し、Bluetoothイヤホンからは音楽が流れています。あなたにとっては穏やかな日常ですが、スマホにとっては、膨大な数の小さなBluetoothパケットを絶えずさばく過酷な作業時間なのです。

スマートウォッチが歩数を同期するたび、イヤホンが次の音声データを受け取るたび、バックグラウンドのデバイスが接続確認を行うたびに、スマホ内のメインアプリケーションプロセッサは強制的に起床させられ、データを確認し、処理を決め、そして再びスリープへ。これが何千回も繰り返されれば、5000mAhの大容量バッテリーでもあっという間に心許なくなってしまいます。

Androidのエンジニアたちはこのパターンに目をつけ、こう考えました。「小さなBluetoothデータごとに大きなCPUを起こす必要なんてないのでは?」「退屈で反復的なBluetoothトラフィックだけを専門に扱う、小さな補助脳を用意して、メインCPUにはゆっくり休んでもらうのはどうだろう?」——まさにここから生まれたのが、「低電力アイランド」(Low Power Island、略してLPI)という概念です。

現代のAndroid Bluetoothアーキテクチャ、特にAOSP 16世代以降では、Bluetooth処理のかなりの部分を、Bluetooth無線部の近くに置かれた専用の低電力プロセッサにオフロードできます。この小さなプロセッサはBluetoothコントローラーやSoCに組み込まれ、非常に高い電力効率で動作するよう設計されています。メインCPUよりはるかに少ない電力しか消費せず、本格的なアプリケーションプロセッサのようにバッテリーを食いつぶすことなく、常時稼働できます。Android側の役割は、どのトラフィックをこのアイランドに住まわせ、どのトラフィックをメインCPUに任せるべきかを判断することです。

では、Androidはその判断を実際にどう行っているのでしょうか? ここで登場するのがBluetoothソケットと、BluetoothSocketSettingsという存在です。

通常のアプリでBluetoothSocketを開くとき、それは単にバイト列を送受信するためのパイプを開いているように感じられます。しかし内部では、フレームワークはもっと深い問いを立てています。「このパイプは、メインCPUを起こす大通りを通すべきか? それとも、低電力アイランドの専用道路網に直接つなげられるのか?」と。

最新のAOSP Bluetoothスタックでは、その答えはBluetoothSocketSettingsという小さな設定オブジェクトによって表現されます。このクラスにより、システムレベルのコードはソケットの振る舞いを記述でき、データを通常のホスト経路に置くか、低電力プロセッサで終端するハードウェアデータパスにオフロードするかを指定できます。

内部にはDATA_PATH_NO_OFFLOADDATA_PATH_HARDWARE_OFFLOADといったフィールドがあり、さらにhubIdendpointIdrequestedMaximumPacketSizeといった追加情報が、LPI環境でのパケットルーティング方法をコントローラーに伝えます。

外見上は普通のBluetoothSocketと何ら変わりません。しかしBluetoothフレームワークの内部では、そのソケットには特別なメタデータが付与され、Bluetoothスタックに対して静かにこう告げているのです。「これは特別なソケットだ。アイランドへ送れ」と。

その後、ホストスタックはLPPオフロードマネージャーおよびソケット専用のHAL(Hardware Abstraction Layer)とやり取りし、ソケットの開閉のたびに低電力プロセッサへ通知し、データ処理の責任を引き渡します。

カフェの例えで言えば、以前はすべての客が注文を直接メインのバリスタに叫んでいました。しかし低電力アイランドとBluetoothSocketSettingsのおかげで、Androidは「いつものエスプレッソの注文はサイドカウンターの若手バリスタに回していい。変わったカスタムドリンクだけメインのバリスタに頼もう」と言えるようになったのです。ユーザー体験は同じまま、カウンターの裏側は格段に整然とし、無駄なエネルギーも大幅に減りました。

本記事では、このハイレベルなストーリーから、実際のAndroid APIの世界へとズームインしていきます。BluetoothSocketSettingsがフレームワークでどう定義されているか、ハードウェアオフロードはどう要求するか、そして一見難しそうなhubIdendpointIdが平易な言葉で何を意味するのかを見ていきましょう。

目次

  1. BluetoothSocketSettingsの構造

  2. HALの中身:Bluetoothオフロードの実際の仕組み

  3. CPUが眠ってもBluetoothは止まらない:電源管理の実際

  4. 開発者がBluetoothSocketSettingsを活用するには

  5. まとめ:「賢く眠る」ことのエレガンス

BluetoothSocketSettingsの構造

ここまで、BluetoothSocketSettingsを「スマホ内部のどこかにある日当たりの良い低電力アイランドへパケットを送ってくれる魔法のチケット」のように語ってきました。では、そのチケットがコード上でどんな姿をしているのか、実際に覗いてみましょう。

AOSP(Android Open Source Project)のツリーを開いてフレームワーク層まで辿ると、frameworks/base/core/java/android/bluetooth/BluetoothSocketSettings.javaというクラス定義が隠れています。一見するととても小さく、これほどバッテリーを節約できるとは思えないほどシンプルです。しかし、この小さなクラスこそが、Bluetoothスタックに対して「ソケットのデータをどこに流すべきか」という秘密の指示を運んでいるのです。

簡略化した定義は以下のような形です:

public final class BluetoothSocketSettings implements Parcelable {
 public static final int DATA_PATH_NO_OFFLOAD = 0;
 public static final int DATA_PATH_HARDWARE_OFFLOAD = 1;
 private int mDataPath;
 private int mHubId;
 private int mEndpointId;
 private int mRequestedMaxPacketSize;
 public BluetoothSocketSettings(int dataPath, int hubId, int endpointId,
 int requestedMaxPacketSize) {
 mDataPath = dataPath;
 mHubId = hubId;
 mEndpointId = endpointId;
 mRequestedMaxPacketSize = requestedMaxPacketSize;
 }
 public int getDataPath() { return mDataPath; }
 public int getHubId() { return mHubId; }
 public int getEndpointId() { return mEndpointId; }
 public int getRequestedMaxPacketSize() { return mRequestedMaxPacketSize; }
}

新しいソケットがAndroid Bluetoothで作成されるとき、システムまたは特権サービスは、こうした設定オブジェクトをスタックに渡すことができます。重要なのはDATA_PATH_HARDWARE_OFFLOADという行です。これこそがBluetoothシステムに向けて「ねえ、このトラフィックはコントローラーのマイクロプロセッサ上に置いて、メインCPUを起こさないでちょうだい」と告げるスイッチなのです。

hubIdendpointIdは、いわばアイランド上の住所のようなものです。ファームウェアに対して、特定のソケットにどの論理ポートやキューを使うかを指示します。またrequestedMaxPacketSizeはバッファ確保量の調整に使われ、スループットと省電力性のバランスを取るのに役立ちます。

ここで疑問が浮かぶかもしれません。「この小さなJavaオブジェクトは、どうやって実際のハードウェアまで届けられるのだろう?」答えはHAL(ハードウェア抽象化レイヤー)にあります。BluetoothSocket.connect()のような呼び出しは、最終的にbtif_sock.ccbtif_core.ccなどのネイティブコードへと流れ込みます。そこでは次のような痕跡が見られます:

bt_status_t status = BTA_SockConnect(type, addr, channel, flags);
if (settings.data_path == DATA_PATH_HARDWARE_OFFLOAD) {
 BTIF_TRACE_DEBUG("Configuring socket for hardware offload path");
 BTA_SockSetOffloadParams(settings.hub_id, settings.endpoint_id);
}

この断片はシンプルに見えますが、責任範囲の大きな転換点を表しています。すべてのパケットをホストスタックへ送り上げる代わりに、Bluetoothコントローラーがデータパスの所有権を主張できるようになったのです。SoC内のBluetoothファームウェアが引き継ぎ、パケットの再送制御、ACK応答、フロー制御を、メインCPUを頻繁に起こすことなく処理します。

このような接続中にデバイスのカーネルログを監視すれば、次のようなログを目にすることもあるでしょう:

bt_vendor: enabling LPI offload for handle 0x0041
bt_controller: lpi path active, cpu wakelocks released

このログ行こそ、データパスが低電力アイランドへの移行に成功した静かな証明です。

人間的な言葉に訳すなら、スマホは「このBluetoothの会話は十分予測可能だからミニプロセッサに任せられる」と判断し、大きなCPUに丁寧にこう告げたわけです。「君はお昼寝してて。僕がやっとくよ」。

次のセクションでは、この旅路をさらに一段深く、HALとファームウェアの境界まで潜り込みます。ソケット設定がどのようにコントローラーチップ内部の実際の低電力データルーティングへと変換されるのか。ここからが本当のハードウェアマジックであり、ミリワット単位の節約が積み重なり始める場所です。

HALの中身:Bluetoothオフロードの実際の仕組み

ここまでは主にAndroidのJava層とネイティブ層、つまりフレームワークやシステムサービスが暮らす快適なマンションの話でした。しかし、その下には巧妙な機械類が詰まった地下室があります。それがハードウェア抽象化レイヤー(HAL)です。ここでAndroidは「オブジェクト」という言葉を話すのをやめ、オペコードとバッファの言語を話し始めます。ソフトウェアとシリコンを結ぶ橋渡しがHALなのです。

BluetoothSocketSettingsのフラグがシステムに「ハードウェアオフロードを使用してください」と指示しても、その要求が魔法のようにチップへ瞬間移動するわけではありません。要求はBluetoothスタックを一段一段降りていき、JNI(Java Native Interface)を通ってC++へ、そしてhardware/interfaces/bluetooth/内で定義されるHALへと到達します。

Android 14から、特にAOSP 16では、HALはより賢くなりました。LPIの機能を理解し、特定のソケットトラフィックをLPIへルーティングできるようになったのです。

簡略化したHAL関数を覗いてみましょう。架空のコードではなく、bluetooth_audio_hw.ccbluetooth_socket_hal.ccで見られる内容に近いものです:

Return<void> BluetoothHci::createSocketChannel(
 const hidl_string& device, const BluetoothSocketSettings& settings,
 createSocketChannel_cb _hidl_cb) {
 int fd = -1;
 if (settings.data_path == DATA_PATH_HARDWARE_OFFLOAD) {
 ALOGI("LPI offload requested for socket on hub %d endpoint %d",
 settings.hub_id, settings.endpoint_id);
 fd = controller->allocateLpiChannel(settings.hub_id, settings.endpoint_id);
 } else {
 fd = controller->allocateHostChannel();
 }
 _hidl_cb(Status::SUCCESS, fd);
 return void();
}

平たく言えば、このメソッドはBluetooth交差点の交通整理員のようなものです。ソケット設定を見て、データをどの道路に流すかを決めます。DATA_PATH_HARDWARE_OFFLOADが設定されていれば、データパスは通常のホスト側バッファではなく、コントローラー内部のMCUに配線されます。

controller->allocateLpiChannel()の呼び出しは、HALが「OKチップさん、低電力プロセッサの中だけで完結するキューを作ってください」と依頼する場面です。このマイクロコントローラーはBluetooth無線部に物理的に近い位置にあり、ACK応答、小規模なデータバースト、さらには一部のプロトコルタイミングまで、本来ならメインCPUの起床が必要だった処理を自力でこなせます。

チャンネルが作成されると、Androidフレームワークやアプリからは通常のファイルディスクリプタとして見えます。まるでソケットが完全にローカルであるかのように。魔法の正体は、このディスクリプタがLinuxカーネルのバッファではなく、ファームウェア管理のメモリとDMAパスによって支えられていることにあります。

デバッガを接続したりコントローラーのログをダンプしたりすれば、次のようなログが見られるかもしれません:

bt_lpi_mcu: channel 0x03 opened for handle 0x0041
bt_hci: diverting ACL packets to LPI path
bt_lpi_mcu: sleeping host processor

3行目のsleeping host processor(ホストプロセッサをスリープさせる)——これは電力設計エンジニアにとって夢のようなログです。スマホはBluetoothを生かしたまま、CPUサブシステムの大部分を文字通りシャットダウンしているのです。

ここはまた、QualcommやBroadcomといったベンダーが独自の工夫を盛り込む場所でもあります。彼らのHALには「キープアライブタイマー」「集約間隔(coalescing interval)」「ファームウェア駆動の再送制御」などの追加フックが含まれることが多く、メインプロセッサが勤務外でも接続が滑らかに感じられるようにしています。

高い視点から見ると、パイプラインは次のようになっています:

App -> Bluetooth Framework -> JNI -> btif_sock -> HAL -> Controller MCU (LPI)

各レイヤーは、バトンを次のレイヤーにきれいに渡すために必要な最低限の情報だけを持っています。HALは翻訳者として振る舞い、高レベルな設定をチップファームウェアが実行できる低レベルなコマンドへと変換します。

スマートウォッチがパケットを送ったり、イヤホンが音声データを要求したりする頃には、メインCPUは瞬きすらしません。トランザクションの全工程がBluetoothコントローラーの小さな領域内で完結し、電力をごくごくと啜るように消費するのです。

次のセクションでは、このオフロードアーキテクチャがAndroidの電源管理システム(wakelock、Dozeモード、カーネル連携など)とどう統合されているか、そしてメインCPUが眠っていても接続が一度も途切れない仕組みを探ります。

CPUが眠ってもBluetoothは止まらない:電源管理の実際

さて、ソケットのオフロードがアプリ層からHALを経由して、Bluetoothチップ内の小さなMCUに着地するところまで見てきました。では、その後はどうなるのでしょうか? ファイル転送や音声ストリーミングが進行中なのに、スマホのメインCPUが昼寝を決め込んだら? 接続が切れるリスクはないのでしょうか?

ここで活躍するのがAndroidの電源管理の振付(コレオグラフィ)です。3人の出演者によるダンスが繰り広げられます。Power HALBluetoothスタック、そしてカーネルのwakelockシステムです。

Bluetoothソケットが低電力アイランド向けに設定されると、AndroidのBluetoothスタックはカーネルに対し、「この接続はメインCPUの助けなしで維持できます」と合図を送ります。内部的には、通常Bluetoothトラフィック中にプロセッサを起きたままにしておくwakelockタイマーが解除または縮小されます。カーネルログには次のような記録が残るかもしれません:

wakelock: release "bt_wake" (LPI mode active)
bt_controller: firmware handling link supervision locally

このメッセージはシステムエンジニアにとって黄金の情報です。コントローラーが接続の全権を握ったことを意味します。Bluetoothファームウェアは監督タイムアウト(supervision timeout)の監視、再送制御、暗号化カウンタの維持を担っています。

電源マネージャーの視点では、メインCPUへの割り込みが発生しないため、Bluetoothデバイスは「アイドル」状態に見えます。一方でコントローラーMCUは、自身の低電力クロックドメインを使って、イヤホンやスマートウォッチと静かにパケットを交換し続けています。

これを調停するため、Bluetooth HALはトラフィック量の変化をPower HALへ知らせる小さなコールバックを公開しています。bt_vendor_qcom.ccには次のようなコードがあるかもしれません:

void bt_lpi_activity_update(bool active) {
 if (active)
 power_hint(POWER_HINT_LPI_ACTIVITY, 1);
 else
 power_hint(POWER_HINT_LPI_ACTIVITY, 0);
}

activeがゼロになると、Power HALはより深いシステムスリープ状態(Suspend-to-RAMなど)への移行を許可できます。Bluetoothが自分で接続を守り続けるからです。

本当の魔法は、ユーザーがこの一切に気づかないことです。画面は消え、CPUコアはゲートされ、スマホは「眠っている」ように見えても、Bluetoothオーディオは再生され続け、スマートウォッチは同期され、スマホは発見可能なままです。

ほとんど詩的ですらあります。メインプロセッサは夢を見て、コントローラーは静かにハミングし、プレイリストは何事もなかったかのように流れ続けるのです。

実機のAndroidデバイスで確認したい場合は、次のコマンドを使います:

adb shell cat /sys/kernel/debug/wakeup_sources | grep bt

ストリーミング中にもかかわらずbt_wakeのカウンタが低いまま推移していたら、おめでとうございます! 低電力アイランドのオフロードが美しく機能している証拠です。

次のセクションでは、ファームウェアの深淵から地上へ戻り、これらすべてが普段の開発者の世界にどう収まるのかを見ていきます。アプリ開発者やシステム開発者は、これらのソケット設定を直接操作したり恩恵を受けられたりするのでしょうか? そして理解を深めることで、電力を「ごくごく飲む」のではなく「啜るような」Bluetoothアプリを作るにはどうすればいいのでしょう?

開発者がBluetoothSocketSettingsを活用するには

Bluetoothスタックの奥深くを覗いたところで、今度は私たち開発者が実際に暮らす階層へと戻りましょう。「ハードウェアの手品は確かにすごいけど、それで私には何ができるの?」と思っているかもしれません。

ここからが面白いところです。低電力アイランドは主にシステムレベルの機能ですが、その仕組みを理解しているだけで、より省電力で予測可能性の高いBluetoothアプリを設計する助けになります。

フレームワークレベルでは、アプリからLPIを直接オン・オフすることはできません。そのスイッチはBluetoothServiceやBluetoothSocketManagerServiceといったシステムコンポーネントの深部にあります。しかし、BluetoothSocketBluetoothServerSocketを使うたびに、データはLPIオフロードが利用可能かどうかを判定するそれらの層を黙って通過しています。

つまり、あなたのアプリは自動的に恩恵を受けられます。ただし、CPUを不必要に起きたままにするようなことをしなければ。たとえば、適切なスレッドスリープの使用、ビジーループの回避、Android標準のBluetooth I/Oストリームによるバッファリングへの委譲などを守れば、オフロードロジックの信頼を得たままいられます。

AOSPのsystem serverログをBluetoothソケット接続時に覗くと、次のような記録に気づくかもしれません:

BluetoothSocketManager: Offload eligible socket detected, enabling LPI mode
Bluetooth HAL: LPI channel activated for fd=42

この短い行が示しているのは、あなたのソケットが指一本動かすことなく、静かにアイランド経由へ再ルーティングされたということです。

内部では、フレームワークがBluetoothSocketSettingsオブジェクトを生成し、ソケットのオープン時にチェーンの下位へ渡しています。擬似的なJavaコードで表すと次のようになります:

BluetoothSocketSettings settings =
 new BluetoothSocketSettings(
 BluetoothSocketSettings.DATA_PATH_HARDWARE_OFFLOAD,
 /* hubId */ 1,
 /* endpointId */ 2,
 /* maxPacketSize */ 512);
BluetoothSocket socket = adapter.createSocket(device, settings);
socket.connect();

もちろん、これはまだ公開SDKの一部ではありませんが、システムアプリや特権フレームワークは同様の呼び出しで、トラフィックの扱い方を記述しています。

では、なぜ開発者であるあなたがこれを気にかけるべきなのでしょうか? そうした経路が存在すると知っていれば、それを念頭に設計できるからです。たとえば次のような工夫が考えられます:

  • 小さなBLE書き込みを1件ずつ送らずにまとめて送信(batching)し、コントローラーがオフロードバッファ内で効率よく処理できるようにする。

  • 頻繁な接続・切断の繰り返しを避ける。これを繰り返すと、スタックはメインCPUを何度も起こすことになる。

  • バックグラウンド転送を、低電力バッファの制限内に収まるよう設計する(小さいチャンク、長めの間隔を意識する)。

要するに、データパターンが予測可能であればあるほど、ホストを起こさずにアイランド内に留まる可能性が高くなるのです。

もしカスタムAndroidデバイスや組み込み製品向けのシステムソフトウェアを開発しているなら、さらに先へ進めます。HALの挙動を調整し、カスタムのhub IDやendpoint IDを割り当て、ファームウェアがDMA転送に使う最大パケットサイズまでチューニングできます。これにより、ほぼ完全にオフロードされた状態で動作するBluetooth機能——低エネルギーのテレメトリストリーミングやウェアラブルセンサーの同期など——を構築できるのです。

そこまで到達すると、BluetoothチップはメインOSが眠っている間も働き続けるミニサーバーとなり、驚異的なバッテリー寿命と素早い再接続を実現してくれます。

最終セクションでは、話を締めくくり、全体像を振り返ります。なぜBluetoothSocketSettingsと低電力アイランドの組み合わせが、Androidの「見えないエンジニアリング」の最もエレガントな例の一つなのか。基調講演で語られることは滅多にないけれど、真夜中になってもスマホのバッテリーが残っているという形で毎日実感できる、静かな勝利の一つなのです。

まとめ:「賢く眠る」ことのエレガンス

少し立ち止まって振り返ってみましょう。私たちの旅は、働きすぎのバリスタがいる喫茶店から始まりました。そして、メインのバリスタが席を離れている間も店を切り盛りしてくれる隠れた助手——低電力アイランド——を発見しました。

humbleなBluetoothソケットの旅路を追い、BluetoothSocketSettingsに包まれ、HALを旅し、最後にはコントローラー内部の小型プロセッサにたどり着きました。大きなCPUが夢を見ている間も、その小さなプロセッサは静かに稼働し続けます。

そここそが美しさの核心です。AndroidのBluetoothオフロード機構は、「見えないエンジニアリング」の最もエレガントな例の一つなのです。新しいAPIや派手なアニメーションで自己主張することはありません。ただ黙々と、バッテリー持ちを延ばし、Bluetoothの信頼性を高め、スマホの操作感を滑らかにする。あなたがその存在に気づかないまま、すべてが実現されています。

技術的な観点から見ると、その卓越性はバランスにあります。システムは必要なときにはフル機能のソケットと高度なプロトコル処理を提供しつつ、一般的なデータフロー——オーディオ、テレメトリ、通知、心拍ストリーミング——については低電力コントローラーにハンドルを委ねます。Androidが「委任術」を覚えた、と言ってもいいでしょう。

画面が消えたままのスマホでスマートウォッチが同期するたび、長時間のフライト中もバッテリーを食いつぶさずにイヤホンが接続され続けるたびに、あなたはBluetoothSocketSettingsと低電力アイランドのフレームワークが働く姿を目撃しています。それらは、現代のAndroid設計におけるより大きな哲学——インテリジェンスをハードウェアに近づける——の一部なのです。チップに自律的なタスクを処理させればさせるほど、メインプロセッサを休ませることができます。

開発者やシステムエンジニアにとって、このアーキテクチャの理解は単なる学問ではありません。自分自身の機能設計のヒントになり得ます。カスタムAndroid ROMの構築、ウェアラブル向けファームウェアの最適化、Bluetoothチップ搭載IoTデバイスの開発——いずれにおいても教訓は明白です。メインCPUにすべてのパケットの子守をさせてはいけない。できる限りオフロードし、休むべきときには休む。そうすれば、デバイスは何時間もの追加稼働時間であなたに恩返ししてくれるでしょう。

次にイヤホンを接続して、スマホが熱を持たず、バッテリー残量がほとんど減っていないことに気づいたら、思い出してください。内部の深くでは、小さなBluetooth MCUが重労働をすべて引き受け、メインCPUは低電力ハンモックでお昼寝を楽しんでいるのだと。

それこそが、Androidの低電力アイランドとBluetoothSocketSettingsの静かな天才性です。これは単にBluetoothの話ではありません。デバイスに「忙しくする」のではなく「賢くなる」ことを教えるという話です。そして、もしかするとそれは、私たち人間にとっても心に留めておきたい教訓なのかもしれません。

  1. FastbootDとは?AndroidデバイスにカスタムROMをインストールするためのステップバイステップガイド

    以前は、AndroidスマホにカスタムROMをインストールするのは比較的簡単でした。ブートローダーのロックを解除し、カスタムリカバリーを導入すれば、ほぼ準備完了だったのです。 しかし状況は変わり、TWRPやFastbootでカスタムROMを書き込めない場合、それは間違ったレベルで書き込みを試みている可能性があります。近年のスマホの多くには「FastbootD」と呼ばれるもう一段階のモードが存在し、システムパーティションへの変更はここで行う必要があります。 この記事では、FastbootDとは何か、FastbootDモードへ起動する方法、そしてカスタムROMやGSI(ジェネリックシステムイメージ

  2. なぜAndroidスマホのOSアップデートはこんなに遅いのか?その理由を徹底解説

    Android 5.0 Lollipopは11月にリリースされました。しかし3ヶ月後の2月、TechCrunchの報道によると、Lollipopを搭載したAndroidデバイスは全体の2%未満だったといいます。そして3月になった今でも、その数字は約3%前後にとどまっています。 一方、最大のライバルであるAppleと比較してみましょう。Appleは9月にiOS 8.0をリリースし、わずか2ヶ月後の11月には60%以上のiPhoneが最新バージョンで動作していました。しかも、過去のiOSバージョンの普及スピードから見ると、これはむしろ「遅い」ペースだったのです。 いったいなぜでしょうか?なぜAnd