📜 ⬆️ ⬇️

Pebble code optimization


On Habré already there were several articles about the general principles of writing code under Pebble. For programming, the C language is used, and the development process itself takes place in the browser, while compilation takes place on remote servers, and there is no possibility to change its parameters, except to install Ubuntu and install the necessary tools for offline compilation. But even such a move does not relieve the main limitation - only 24 KB of RAM is available on the device, which is also used for compiled code, that is, 5-10 KB of truly dynamic memory remains. And if for simple programs that are used as thin clients or additional sensors for a phone, this is enough with your head, then for writing a self-contained more or less complex game that does not need a smartphone, this is frankly not enough. This is where code size optimization is needed.

I already filled my bumps, and therefore I propose to learn from my mistakes, which I combined into 16 tips. Some of them may seem like captains, some will be relieved by a good compiler with the correct compilation flags, but I hope some of them will be useful to someone.

About motivation
Many habrovchan 10 years ago were phones Siemens, and, probably, many played the game Stack Attack, which was often pre-installed. The processor with a frequency of 26 MHz from the owner of a modern smartphone causes a grin. But, despite the very weak iron by today's standards, these ancient black and white phones supported Java games, which is Stack Attack 2 Pro.

I remembered this game when I had a pebble. Its hardware is an order of magnitude more powerful than those old phones, but the screen is almost the same. After simple tests, it turned out that this screen could easily show 60 frames per second. More or less complex games in the pebble appstore can be counted on the fingers, so it was decided to write Stack Attack on the pebble.
')
The search for screenshots from which one could get normal resources for the game did not give anything. Therefore, I found a Siemens C55 emulator on an old-fashioned site , and the game itself. Thus it was possible to remember what the game looked like. And after picking in the jar-archive, it was possible to retrieve relatively simply pictures and texts.
For those who do not want to install all kinds of strange emulators (which, oddly enough, even run Windows with a grief in half), I recorded nostalgic videos:





1. The first, and most obvious way - use inline wherever there is such an opportunity. If the function is called exactly 1 time, this will save 12 bytes. But if the function is not trivial, then you can fly too hard, so be careful. Also, the disadvantage of this method is that in most cases you will have to write code in the .h file.
2. No matter how trite it sounds, write less code, as long as it does not interfere with its normal reading. In general, less code - less binary file.
3. Transfer the texts to the resource files. The program for pebble may contain about 70 Kb of resources, which is quite enough if it does not show a new image every minute.
Usually, all texts are not displayed immediately, so the use of dynamic memory instead of static memory will save space. The disadvantage is that you have to write extra code that loads and unloads resources by their identifiers. It may also seem that the readability of the code will suffer from this, however, this is not always the case. As an example, I will give the code from my game (above) and a similar section of code from the test project (below):

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. Instead of freeing each of the resources separately, use arrays of resources and release them in a loop. With three or more resources, this approach saves memory.
Example:

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

Better than:

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

5. Avoid unnecessary variables where appropriate. For example, the code

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

takes 20 bytes less than

  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. If the code is already impossible to read, write the most optimal. For example, here is a part of the code from the tertiary_text project:

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

This code does the same as

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

The upper and lower codes differ in size several times, and, in my opinion, their readability is equally low.

7. Use enum to transfer successive calls to the loop. Moreover, due to processor magic such code can work even slightly faster.

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

Instead:

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


8. When I for the first time after many years saw this picture:



I thought: they knew how to do before! Here, if you look closely, the background is cyclical, here it is cyclical, and here ... They saved the memory as they could! Here's another article about how to save memory on the same clouds and bushes.

In fact, when I unpacked the resources, I saw that the whole background was made with one picture. At first, I did the same - and immediately lost about 2KB of RAM, where I could get four times less.

So, the advice itself: use images as small as possible, because each of them is "hanging" in memory. Programmatically draw everything that you can, fortunately, processor power is enough for 60 frames per second.
60 frames per second cheating
Due to the fact that you can draw up to 60 frames per second, it is possible to display along with black and white also a “gray” color. I quickly wrote a test program ( github ), which demonstrates this, however, I did not see any real use of this feature. In the first available program that demonstrates the image from the camera on a pebble, this was not.

I divided the background into parts, which are cyclically repeated. Pebble automatically repeats an image if the rectangle in which it is to be displayed is larger than the image itself, and it is worth using. But if you overdo and draw a 1x1 image on the whole screen, the fps will be very low. For such purposes it is better to use graphic primitives - lines, rectangles.

9. Make resources so that they can be used "out of the box", that is, without writing additional code.
In the game, the character can go left and right, the images are symmetrical. At first I thought to save space, and wrote code that displays images in a mirror. But after memory ceased to be enough, it was necessary to refuse this code.

10. Avoid "long-lived" resources where it is not justified. If after selecting the game mode the menu will no longer be used, destroy it immediately before the game. If used, remember its state and reproduce it when necessary. If the picture is displayed only at the start, delete it immediately after the show.

11. Use static-methods and static-variables, use const everywhere where the variable is not expected to change.

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

better than just
 char caps[] = "ABCDEFGHIJKLM NOPQRSTUVWXYZ"; 


12. Use the same callback where it is possible. For example, if in two menus menu_draw_header_callback is empty, there is no point in writing it twice.

 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. Use the user_data of those objects that have it. The memory is already allocated, why not use it for your own purposes?

14. Use int as the main type, even if it should be recalculated from 0 to 5. I think the gain is due to the fact that the compiler inserts additional code if smaller types are used.

15. Try to reuse the code the maximum number of times.
This tip is similar to tip # 12, but more general. Do not use copy-paste method with changing several lines of code after that, instead use a flag that would be passed to the function.

16. The last tip is more dangerous than all previous ones I’ll warn you that I didn’t use it, and I don’t recommend it to anyone. However, there are situations when there is no other way out. In order for the children not to read it behind your back, I hide the advice under the spoiler.
If you already have 18
Does not release resources. Sometimes this may have no consequences, for example, if resources are destroyed only at the end of the program. But potentially this will lead to unstable work and departures that are very difficult to track. Pebble logs the amount of used memory after the program ends. I wish you always to have 0b there.
Spoiler for 24 bytes
If the program uses rand (), then 24 unused bytes may remain after the output. This bug is about a year old. For myself, I solved this problem with the following code:

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




Result


The game is available in the pebble appstore, the code is available on github .

Here is a video of what happened:

Source: https://habr.com/ru/post/241555/


All Articles