Pebbleのコード最適化



Habréには、Pebbleでのコード作成の一般原則に関する記事が既にいくつかありました。 プログラミングには、C言語が使用され、開発プロセスはブラウザーで行われますが、コンパイルはリモートサーバーで行われ、Ubuntuをインストールしてオフラインコンパイルに必要なツールをインストールしない限り、パラメーターを変更する方法はありません。 しかし、そのような移動でも主な制限は保存されません。デバイスでは24 KbのRAMしか使用できず、コンパイルされたコードにも使用されます。つまり、5〜10 Kbの動的メモリが残ります。 また、シンクライアントまたは電話の追加センサーとして使用される単純なプログラムの場合、頭でこれで十分であり、スマートフォンを必要としない自給自足の多かれ少なかれ複雑なゲームを書くには、これは率直に言って十分ではありません。 これは、コードサイズの最適化が必要な場所です。



私はすでにバンプを埋めたので、16個のヒントにまとめた私の間違いから学ぶことを提案します。 それらの一部はキャプテンのように見えるかもしれません。正しいコンパイルフラグを備えた優れたコンパイラはそれらの一部を保存しますが、それらの一部が誰かに役立つことを願っています。



動機について
多くのKhabrovskの住人は10年前にSiemensの携帯電話を持っていて、おそらく多くの人がゲームStack Attackをプレイしました。 最新のスマートフォンの所有者からの26 MHzの周波数を持つプロセッサは、にやにや笑いを引き起こします。 しかし、今日の標準ではハードウェアが非常に弱いにもかかわらず、これらの古代の黒と白の携帯電話は、Stack Attack 2 ProであるJavaゲームをサポートしていました。



小石を手に入れたとき、このゲームについて思い出しました。 そのハードウェアはそれらの古い携帯電話よりもはるかに強力ですが、画面はほぼ同じです。 簡単なテストの後、この画面には毎秒60フレームが表示されることがわかった。 小石のアプリストアで多かれ少なかれ複雑なゲームを指で数えることができるので、小石にスタック攻撃を書くことにしました。



ゲームの通常のリソースを取得するためのスクリーンショットの検索では、何も得られませんでした。 そのため、古いサイトゲームのシーメンスC55エミュレーターを見つけました。 したがって、ゲームの外観を思い出すことができました。 jarアーカイブを選択した後、写真やテキストを取得するのは比較的簡単でした。

あらゆる種類のあいまいなエミュレーターをインストールしたくない人(奇妙なことに、Windows 8でも半分で悲嘆に暮れる)は、懐かしいビデオを録画しました。











1.最初の最も明白な方法-可能な限りインラインを使用します。 関数が1回だけ呼び出されると、12バイト節約されます。 ただし、関数が簡単でない場合は、非常に飛ぶことができるので注意してください。 この方法のもう1つの欠点は、ほとんどの場合、.hファイルにコードを記述する必要があることです。

2.どんなに些細なことでも、通常の読み取りが妨げられるまでコードを少なくします。 一般的に、少ないコード-少ないバイナリ。

3.テキストをリソースファイルに転送します。 小石用のプログラムには約70 KBのリソースを含めることができますが、毎分新しい画像を表示しなくても十分です。

通常、すべてのテキストはすぐには表示されないため、静的ではなく動的メモリを使用するとスペースが節約されます。 欠点は、識別子によってリソースをロードおよびアンロードする追加のコードを記述する必要があることです。 また、コードの可読性が低下するように思われるかもしれませんが、これは常にそうではありません。 例として、ゲームのコード(上)とテストプロジェクトのコードの同様のセクション(下)を示します。



static void cranes_menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { int const index = cell_index->row; menu_cell_basic_draw( ctx, cell_layer, texts[ index * 2 ], texts[ index * 2 + 1 ], NULL ); } static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { switch (cell_index->row) { case 0: menu_cell_basic_draw(ctx, cell_layer, "Basic Item", "With a subtitle", NULL); break; case 1: menu_cell_basic_draw(ctx, cell_layer, "Icon Item", "Select to cycle", NULL); break; } }
      
      







4.各リソースを個別に解放する代わりに、リソースの配列を使用してループで解放します。 3つ以上のリソースがある場合、このアプローチはメモリを節約します。

例:



  for ( int i=0; i<7; ++i ) { gbitmap_destroy( s_person_images[i] ); }
      
      





より良い:



 gbitmap_destroy( s_person1_image ); ... gbitmap_destroy( s_person7_image );
      
      





5.必要に応じて、不要な変数を避けます。 たとえば、コード



 for (int i=0; i<9; ++i) { for (int k=0; k<3; ++k) { btexts[i/3][i%3][k] = master[count]; count++; } }
      
      





20バイト少ない



  for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { for (int k=0; k<3; k++) { btexts[i][j][k] = master[count]; count++; } } }
      
      





6.コードの読み取りがすでに不可能な場合は、最適に書き込みます。 たとえば、tert-textプロジェクトのコードは次のとおりです。



  size /= 3; if (b == TOP) end -= 2*size; else if (b == MID) { top += size; end -= size; } else if (b == BOT) top += 2*size;
      
      





このコードは以下と同じです



 size /= 3; top += b*size; end -= (2-b)*size;
      
      





上部と下部のコードはサイズが数倍異なり、私の意見では、読みやすさも同様に低いです。



7. enumを使用して、連続する呼び出しをループに転送します。 さらに、プロセッサーの魔法により、このようなコードは少しでも速く動作します。



  unsigned char RESOURCE_ID_BOXES[11] = { RESOURCE_ID_BOX1, RESOURCE_ID_BOX2, RESOURCE_ID_BOX3, RESOURCE_ID_BOX4, RESOURCE_ID_BOX5, RESOURCE_ID_BOX6, RESOURCE_ID_BOX7, RESOURCE_ID_BOX8, RESOURCE_ID_BOX9, RESOURCE_ID_BOX10, RESOURCE_ID_BOX11 }; for (int i=0; i<11; ++i) { s_boxes_bitmap[i] = gbitmap_create_with_resource( RESOURCE_ID_BOXES[i] ); }
      
      





代わりに:



 s_boxes_bitmap[0] = gbitmap_create_with_resource( RESOURCE_ID_BOX1 ); ... s_boxes_bitmap[10] = gbitmap_create_with_resource( RESOURCE_ID_BOX11 );
      
      







8.何年も後にこの写真を見たとき:







私は思った:彼らは前にそれをする方法を知っていた! ここで、よく見ると、背景は周期的であり、ここでは周期的であり、ここでは...彼らはできる限りメモリを節約しました! 同じ雲や茂みでメモリを節約する方法に関する別の記事があります。



実際、リソースを展開すると、背景全体が1つの画像で作成されていることがわかりました。 最初は同じことをしました-すぐに約2KBのRAMを失いました。



したがって、アドバイス自体:可能な限り小さいイメージを使用してください。それぞれのイメージがRAMで「ハング」するからです。 プログラムで、幸いなことに、プロセッサの能力は毎秒60フレームで十分です。

1秒あたり60フレームの不正行為
1秒間に最大60フレームを描画できるため、白黒と一緒に「灰色」の色を表示することができます。 これを実証するテストプログラムgithub )をすばやく作成しましたが、この機能の実際の使用方法はわかりませんでした。 最初に出てきたプログラムでは、小石の上にカメラの画像を表示していましたが、そうではありませんでした。


背景を周期的に繰り返される部分に分割しました。 Pebbleは、表示される長方形が画像自体よりも大きく、使用する価値がある場合、画像を自動的に繰り返します。 ただし、行き過ぎて1x1画像をフルスクリーンで描画すると、fpsは非常に低くなります。 そのような目的では、線、長方形などのグラフィックプリミティブを使用することをお勧めします。



9.リソースを作成して、「そのまま」使用できるようにします。つまり、追加のコードを記述しません。

ゲームでは、キャラクターは左右に歩くことができますが、画像は対称的です。 最初は、スペースを節約することを考え、画像をミラーリングするコードを書きました。 しかし、メモリが十分でなくなった後、このコードを放棄しなければなりませんでした。



10.これが正当化されない場合、「長期にわたる」リソースを避けます。 ゲームモードを選択した後、メニューが使用されなくなった場合は、ゲームの直前にメニューを破棄します。 使用する場合は、その状態を記憶し、必要に応じて複製します。 起動時にのみ画像が表示される場合は、表示後すぐに削除してください。



11.静的メソッドと静的変数を使用し、変数を変更する予定がない場合はconstを使用します。



 static const char caps[] = "ABCDEFGHIJKLM NOPQRSTUVWXYZ";
      
      





ただより良い

 char caps[] = "ABCDEFGHIJKLM NOPQRSTUVWXYZ";
      
      







12.可能な場合は同じコールバックを使用します。 たとえば、2つのメニューでmenu_draw_header_callbackが空の場合、2回記述するのは意味がありません。



 static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, uint16_t section_index, void *data) { } menu_layer_set_callbacks(menu_layer, NULL, (MenuLayerCallbacks) { .get_num_rows = menu_get_num_rows_callback, .draw_header = menu_draw_header_callback, .draw_row = menu_draw_row_callback, .select_click = select_callback, });
      
      





13.それを持つオブジェクトのuser_dataを使用します。 メモリは既に割り当てられています。自分の目的に使用してみませんか?



14. 0から5までカウントする必要がある場合でも、intをメインタイプとして使用します。より小さなタイプが使用される場合、コンパイラが追加のコードを挿入するという事実によると思います。



15.コードを最大回数再利用してみてください。

このヒントは、ヒント番号12に似ていますが、より一般的です。 その後、数行のコードを変更してcopy-pasteメソッドを使用しないでください。代わりに、関数に渡されるフラグを使用してください。



16.最後のアドバイスは、以前のアドバイスよりも危険です。 使用しなかったことをすぐに警告します。誰にも使用しないことをお勧めします。 ただし、他に方法がない状況があります。 子供が誤ってあなたの背中の後ろでそれを読むのを防ぐために、私はネタバレの下にアドバイスを隠します。

すでに18がある場合
リソースを解放しません。 プログラムの最後でのみリソースが破棄される場合など、これは結果にならない場合があります。 しかし、潜在的にこれは不安定な動作とクラッシュにつながり、追跡が非常に困難です。 Pebbleは、プログラムの完了後に使用されたメモリの量をログに表示します。 常に0bが存在することを願っています。

ネタバレ約24バイト
プログラムがrand()を使用する場合、終了後に24の未割り当てバイトが存在する可能性があります。 このバグは1年前から存在しています。 私自身は、次のコードでこの問題を解決しました。



 int _rand(void) /* RAND_MAX assumed to be 32767. */ { static unsigned long next = 1; next = next * 1103515245 + 12345; return next >> 16; }
      
      











結果



ゲーム小石のアプリストアで入手でき、コードはgithubで入手できます。



これが何が起こったかのビデオです:






All Articles