2012年6月21日木曜日

AndroidのSparseArrayは本当に速いのか測定してみた

前回のエントリで紹介したBundleSaverを作成する際に、SparseArrayというクラスの存在を知りました。

SparseArrayは、Android向けにつくられたパフォーマンスに優れたHashMap代用とのことで、その使い方と気になる性能について調べてみました。
実際に測定することでメリットやデメリットがわかったので、ご紹介します。

  1. SparseArrayってなぁに?
  2. どう使うの?
  3. HashMap と SparseArray の性能比較
  4. 考察
  5. まとめ
  6. 参考(計測に利用したクラス)

  1. SparseArrayってなぁに?

SparseArrayは、キーにintを利用することを前提としたHashMapだと考えると分かりやすいかと思います。
(Integerではなく、intです。)

また、SparseArrayでは、値にObject型を格納できますが、値がint, booleanの場合は、それぞれSparseBooleanArray, SparseIntArrayというクラスも用意されています。

内部にはキー用の配列、値用の配列を個別に持ち、配列添字による取得以外は、基本的に二分木探索で目的の値を取得するつくりになっています。
また、保持するキーと値のペアを削除する際は、内部的に削除フラグを立て、随時寄せる処理を行っていました。
おそらく配列を有効利用することで不要なメモリ消費を押さえる目的と思われます。

  2. どう使うの?

基本的にはHashMapと同様です。

SparseArray.keyAt(int index)で、キー配列の添字に該当するキーを取得します。
SparseArray.valueAt(int index)で、値配列の添字に該当するキーを取得します。
もちろん、キーが分かっているので、SparseArray.get(int key)でも構いません。

  3. HashMap と SparseArray の性能比較

SparseArrayは、Androidの静的解析ツールLintでも対応すべき対策として指摘されるとのことなので、効率的なのだろうとは思いますが、実際にどういう面で効率的なのかが分からなかったので、性能調査を行いました。

調査環境
  • Android Emulator
  • Android 2.3.3

調査方法
  • onCreate時に、HashMapとSparseArrayを生成し、任意の数のキー、値のペアを操作する。
  • 操作内容は、put, getとし、対象の全キー操作完了までの時間を計測する。
  • なお、キー一覧の生成時間は計測時間から除外する。

計測結果
 計測結果は以下のとおり

Put操作















Get操作















Put/Get操作の合計















  4. 考察


上記結果から、処理対象が増加するほどSparseArrayのほうがパフォーマンスが出ると見なすことができます。

興味深いのは、処理時間の内訳です。
ご覧のとおり、put処理では、HashMapがかなり時間がかかっているにも関わらず、get処理では、HashMapのほうが処理速度は速くなっています。

これはそれぞれが採用するアルゴリズムの違いが差として現れていると考えられます。
HashMapが採用するアルゴリズムはハッシュ探索なので、値格納時にハッシュ関数処理とハッシュ表の作成処理が必要です。
その代わり、探索はハッシュ関数による格納位置を求めるだけですみます。
これがputが遅く、getが速い理由です。

一方、SparseArrayは探索アルゴリズムに二分探索木を採用しています。
これは、値格納時にキーの大小比較を行う程度で済みますが、探索は二分探索木で行われるため、線形探索に比べれば圧倒的に速いとはいえ、
件数が多くなるほど時間がかかるようになってしまいます。

メモリの観点から

メモリ利用率からも測定を行いました。
(とはいえ、GC発生回数という簡易な測定です…。目安にはなるかと思います。)

GC発生回数















HashMapのほうがGC発生頻度が高く、SparseArrayがメモリを効率的に利用していることがわかります。


  5. まとめ 

まとめです。
  • 速度面、負荷面の観点で、SparseArrayはHashMapより高いパフォーマンスを発揮する。
  • ただし、Put後のGet処理が多く、性能が求められる用途(キャッシュなど)ではHashMapの利用を検討してもよい。
  • その際はソフトリファレンス等を効果的に使い、メモリ圧迫を回避する手段を講じること。


  6. 参考(計測に利用したクラス)

性能測定で利用したクラスです。
これらのTaskを別途準備した計測用のクラスに渡して計測を行いました。

こういう計測を行ったほうが効果的だよというのがあればご指摘もらえればと思います。


-------------------
以上です。HashMapとSparseArrayの使い分けに関して、何かの参考になれば幸いです。
ではでは。


2012年6月16日土曜日

振る舞いのよいAndroidアプリのために。BundleSaver。


Androidで、Bundleへの保存/復元を自動で行ってくれるユーティリティをつくりました。
GitHub / monochromegane / BundleSaver

2012/07/08
BundleSaverのバージョンアップと使用手順の変更を行いました。
下記ページもあわせてご覧ください。
続・振る舞いのよいAndroidアプリのために。StateSaver。



今回のアジェンダです。
  1. Bundleってなぁに?
  2. Bundleの問題点
  3. BundleSaverで解決

  1. Bundleってなぁに?

AndroidのActivityにはライフサイクルがあります。
別のアプリが前面に来るなどして、バックグラウンドにまわった後、他のアプリによってメモリが不足した場合にActivityが破棄されることがあります。
このとき、メモリ上にだけ展開されていたインスタンス変数などの値も破棄されてしまいます。

再度呼び出されるときは、もう一度Activityを生成しなおすため、前回の状態は失われています。
ユーザ側の立場に立ったとき、前回と同じ状態になるよう復元する必要があります。
このとき、状態を保存、復元する入り口としてAndroidが準備してくれているのがBundleです。

実装方法などは、以下にわかりやすくまとまっています。
Y.A.M の 雑記帳 / Android Bundle で状態を保存


  2. Bundleの問題点

Bundleは実装内容は決まりきっているわりに、インスタンス変数といったアプリ開発中に随時変わっていくところを対象としているので、めんどくさくなってついつい変数との整合性をあわせるのを後回しにしがちです。

保存する必要がある新しい変数つくると、それに応じたreadする処理、writeする処理を書かなくてはならないというのが問題だと思います。

  3. BundleSaverで解決

BundleSaverは、変数の宣言時にアノテーションでBundle対象であることを記載するだけで、保存、復元は自動で行ってくれるユーティリティです。

こんな感じで使います。


対象となる型は、Bundleに格納できるもの + α です。
プリミティブ型、ラッパーオブジェクト、Bundle, Parcelable, Serializable などが格納できます。
詳しくはGitHub上のREADMEをご覧ください。

実装としては、対象のActivityに対して、BundleTargetアノテーションが設定されているインスタンス変数をリクレクションで取得、変数名からキーを生成してBundleへ格納。
復元時は、その逆を行うといった感じです。

対象となるインスタンス変数にアノテーションをつけるだけで、保存/復元処理はBundleSaverを呼ぶだけなので、使いやすくなるんじゃないかと思います。
状態の保存/復元は地味ですが、ユーザ側にとって振る舞いのよいアプリケーションをつくるのに重要な処理なので、ぜひ使ってみてください。

-------------------------
ここはこうした方がよいよとかご指摘ありましたら、教えてもらえれば幸いです。
ではでは。



Android Night in Fukuoka vol.26 に参加しました

6/4にAndroid Night in Fukuoka vol.26に参加してきました。

Android Night in Fukuoaka vol.26

今回もLT中心でした。
内容をざっとまとめておきます。


Lightning Talk


Google I/O報告会2012のお知らせ

6/27から開催されるGoogle I/Oの報告会が全国各地で開催されるとのこと。
Google I/O報告会2012

開催から一週間ちょいの最新ネタが福岡で聞けるスバらしい会。これは行きます。


NFC

NFCアプリでiDmを利用する際の注意事項について。


情報整理術

未来から来たカナイさんによる21世紀の情報整理術について。電子書籍化の取り組みとそれによって得たもの。


JenkinsとXFD

XFD(eXtream Feedback Device)と呼ばれる「過激な」フィードバック装置の紹介。
Jenkinsと組み合わせてテスト、ビルド失敗時にランプがつく装置のデモ。
実際に使っているプロジェクトがあるとのことで、こういう遊び心がある風土は見習いたいです。


Androidの描画処理

Androidの描画処理をクラス図とシーケンス図を用いて説明。Viewのinvalidate処理を追っていくと、最終的にHandlerを継承した親Viewでキューへの登録処理が行われますよという話。
Viewの中でもHandlerが最終的に利用されており、Looper等もうまく利用すれば面白いことができる(かも)。

Androidのソースは継続的に読む機会をつくりたいな。


アプリ紹介:SoundFire

NFC、音声認識をうまく利用したYoutube再生プレイヤー。NFC技術単体では興味が湧かなかったけど、アプリ起動など具体的に使うシーンを考えると面白くなってくるなと思えるLTでした。


アプリ紹介:日本Androidの会福岡支部アプリ

同じくNFCと組み合わせた日本Androidの会福岡支部用のアプリ開発進捗報告。出欠確認に利用できるのは、いろいろと応用ができそう。 

  


ソフトウェア勉強会について


タガさん発案のソフトウェア系の勉強会の方向性について。基礎コースとゴリゴリコースに分かれてやっていくことになりました。
ゴリゴリコースに参加予定。モノをつくるというより、ペアプロなどでお互いの開発スキルを伸ばしていく会になりそう。かなり楽しみ。



所感

今回もハードからソフトまでAndroidの切り口で色々な話が聞けました。


この会のよいところは、Androidというカテゴリでいろんな分野のひとが集まること。
普段興味を持っている範囲から踏み出したところの話を聞いて、自分の中で考えが広がっていくところだと思います。
興味を持ったところは、直接LTした人と話をしてもよし、更に特化した勉強会のお知らせも会の中であるので、そっちに行ってもよし。

いい情報交換の場として参加させてもらっています。

次回は7/2です。Androidに興味ある方は是非。

Android Night in Fukuoka Vol.27

アジャイルサムライ福岡道場 零の巻 に行ってきました。

6/14にアジャイルサムライ福岡道場 零の巻 に行ってきました。

今回は、零回ということで、参加者の想いや今後の方針を確認がメインの会でした。
特に、ワールドカフェでのお題「価値ある成果」とは何かを話し合うことで、参加者が今後、アジャイルに関わらず、システム開発に関わるときの、根本の大事なことを改めて気づかせてもらえた、いい会だったと思います。

アジェンダは以下のとおり
  1. チェックイン
  2. LT: 僕のアジャイル黒歴史と希望
  3. ワールドカフェ
  4. 今後の方針
  5. その他、関連イベント紹介

   1. チェックイン

まずは自己紹介。
参加者は20名程度。皆さん、ほぼ何らかのかたちで開発に携わっているみたいです。
アジャイル経験者は1/4程度。
ちなみに全部男性でしたw

発注側の立場の方もいらっしゃったので、偏らない意見交換の場になれるといいですね。


   2. LT: 僕のアジャイル黒歴史と希望


@gokingさんによるLT。
アジャイルプロジェクトの失敗談を通して、顧客を巻き込む重要さと難しさを語っていただきました。
現在、顧客と一緒になったアジャイルプロジェクトを進めれているとのことで、そのあたりの情報を共有していけたらとのことでした。

実践の話は、一番みんな聞きたいところだと思うので今後期待です。



   3. ワールドカフェ

ワールドカフェって知ってましたか。
テーブルごとに話し合いを行う、メンバ交代で意見交換を効率的に行う形式みたいです。

今回のような組織やコミュニティの比較的多人数の集まりで、設定したテーマに関して、ダイナミックで協働的な話し合いの場を作り出すのに効果的とのこと。
 
お題は「価値ある成果」とは何か。です。

だいたい、各テーブルでこんな感じで話が進んだと思います。

  そもそもアジャイルってどうなの?

  未経験者が多いため、アジャイルって使えるの?という疑問を経験者に聞くところから。
経験者からも失敗談、主に、開発側のイテレーションに対する経験不足、顧客側との相互理解の不足が原因の失敗談を聞くことができました。

アジャイルって難しい…?


  価値ある成果って?


次は価値ある成果とは、を話しましたが、ここは開発側、発注側の立場の違いだけでなく、開発側の中だけでも意見がばらばらなところでした。
システム、コスト、スケジュール、要求どおり、次につながること、etc...


  価値ある成果を達成するには?


では、その価値ある成果を達成するには、どうしたらよいの?という話。
全ての価値を達成するのは無理。無限のコストと無限の期間がなければ。
そこでトレードオフが発生します。
何を捨てて、何を優先するのか。
捨てることに対して、「理解を得ながら」システムをつくっていかなければ、お互いが不幸になる。
価値ある成果を達成するためには、お互いがシステムをつくるのに同じ土俵に立って逐一、理解を深め合いながらシステムに関わっていくことが必要。


  もう一度、価値ある成果って?

逆説的にはなりますが、「何を価値あるものとみなすか」それを相互が、逐一理解を深めながら進めた結果が、価値ある成果なのだと思います。

価値観は、みんなそれぞれ違う。
そこを埋めていく過程こそが成果につながる。

何を価値とみなすかを話していく。共有していく。
お互いに納得済みのゴールが価値ある成果。

こんな感じでしょうか。

その「目的」のためにアジャイルや、飲みニケーションといった「手段」をつかっていくのだということを忘れないようにしなければいけないと思いました。


(ああ、上司が何度も苦労して何度も語りかけてきたところだ、これ…。)




   4. 今後の方針
  • 月1の頻度で実施
  • まずは輪読から
  • 事前に2章ずつ読んでくること
  • 各節ごとに話し合い、疑問点、まとめを行う
  • 次回、7/27(金)を予定
  • Facebookにグループ作成して、やりとりを行う

アジャイル道場なので、やり方もアジャイルで適宜変えていきましょう。 by @goking




   5.その他、関連イベント紹介


7月のアジャイル関連イベントの紹介
  • スクラムブートキャンプ
  • Coderetreat
  • Jenkins勉強会



詳細は個別にFacebook、Twitterで告知。


------------------------------
アジャイルサムライ福岡道場。興味あるかたはATND等で告知しますので是非参加してください。ではでは。



2012年6月14日木曜日

AndroidのListViewを速くするためにやったこと

先日公開した、電話帳アプリ「OneHand Dialer」ですが、品質、性能面で問題ありとのご指摘を受けました。
今回は、備忘録も兼ねて、対策内容をまとめておきます。


Android ListViewを速くするためにやったこと
  1. 現象と原因
  2. 正規表現の使用は極力避ける
  3. Bitmapはキャッシュする
  4. BitmapはBitmapFactory.Optionsを使って縮小する
  5. おまけ:GC発生箇所の調査環境構築


  1. 現象と原因

以下の現象が発生するとのことで、調査を行いました。

・端末:GALAXYNexus(Android 4.0)
・起動時の読み込みに5秒ほどかかる
・データ件数は300〜400件程度
・その他、よく落ちるとのこと(詳細不明)

うーん…。

自分の端末での動作確認のときは、そんなに連絡先の件数が多くなかったこともあり、再現できてなかったのですが、 同等件数のデータと顔写真データが設定されている端末での動作確認を行ったところ起動時に時間がかかる現象は発生させることができました。

よく落ちる現象は手元の端末では再現させることができなかったのですが、別途Android4.0の環境で動作確認を行ってみたいと思います。

原因は、連絡先一覧の取得〜表示処理までに大量のGCが発生していたためでした。
以下、個別の原因と対策です。


  2. 正規表現の使用は極力避ける

OneHand Dialerでは、あいまいソートという機能により、ふりがなに、ひらがな/全角カタカナ/半角カタカナが混在していても、同一視して並べ替える機能があります。
実装には、Comparatorインターフェースを実装して独自の比較を定義しています。

その中で、ひらがな/全角カタカナ/半角カタカナなどを区別するために正規表現を利用していたのですが、そこで、短命オブジェクトが大量に生成されており、GCの対象となっていました。

件数が多くなるほどcompareメソッドが呼ばれる回数が多くなるため、GC発生回数が増えたというわけです。

今回は、正規表現による判定をUnicodeBlockで代用することで回避できました。


また、大文字、小文字の統一も当初、正規表現で判定して、個別にtoUpperCaseメソッドを行っていたのですが、これも短命オブジェクトを大量に生成していたため、最終的な比較処理で利用していたcompareToメソッドをcompareToIgnoreCaseに変更することで条件を除外する対策も行いました。

今回は、代用する条件がありましたが、正規表現でしか表現できない条件を繰り返し利用する場合は、性能面で注意が必要だと思います。
何か対策をご存知のかたがいらっしゃれば、教えていただければと思います。


  3. Bitmapはキャッシュする

OneHand Dialerでは、連絡帳一覧に顔写真を表示しています。
また、ImageViewの角を丸く見せるため、Bitmap生成処理が多少複雑になっており、これらが、ListViewのgetViewの度に呼ばれることで、メモリを圧迫していました。

対策として、Bitmapの一覧をキャッシュするよう変更しました。
また、キャッシュにはソフトリファレンスを利用することで、キャッシュによるメモリ圧迫を回避しています。

実装に関しては、以下が参考になると思います。
Bitmapのキャッシュ
Techfirm Android Lab / AsyncTaskでユーザビリティを向上させる
ソフトリファレンスによるキャッシュ
Techfirm Android Lab / CacheオブジェクトにはSoftReferenceを


  4. BitmapはBitmapFactory.Optionsを使って縮小する

大きなサイズのBitmap生成はコストが高い処理です。
リサイズもできますが、元のサイズのデータを一旦メモリに展開して行うため、非効率的です。

今回は、BitmapFactory.Optionsを使った効率的なBitmapの生成を行うことで対応しました。

AndroidのBitmapFactory.Optionsを使うと、メモリ展開前に画像サイズを取得できるため、最初からリサイズした状態で(少ないメモリで)Bitmapを生成することが可能です。

ポイントは、inSampleSizeプロパティです。
リサイズの縮尺を整数で指定します。1で等倍、2で1/2のサイズにリサイズされます。
ここでは、viewのサイズに合わせたサイズになるように値を求めています。


BitmapFactory.Optionsの使い方は以下を参考にしました。
効率的なBitmapの生成
iPhone充日記 / ListViewに画像を非同期で読み込む場合の注意


  5. おまけ:GC発生箇所の調査環境構築

GCの発生箇所の特定は以下を参考にしました。

MAT環境の構築
電脳羊(Android Dream)/ Eclipseで使うMemory Analyzerのインストール
※自分の環境では、Eclipseとの連動のため、ここにある手順に加えて、Eclipse -> 環境設定 -> Android -> DDMS -> HPROFオプションで「Open in Eclipse」を選択する必要がありました。

MATによるヒープタンプの見方
Bescottee / メモリリークを発見!Androidアプリのメモリ解析手法

※上記のMATに加え、DDMSタブのAllocation Trackerも参考にしました。

以上です、どなたか参考になれば幸いです。ではでは。


2012年6月11日月曜日

Android UI について

今回、OneHand Dialerという片手で使える電話帳をリリースしました。

電話帳: OneHand Dialer [Free]

アプリ作成にあたっては、自身が電話帳アプリ、ひいてはAndroidのUIで使いにくいなと思っていた点を解消できればと思って作成しました。

無事リリースできたので、つくっているときに感じたことや今後のことをまとめてみました。


AndroidのUIで使いにくいと感じていた点


Androidをはじめ、タッチパネルではフリップやタップ操作により、直感的な操作が行えるようになりましたが、反面、特定のスクロールアクションや画面内の各ボタンまで指を運ぶ必要があるため、いくら慣れても操作に一定の時間がかかってしまう不満がありました。

AndroidのUIはPC操作でいうところのマウスだけ与えられた状態に近いと思います。
よく使うアプリほど、キーボードショートカットに該当する操作が提供されるべきだと考えます。


電話帳アプリへの不満点


特に電話帳アプリでは、思ったときにすぐ連絡先へアクセスしたいにも関わらず、

・一覧のスクロールが行き過ぎる
・一覧のスクロールが片手操作しづらい
・検索ダイアログが上部にあったりして、片手持ちの際に届かない
・タッチ場所により、ソフトウェアキーボードが出たり消えたりしてわずらわしい

といった邪魔をする要因があり、やきもきしていました。
(スマートフォンなのに電話が一番かけづらい…)


OneHand Dialerでの対策


上記を解決するため、わかりやすい、かつ、慣れるほど短い操作で使えることを目標にデザインを考えてみました。

基本思想は以下です。

・よく利用する操作は手元、画面下部に集約する
・リスト操作は大きな動きと細かい動きを制御できるようにする

基本画面は以下です。


・操作のための固定キーボードを提供
検索キーワードはここから入力。
下部のコマンド群はリスト内容によって提供操作を変更。

・リスト操作は上下キーによるカーソル移動とフリップ操作を連動
また、上下キーは長押しによる速い移動が可能。


キーボードの検索キーワード入力、上下キーでのリスト移動、コマンドボタン、これらが画面下部に集約できたことで操作が全て手元で行え、電話発信にかかる手番は最小限にとどめることができたと思います。


今後


今回のアプリ作成にあたり、AndroidのUIパターンを参考にしてみましたが、リスト画面の操作向上に対するパターンは探した限りなかったと思います。
ただ、画面下部に集約するというUIデザインは、いわゆるNavigation BarとかAction Bar, Quick Actionを参考にしました。

これらをあわせて、リスト操作を手元で行えるコマンド部品というのを下部に配置するという方針に至りました。
リストの表示部分が少なくなるという課題はありますが、操作性としては一定の効果は出せたと思います。

今後は、それらをもう少し煮詰めて、いわゆるキーボードのショートカットを実行できるコマンド群を入力する汎用的な部品をつくってみたいなと考えています。
コマンド群はソフトウェアキーボードなので、画面に応じて実行コマンドを変更できるので汎用性は問題ないはずです。
(リストであれば、細かい動きのための上下キーは欲しいところ)

コマンドUIパターンはライトユーザ、ヘビーユーザのどちらも満足できる操作感を実現する架け橋になる(かな?)