自分のアプリが使っているCPU時間とメモリサイズを取得する

SysStats MonitorSysStats Liteでは、iPhone/iPod touchシステム全体の、メモリ使用状況を表示しています。この中には、暗黙的にこれらのアプリケーション自身が使用しているメモリサイズも含まれています。そのことは、PCではほぼ誤差の範囲かもしれませんが、iPhone/iPod touchでは若干影響があります。
例えば、SysStats Monitorですべての画面を開いて、さらにヘルプドキュメントを参照すると、10MB以上のメモリを消費します。これは、iPhone 3Gでは、およそ10%になります。そのため、空きメモリの容量が、実際よりも逼迫しているように見えてしまいます。
このように、小さな影響なのですが、実態をさらに正確に把握するために、SysStats MonitorSysStats Liteでは、自分が使っているメモリサイズを併記しています。
CPUは、さらに影響は小さいですが、バックグラウンドの使用率を正確に把握することできるので、自分が使っているCPU使用率を併記しています(SysStats Monitorのみ)。

http://web.me.com/kimada/images/screenshots/ssm_dashboard_v100.png

これらの情報は、開発者がアプリケーションのデバッグを行う時にも役立つ情報であり、以前、自分のアプリが使用しているメモリサイズを取得するには - The iPhone Development Playgroundでも説明した通り、開発者向けのInstrumentsというツールを使うことで見ることができます。ただ、アプリケーション自体に仕込んでおくことでも、デバッグ時に便利に使用できると思うので、その方法について説明しておきたいと思います。

使用する関数

MacOSXカーネルは、Machをベースとしています。Machでは、UNIXのプロセスに相当するものは、タスクと呼ばれていますが、そのタスクの基本情報を取得するtask_info関数を使用します。

task_info man page

コーディング例

以下に、task_info関数で、メモリ使用量とCPU使用時間を取得して、ログに出力する例を示します。

#include <mach/mach.h>

// CPU使用時間をミリ秒に変換する関数
#define tval2msec(tval) \
((tval.seconds * 1000) + (tval.microseconds / 1000))

//
// CPU使用時間とメモリ使用量を、ログに出力する例
//
+ (void)logCurrentTaskInfo {
    
    //
    // タスク基本情報(TASK_BASIC_INFO)を取得する
    //
    
    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
    
    if (task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count)!= KERN_SUCCESS)
    {
        NSLog(@"%s(): Error in task_info(): %s",
              __FUNCTION__, strerror(errno));
        return;
    }
    
    // メモリ使用量(bytes)
    vm_size_t rss = t_info.resident_size;
    
    // すでに終了したスレッドのCPU使用時間
    uint64_t userTime = tval2msec(t_info.user_time);
    uint64_t systemTime = tval2msec(t_info.system_time);
    
    
    //
    // 実行中のスレッドのCPU使用時間(TASK_THREAD_TIMES_INFO_COUNT)を取得する。
    //
    
    struct task_thread_times_info tti;
    t_info_count = TASK_THREAD_TIMES_INFO_COUNT;
    
    kern_return_t status = task_info(current_task(), TASK_THREAD_TIMES_INFO,
                                     (task_info_t)&tti, &t_info_count);
    if (status != KERN_SUCCESS) {
        NSLog(@"%s(): Error in task_info(): %s",
              __FUNCTION__, strerror(errno));
        return;
    }
    
    // 取得したCPU時間を、TASK_BASIC_INFOのCPU使用時間と合算する。
    userTime += tval2msec(tti.user_time);
    systemTime += tval2msec(tti.system_time);
    
    NSLog(@"resident_size=%u, userTime=%qu ms, systemTime=%qu ms", rss, userTime, systemTime);
    
    
}

この方法で取得したCPU使用時間を2回取得し、その差分を経過時間で除算することで、CPU使用率を求めることができます。

SysStats Lite1.2がリリースされました。

前回のエントリ(SysStats Lite1.2は、審査が長期化するようです。 - The iPhone Development Playground)で、審査が長期化しそうだとお知らせしていた、SysStats Liteのver.1.2がリリースされました。

今回のアップデートは、以下の2つのことを目的としたものであり、目に見える変化は、バッテリの残量を%表示する機能が追加されただけという、地味なものです。

  • iPhone3G、iPod touchに、バッテリー残量の%表示機能を提供する。
  • アプリケーション自体は、OS2.xでも動くようにする。

AppStoreの説明文にも書きましたが、2点ほど特記事項があります。

バッテリーの残量が表示される条件

バッテリーの残量が表示されるのは、このアプリケーションをOS3.0以降で動かしたときだけになります。
そのため、OS2.xをご使用中の方には、今回のアップデートは、何も変化がないことになります。バッテリ残量を取得する機能はOS2.xで実行した時には利用することができないので、そういう形になってしまっているということを、ご理解いただければと思います。
(細かい話をすると、画面の表示項目のズレの微調整はしているので、まったく変化がないということではありませんが。。。)

バッテリー残量の取得精度について

バッテリ残量の取得の精度は、5%単位となっています。3GSでステータスバーに残量の%表示をしている場合には、最大で5%の誤差が出ることがありますが、それは取得している値が不正確というわけではなく、iPhoneの標準機能の制約なので、あらかじめご了承願います。


あともう一点、結果として2.2.1でビルドしたアプリから、3.0で追加されたAPIを使ってみました - The iPhone Development Playgroundにも書いた、技術的な目標もクリアすることができたということになります。

よろしくお願い致します。

SysStats Lite1.2は、審査が長期化するようです。

先日、SysStats Liteのアップデートを提出したことをお知らせしました。

2.2.1でビルドしたアプリから、3.0で追加されたAPIを使ってみました - The iPhone Development Playground

ところが、Appleから、審査に時間がかかるというメールが来ました。具体的な説明は無く、原因は不明なため、残念ながら今のところ対処のしようがない状態です。
バッテリー残量の数値表示は、3GSでは標準でサポートされていますが、3Gにはその機能がありません。3Gユーザの方に便利かと思い、機能追加したのですが、リリースは困難な様相を呈してきました。

以上、取り急ぎ、状況を報告します。
よろしくお願い致します。

2.2.1でビルドしたアプリから、3.0で追加されたAPIを使ってみました

先ほど、SysStats Liteに、バッテリ残量の表示を追加して、アップデートを申請しました。
やることは非常に簡単なのですが、その中で、少しだけ悩んだことがありました。バッテリ残量取得のAPIは、OS3.0以降で追加されたので、ふつうにコーディングすると、OS2.2.1環境では、コンパイルが通りません。かといって、3.0でビルドしてしまっては、2.2.1では動作させることができなくなってしまいます。
ということで、2.2.1でビルドしたアプリから、3.0で追加されたAPIを使う方法を、メモを兼ねて書いておきたいと思います。

これと同じようなことをしたい場合、3.0以降で追加されたAPIについては、NSInvocationクラスを使って、動的に呼び出してやることで、回避することができます。*1

バッテリー残量を取得するためには、UIDeviceクラスに定義されている、以下の2つのプロパティにアクセスする必要があるので、これらを動的に呼び出します。

  • batteryMonitoringEnabled
  • batteryLevel

あと、これらのプロパティは、3.0以降で実行されている場合にのみ存在しているので、存在チェックが必要となります。
以下に、実際のコーディング例を示します。

+ (float)batteryLevel {
    
    float batteryLevel = -1;
    UIDevice *device = [UIDevice currentDevice];
    
    if([device respondsToSelector:@selector(batteryLevel)]) { // batteryLevelプロパティの存在チェック

        // batteryMonitoringEnabledプロパティのメッセージ定義
        NSMethodSignature *setBatteryMonitoringEnabledSig = [UIDevice instanceMethodSignatureForSelector:@selector(setBatteryMonitoringEnabled:)];
        NSInvocation *setBatteryMonitoringEnabledInv = [NSInvocation invocationWithMethodSignature:setBatteryMonitoringEnabledSig];
        [setBatteryMonitoringEnabledInv setTarget:device];
        [setBatteryMonitoringEnabledInv setSelector:@selector(setBatteryMonitoringEnabled:)];
        BOOL batteryMonitoringEnabled = YES;
        [setBatteryMonitoringEnabledInv setArgument:&batteryMonitoringEnabled atIndex:2];

        // batteryLevelプロパティのメッセージ定義
        NSMethodSignature *batteryLevelSig = [UIDevice instanceMethodSignatureForSelector:@selector(batteryLevel)];
        NSInvocation *batteryLevelInv = [NSInvocation invocationWithMethodSignature:batteryLevelSig];
        [batteryLevelInv setSelector:@selector(batteryLevel)];
        [batteryLevelInv setTarget:device];

        // batteryMonitoringEnabledプロパティにYESを設定
        [setBatteryMonitoringEnabledInv invoke];

        // batteryLevelプロパティから値を取得
        [batteryLevelInv invoke];
        [batteryLevelInv getReturnValue:&batteryLevel];
        
    }
    
    return batteryLevel;
    
}

他にも、OSのバージョンをチェックして、処理を振り分けるなど、いくつか方法はあると思いますが、今回の場合は、使用するAPIが限定的なので、上記のような形で実装しました。
とりあえず、非公開APIや、偶然の実行結果を使用しているわけではなく、正式にサポートされている方法なので、審査でリジェクトされることはないと思っていますが、とりあえず、これで結果待ち状態です。

以下に、バッテリー残量表示のイメージを載せておきます。

*1:performSelector:メソッドは、引数や戻り値に、アトミック型が使われている場合には使用できないので、NSInvocationを使用しています。

SysStats Lite 1.1.1がリリースされました。

SysStats Lite 1.1.1が、今朝ほど、AppStoreにリリースされました。変更点は、以下の通りです。

  • 「稼働中のアプリケーション一覧」で、iPod Touchでは、「電話」を表示しないように修正しました。
  • 空きメモリサイズの表示を大きくしました。
  • このアプリケーション自体が使用しているメモリサイズを表示するようにしました。
  • メモリ領域の内訳を測定可能な部分の合計値を100%とした場合の相対比を表示していた円グラフを削除しました。

下側の円グラフがなくなったことで開いたスペースに、空きメモリサイズを移動して、少し大きめに表示するようにしました。
さらに、このアプリ自体が消費しているメモリサイズ(このアプリの使用量)*1を表示するようにしました。
「空き」 + 「このアプリの使用量」が、SysStats Liteを終了し、ホーム画面表示に戻った状態の、おおよその空きメモリサイズと見なすことができます。

あと、空きメモリサイズのことについては、以下のエントリも参考にしていただければと思います。
3GSでも、空きメモリがかなり少なくなることがあるみたいですね。 - The iPhone Development Playground

よろしくお願いします。

*1:「このアプリの使用量」の取得方法については、自分のアプリが使用しているメモリサイズを取得するには - The iPhone Development Playgroundにて説明していますので、そちらをご参照ください。

3GSでも、空きメモリがかなり少なくなることがあるみたいですね。

iPhone 3GSの「倍増したRAM」を体感する。 - The iPhone Development Playgroundで、電話、Mail、SafariiPodの4つのプロセスが立ち上がっていても、空きメモリが、80MBあるということを書きました。ところが、使い込んで行くと、MailやSafariのメモリ使用量が、さらに増えて行き、最終的には、空きメモリがかなり少なくなることがわかりました。
まず、以下のスクリーンショットの抜粋を見てください。

4.1MBと、かなり空きメモリが、少なくなっています。これは、3Gのときによく見られた円グラフの形によく似ていますね。実際の電話、メール、SafariiPodのメモリ使用量の内訳は、以下のようになっています。

注: 赤枠で囲まれた部分が、メモリ使用量になります。
アプリケーション名とプロセス名の対応は、以下の通りです。

アプリケーション名 プロセス名
電話 MobilePhone
メール MobileMail
Safari MobileSafari
iPod MobileMusicPlaye

この4つのプロセスのメモリ使用量を合計すると、100MBを超えています。特に、SafariとMailだけで90MB以上になっています。3Gでは、ありえない数値ですね。もしかすると、空きメモリがあれば、どんどんキャッシュなどにメモリを使うような実装になっているのかもしれませんね。
では、この状況で、メモリを多く使いそうなゲームを起動するとどうなるでしょうか?
試しに、メモリ使用量が40MB以上になるゲームを起動してみましたが、特に問題なく起動され、普通に使うことができました。そして、終了したあとのメモリの状況を見てみました。

そのゲームアプリのメモリ使用量より少し大きいサイズまで、空きメモリが復活しています。
3Gのときは、こういう場合にSafariやメールが強制終了されていましたが、それらのアプリケーションはどうなっているでしょうか?

それらのアプリケーションは、いずれも強制終了されずに実行されたままになっていますが、それぞれのメモリ使用量は、減っています。
これは、各アプリケーションが、キャッシュなどの解放可能なメモリを解放して、空きメモリを増やすような動きをしたのではと想像されます。そもそも、搭載されている物理メモリ容量が大きいので、強制終了までしなくとも、充分な空きメモリが作れたと考えるのが自然なのかなどと考えてしまいます。

このあたりの動作に関する正確な仕様は、ドキュメントなどに書かれていないので、今回の話も「見た目からの想像」になっています。
ただ、3GSの上で、3Gでも動作可能なアプリケーションを実行する分には、空きメモリが少ないことに、あまり過敏になる必要はないと言えるのかなと思います。もちろん、メモリ使用量が100MBにもなるようなアプリが出てくれば、それはまた別の話ですが。。。

3GSは発売されて間もないので、まだまだわからないことがたくさんありそうですね。

7/1 追記

少し、言葉が足りない部分があったので補足します。
ここで説明している内容は、3Gと3GSのハードウェア仕様の違いではなく、物理RAMサイズの違いに焦点を当てています。
3Gでも、空きメモリが少なくなると、同様のこと(各アプリケーションが、キャッシュなどの解放可能なメモリを解放して、空きメモリを増やすような動き)が起きますが、そもそも物理RAMサイズが少ないので、それだけでは必要な空きメモリを作れないことがあります。そんなとき、仮想メモリがないiPhoneでは、スワップアウトもできないので、Safariなどのプロセスが強制終了されることがあります。3GSでは、物理RAMサイズが倍増したので、そういう形で強制終了される場面は少なくなるであろうと思われます。

iPhone 3GSの「倍増したRAM」を体感する。

本日朝、iPhone 3GSを入手しました。
早速ですが、iPhone3GSのRAMは、噂通り倍増したみたいです - The iPhone Development Playgroundでも書きましたが、SysStats Liteを使って、「倍増したRAM」を体感しました。
使用可能なRAMのサイズを示す、「物理メモリサイズ」は、253MBと表示されます。

さらに、これは、電話、メール、SafariiPodの4つの主要アプリケーションプロセスが立ち上がっている状態です。にもかかわらず、約80MB(実際には、SysStats Liteが使っているメモリがあるので、それ以上)の空き領域があります。これまで、メモリ不足で起きていた問題の多くが解消されるのではないでしょうか?
音楽聞きながら、産経新聞も、余裕で読めますよ。
おそらく、Safariを強制終了させたくなる場面もほとんど起きなくなるでしょうね。

iPhone 3GSを手に入れられた方は、ぜひ、お試しください。