Code-Optimierung für Pebble


    Es gab bereits mehrere Artikel über HabréÜber die allgemeinen Prinzipien des Schreibens von Code unter Pebble. Für die Programmierung wird die Sprache C verwendet, und der Entwicklungsprozess findet im Browser statt, während die Kompilierung auf Remoteservern erfolgt. Die Parameter können nur geändert werden, wenn Sie Ubuntu installieren und die für die Offline-Kompilierung erforderlichen Tools installieren. Aber selbst ein solcher Schritt wird die Haupteinschränkung nicht retten - auf dem Gerät, das auch für kompilierten Code verwendet wird, sind nur 24 KB RAM verfügbar, dh 5-10 KB verbleiben im wirklich dynamischen Speicher. Und wenn für einfache Programme, die als Thin Clients oder zusätzliche Sensoren für das Telefon verwendet werden, dies mit dem Kopf ausreicht, dann ist dies ehrlich gesagt nicht genug, um ein autarkes mehr oder weniger komplexes Spiel zu schreiben, das kein Smartphone benötigt. Hier ist eine Optimierung der Codegröße erforderlich.

    Ich habe meine Unebenheiten bereits gefüllt, und deshalb schlage ich vor, aus meinen Fehlern zu lernen, die ich in 16 Tipps zusammengefasst habe. Einige von ihnen scheinen Kapitän zu sein, andere werden von einem guten Compiler mit den richtigen Kompilierungsflags gespeichert, aber ich hoffe, einige von ihnen sind für jemanden nützlich.

    Über Motivation
    Viele Bürger von Khabrovsk hatten vor 10 Jahren Siemens-Telefone, und wahrscheinlich spielten viele das Spiel Stack Attack, das oft vorinstalliert war. Der Prozessor mit einer Frequenz von 26 MHz vom Besitzer eines modernen Smartphones sorgt für ein Grinsen. Trotz der für heutige Verhältnisse sehr schwachen Hardware unterstützten diese alten Schwarzweiß-Telefone Java-Spiele, nämlich Stack Attack 2 Pro.

    Ich erinnerte mich an dieses Spiel, als ich Kiesel bekam. Die Hardware ist viel leistungsfähiger als die alten Telefone, aber der Bildschirm ist fast derselbe. Nach einfachen Tests stellte sich heraus, dass auf diesem Bildschirm möglicherweise 60 Bilder pro Sekunde angezeigt werden. Mehr oder weniger komplexe Spiele im Pebble Appstore können an den Fingern gezählt werden, so dass entschieden wurde, einen Stack Attack auf Pebble zu schreiben.

    Die Suche nach Screenshots, von denen normale Ressourcen für das Spiel abgerufen werden konnten, brachte nichts. Daher habe ich den Siemens C55-Emulator auf der alten Website gefunden und das Spiel selbst . So konnte man sich erinnern, wie das Spiel aussah. Und nach der Auswahl im Glasarchiv war es relativ einfach, Bilder und Texte abzurufen.
    Für diejenigen, die nicht alle Arten von obskuren Emulatoren installieren möchten (die seltsamerweise sogar unter Windows 8 mit gebrochenem Herzen gestartet werden), habe ich nostalgische Videos aufgenommen:





    1. Der erste und naheliegendste Weg - verwenden Sie Inline, wo immer dies möglich ist. Wenn die Funktion genau einmal aufgerufen wird, werden 12 Bytes gespart. Aber wenn die Funktion nicht trivial ist, können Sie sehr viel fliegen, also seien Sie vorsichtig. Ein weiterer Nachteil dieser Methode ist, dass Sie in den meisten Fällen Code in eine .h-Datei schreiben müssen.
    2. Egal wie banal es auch klingen mag, schreiben Sie weniger Code, bis es ein normales Lesen verhindert. Im Allgemeinen weniger Code - weniger Binär.
    3. Übertragen Sie die Texte in Ressourcendateien. Ein Programm für Kieselsteine ​​kann ungefähr 70 KB an Ressourcen enthalten. Dies ist völlig ausreichend, wenn nicht jede Minute ein neues Bild angezeigt wird.
    Da in der Regel nicht alle Texte sofort angezeigt werden, wird durch die Verwendung des dynamischen Speichers anstelle des statischen Speicherplatzes Platz gespart. Der Nachteil ist, dass Sie zusätzlichen Code schreiben müssen, der Ressourcen anhand ihrer Bezeichner lädt und entlädt. Möglicherweise leidet auch die Lesbarkeit des Codes, dies ist jedoch nicht immer der Fall. Als Beispiel gebe ich einen Code aus meinem Spiel (oben) und einen ähnlichen Codeabschnitt aus einem Testprojekt (unten):

    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. Verwenden Sie Arrays von Ressourcen und geben Sie sie in einer Schleife frei, anstatt jede Ressource einzeln freizugeben. Mit drei oder mehr Ressourcen spart dieser Ansatz Speicher.
    Ein Beispiel:

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

    Besser als:

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

    5. Vermeiden Sie gegebenenfalls unnötige Variablen. Zum Beispiel Code

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

    nimmt 20 Bytes weniger als

      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. Wenn der Code bereits nicht lesbar ist, schreiben Sie am besten. Hier ist beispielsweise ein Code aus dem tertiary_text-Projekt:

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

    Dieser Code macht dasselbe wie

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

    Der obere und der untere Code unterscheiden sich mehrfach in der Größe, und meiner Meinung nach ist ihre Lesbarkeit gleichermaßen gering.

    7. Verwenden Sie enum, um aufeinanderfolgende Anrufe in eine Schleife zu übertragen. Darüber hinaus kann ein solcher Code aufgrund von Prozessormagie sogar etwas schneller arbeiten.

      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] );
      }
    

    Stattdessen:

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


    8. Als ich dieses Bild nach vielen Jahren zum ersten Mal sah:



    Ich dachte: Sie wussten vorher, wie es geht! Hier, wenn Sie genau hinschauen, ist der Hintergrund zyklisch, hier ist er zyklisch und hier ... Sie haben die Erinnerung gerettet, wie sie konnten! Hier ist ein weiterer Artikel zum Speichern von Speicher auf identischen Wolken und Büschen.

    In der Tat, als ich die Ressourcen auspackte, sah ich, dass der gesamte Hintergrund in einem Bild gemacht wurde. Zuerst tat ich das Gleiche - und verlor sofort etwa 2 KB RAM, wo viermal weniger hätte getan werden können.

    Der Rat an sich: Verwenden Sie Bilder so klein wie möglich, da jedes von ihnen im RAM "hängt". Zeichnen Sie alles, was Sie programmatisch können, zum Glück ist die Prozessorleistung für 60 Bilder pro Sekunde ausreichend.
    60 Bilder pro Sekunde betrügen
    Aufgrund der Tatsache, dass Sie bis zu 60 Bilder pro Sekunde zeichnen können, ist es möglich, neben Schwarzweiß auch eine „graue“ Farbe anzuzeigen. Ich habe schnell ein Testprogramm ( Github ) geschrieben, das dies demonstriert, aber ich habe nicht gesehen, wie diese Funktion wirklich genutzt wird. Im ersten Programm, das das Bild von der Kamera auf Kieselstein zeigt, war dies nicht der Fall.

    Ich habe den Hintergrund in Teile aufgeteilt, die sich zyklisch wiederholen. Pebble wiederholt das Bild automatisch, wenn das Rechteck, in dem es angezeigt werden soll, größer als das Bild selbst ist und es sich lohnt, es zu verwenden. Wenn Sie jedoch zu weit gehen und ein 1x1-Bild im Vollbildmodus zeichnen, sind die fps sehr niedrig. Für solche Zwecke ist es besser, grafische Grundelemente zu verwenden - Linien, Rechtecke.

    9. Stellen Sie Ressourcen so ein, dass sie sofort verwendet werden können, ohne dass zusätzlicher Code geschrieben werden muss.
    Im Spiel kann der Charakter nach links und rechts gehen, während die Bilder symmetrisch sind. Zuerst dachte ich über Platzersparnis nach und schrieb Code, der Bilder spiegelt. Aber nachdem der Speicher nicht mehr ausreichte, musste ich diesen Code aufgeben.

    10. Vermeiden Sie langlebige Ressourcen, wenn dies nicht gerechtfertigt ist. Wenn das Menü nach Auswahl eines Spielmodus nicht mehr verwendet wird, zerstören Sie es unmittelbar vor dem Spiel. Wenn es verwendet wird, merken Sie sich seinen Zustand und reproduzieren Sie ihn bei Bedarf. Wenn das Bild nur beim Start angezeigt wird, löschen Sie es sofort nach der Anzeige.

    11. Verwenden Sie statische Methoden und statische Variablen. Verwenden Sie const immer dann, wenn die Variable nicht geändert werden soll.

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

    besser als nur
    char caps[] =    "ABCDEFGHIJKLM NOPQRSTUVWXYZ";
    


    12. Verwenden Sie nach Möglichkeit denselben Rückruf. Wenn beispielsweise menu_draw_header_callback in zwei Menüs leer ist, macht es keinen Sinn, es zweimal zu schreiben.

    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. Verwenden Sie user_data der Objekte, die es haben. Der Speicher wurde bereits zugewiesen. Warum nicht für eigene Zwecke verwenden?

    14. Verwenden Sie int als Haupttyp, auch wenn Sie von 0 bis 5 zählen müssen. Ich denke, die Verstärkung beruht auf der Tatsache, dass der Compiler zusätzlichen Code einfügt, wenn kleinere Typen verwendet werden.

    15. Versuchen Sie, den Code so oft wie möglich wiederzuverwenden.
    Dieser Tipp ähnelt Tipp Nummer 12, ist aber allgemeiner. Verwenden Sie die Copy-Paste-Methode nicht, wenn Sie danach einige Codezeilen ändern, sondern verwenden Sie ein Flag, das an die Funktion übergeben wird.

    16. Der letzte Rat ist gefährlicher als alle vorherigen. Ich werde Sie sofort warnen, dass ich es nicht verwendet habe, und ich empfehle es niemandem, es zu verwenden. Es gibt jedoch Situationen, in denen es keinen anderen Weg gibt. Um zu verhindern, dass Kinder es versehentlich hinter Ihrem Rücken lesen, verstecke ich den Hinweis unter dem Spoiler.
    Wenn Sie bereits 18 haben
    Gibt keine Ressourcen frei. Dies kann unter Umständen keine Konsequenzen haben, wenn beispielsweise Ressourcen erst am Ende des Programms zerstört werden. Dies kann jedoch zu instabilem Betrieb und Abstürzen führen, die nur schwer zu verfolgen sind. Pebble zeigt in den Protokollen an, wie viel Speicher nach Abschluss des Programms belegt ist. Ich wünsche dir, dass du immer 0b da hast.
    Spoiler etwa 24 Bytes
    Wenn das Programm rand () verwendet, können nach dem Beenden 24 nicht zugewiesene Bytes vorhanden sein. Dieser Bug gibt es schon seit einem Jahr. Für mich selbst habe ich dieses Problem mit folgendem Code gelöst:

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




    Ergebnis


    Das Spiel ist im Pebble App Store erhältlich, der Code ist auf Github erhältlich .

    Hier ist ein Video von dem, was passiert ist:


    Jetzt auch beliebt: