Tuesday 13 September 2016

How Big Can My Python Script Be?

We all know that the micro:bit has limits. The specs (available either on WikiPedia or the ARMmbed site) show that the memory limits are:
  • 16kB RAM
  • 256kB Flash 
That sounds like a decent amount of memory - it's not in the gigabytes range of even your most modest modern PC or Mac, but for an embedded processor a total of 282kB is not too bad. So surely we can load massive Python scripts onto our micro:bits, right? Wrong. The hard limit for a Python script is just a snip under 8k. Try and copy a script file larger than that and it will fail. Why is that?


RAM vs Flash Memory

First of all, let's look at the two types of memory - normal Random Access Memory (or RAM) and Flash memory. Normal RAM is super fast to access and can be read and written to as many times as you want. It does, however, loose everything when you remove power. Flash memory, on the other hand, retains it's memory even without power. Flash is also slower to read than normal RAM and is usually a little more work for a processor to write to. Flash memory is also limited in the number of times that you can write to it before it starts to degrade. Don't worry - the expected life time for most Flash memory is around 100,000 write cycles so it will take a very long time for your micro:bit's Flash to degrade. The upshot of this is that the two memory types are quite different, and your micro:bit uses them for different purposes. Flash memory is used like a disk drive - the program code is stored there and parts of it are read into the main RAM as needed. Main RAM is then used as the working memory for the processor - it will store dynamic data such as variables and the compiled Python script.

What Is Stored In Flash?

When you write a .hex file to the micro:bit, you're actually writing data to those 256kB of Flash RAM. Look back at our last article and you'll see that this means that we're writing the MicroPython code and our actual Python script there. Let's take a look at the end of the dump file for a simple Python script again:

DAT: 0x000350c0 000000000000000080000020764e0300 ........€.. vN..
DAT: 0x000350d0 78000400040488130100010001000101 x...............
DAT: 0x000350e0 0401000091160000913f0100b1550100 .........?...U..
DAT: 0x000350f0 155d010065160000 .]..e...
ELA: 0x00030000
DAT: 0x0003e000 4d50340066726f6d206d6963726f6269 MP4.from microbi
DAT: 0x0003e010 7420696d706f7274202a0a0a64697370 t import *..disp
DAT: 0x0003e020 6c61792e73686f77282748656c6c6f20 lay.show('Hello 
DAT: 0x0003e030 576f726c6427290a0000000000000000 World').........
SLA: 0x00013a85
EOF:

You can see the end of the MicroPython code followed by our script. The long hexadecimal number right after the DAT is the physical memory location that we are copying to. This means that our script is being copied to 0x3e000 or 253,952 decimal. The total amount of Flash memory is 256 * 1,024 or 262,144 bytes. Taking one away from the other we see that this gives us 8,192, or exactly 8 kB. Subtract the size of the header (MB plus two bytes for size = 4 bytes) and the null terminator at the end (1 more byte) and we get 8,187. From this we can surmise that the maximum size that our Python script can be is 8,187 bytes. We can easily test this by writing a script file that is 8 kB long (just fill the file with lots of comments until it's long enough) and trying to copy it to the micro:bit - it won't work.

8K Is All We've Got Then?

Well.. yes. And no. There are a few options open to us if we want to write larger Python scripts. The first is that we could recompile MicroPython (it's open source) after removing bits that we don't need. There would be a lot of space gained if we just removed the help text. With more space available we could set the start address of the Python script lower, giving us more space.

Another option is a bit of a cheat but it's much easier. Consider the following code:

"""
 A program to flash a heart image and then display a message
"""


from microbit import *


# Flash a heart 10 times
for counter in range(10):
    display.show(Image.HEART)
    sleep(100)
    display.show(Image.BLANK)
    sleep(100)

# Display a text message
display.show('Hello World')

The Python script file for this code is 291 bytes long. Now consider this code which does exactly the same thing:

from microbit import *
for i in range(10):
 display.show(Image.HEART)
 sleep(100)
 display.show(Image.BLANK)
 sleep(100)
display.show('Hello World')

This time we're down to149 bytes just by removing the comments and the blank lines and converting spaces into tabs. Imagine the saving if we did this on a large script?

This process is called minification and we can take it much further than this. The big downside of minifying your code is that it becomes very hard to read. However, we can easily write a tool to take our original script and  automatically create a minified version. This way you never have to deal directly with the hard-to-read, minified code. In fact, we already have such a tool, and we'll give it to you in a future blog post.

There are yet more ways to shoehorn more script code in. Maybe we'll come to those in a future blog post too.

No comments:

Post a Comment