Native Mode "ManyTasks"

A private (members-only) forum for discussing all issues related to the Beta test of Native mode devices.
Locked
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Native Mode "ManyTasks"

Post by dkinzer »

I have modified Mike's ManyTasks.bas program to work on the native mode devices. On a ZX-24n, it can handle 49 tasks. It might be able to do 50 but I reserved 100 bytes of heap space for the strings that are created as part of the output process.

One of the differences with native mode is that the minimum task size is much larger due to two factors. Firstly, when a task switch is done, the context of the current task is saved to its task stack. This requires 35 bytes (32 GP registers, IP, and status register). Secondly, whenever an interrupt occurs, the ISR saves whatever registers it needs to save on the stack of the currently executing task. I'd have to confirm the actual maximum but I suspect that this may require at most 10-15 bytes. Interrupts are not nested so the maximum stack use due to interrupts is the largest stack use of any ISR.

Another difference is that since tasks use the AVR hardware stack mechanism, the stack pointer grows in the opposite direction as compared to a task stack in the VM mode devices. The AVR stack pointer move toward zero as data is pushed on the stack. This means that the task control block had to be moved to the end of the task stack instead of being at the beginning.

You might recall that on VM mode devices, the Main() task stack and the heap grow toward each other, using all of the RAM that isn't otherwise allocated. This allows maximum flexibility but it also makes it more difficult to prevent the heap from encroaching on the Main() task stack. In the current VM version there is no check for this at all. If stack overflow checking is turned on, it will detect when the Main() task stack has encroached on the heap.

In the native mode devices, the heap grows downward from the end of RAm and the Main() task stack grows downward from some position in the otherwise-unallocated RAM space. By default, 512 bytes are reserved for the heap so the TCB for the Main() task is positioned 512 bytes from the end of RAM. The heap limit is just above the Main() TCB and the heap allocator will not grow the heap beyond the heap limit. This prevents the heap from trashing the Main() TCB. Unfortunately, however, there is no protection against the Main() task stack from trashing other allocated data located lower in RAM.

Because of the hard limit on the heap growth, several directives were added to allow you to specify how the unallocated RAM is split up between the Main() task stack and the heap. The simplest way to do this is to specify the number of bytes that you want for the Main() task stack using Option MainTaskStackSize. Since the heap limit is always immediately after the TCB of the Main() task stack, this means that all the remaining RAM is dedicated to the heap. I used this method in ManyTasks.bas to obtain the maximum heap size.

The second way of specifying the split is to use Option HeapSize to directly specify the heap size. This places the heap limit, and therefore the end of the Main() task stack that number of bytes from the end of RAM. The third way is to specify the heap limit directly as an absolute address. This is the least likely to be used but may be useful in special situations.

In the modified ManyTasks code, I added some calls to determine the amount of unused space in the Main() task stack and the first of the allocated task stacks. I tuned the application by choosing an arbitrary size for the two and then running the program. This first guess turned out to be too low (the app didn't run correctly) so I bumped it up by quite a bit and tried again. This time the app ran but only 25 or so tasks could be allocated. The information about the unused space in the task stacks allowed me to trim down the task stacks to just a bit larger than the indicated requirement, thus arriving at the 49 task mark.
Attachments
ManyTasks.bas
(4.5 KiB) Downloaded 6 times
- Don Kinzer
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

Post by stevech »

Is a ZX24n a mega644 with no external RAM?

Within the 49-task example, the segment below, Option.ExtRamEnabled is false?

Code: Select all

#if (Option.ExtRamEnabled)
Option Ramsize 64*1024-Register.RamStart
#endif
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

stevech wrote:Is a ZX24n a mega644 with no external RAM?

Within the 49-task example, the segment below, Option.ExtRamEnabled is false?

Code: Select all

#if (Option.ExtRamEnabled)
Option Ramsize 64*1024-Register.RamStart
#endif
Exactly. The external RAM is only for the ZX-128e, ZX-1281e, ZX-1281, ZX-1280 and later the ZX-1280n. See my manytask.bas stress test where I fit 2021 tasks into a ZX-128e. This number can be increased still further once the limit is removed on how much memory can be allocated from the heap in one go.

On a ZVM mega644, we can run 106 tasks (in 4K RAM) but as Don explained the task context for native mode takes more space and only 49 tasks fit.
Mike Perks
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Re: Native Mode "ManyTasks"

Post by mikep »

dkinzer wrote:thus arriving at the 49 task mark.
Great. Can you post some sample output? I'm interested to see how much faster it runs. You can change the counter to a long and that will give you a better idea of how many counts you are achieving per task slot. However I didn't find any significant degradation in the VM until the number of tasks is large.
Last edited by mikep on 29 January 2008, 8:12 AM, edited 1 time in total.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

stevech wrote:Is a ZX24n a mega644 with no external RAM?
The ZX-24n is identical to a ZX-24a except that it uses the mega644P. The mega644P is nearly identical to the mega644 - it has a second hardware UART (available as Com2) and lower power draw.

The ZX-24n has the external serial EEPROM installed but it isn't used for anything since Program Memory is in internal Flash.
stevech wrote:Within the 49-task example, the segment below, Option.ExtRamEnabled is false?
Option.ExtRamEnabled return False on all devices that aren't capable of having external RAM. On those that are, it will return True only if external RAM has been enabled by setting the configuration.
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: Native Mode "ManyTasks"

Post by dkinzer »

mikep wrote:Can you post some sample output?
I tuned the stack sizes further and got the task count up to 57 using 2-byte counters. I also added code to clear the counter data after the average count data is output. This keeps the counters from rolling over.

The output on a ZX-24n with 57 tasks is

Code: Select all

:***** Elapsed time is 5.027344 seconds
Average count is 5405.088

***** Elapsed time is 5.025391 seconds
Average count is 5404.088
In comparison, on a ZX-24a with the number of tasks artificially limited to 57, the output is:

Code: Select all

***** Elapsed time is 5.039062 seconds
Average count is 1662.088

***** Elapsed time is 5.041016 seconds
Average count is 1662.07
The code size for the ZX-24n is 8628 bytes vs. 667 bytes for the ZX-24a. Of course, the code size for the VM device should also include 32K+ of code for the VM itself.

The attached code is modified to allow 1, 2, or 4-byte counters.
Attachments
manytasks.bas
(5.05 KiB) Downloaded 5 times
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Re: Native Mode "ManyTasks"

Post by mikep »

dkinzer wrote:This keeps the counters from rolling over.
Not necessarily for a byte counter of course.
dkinzer wrote:ZX-24n with 57 tasks is
Average count is 5405.088
ZX-24a with 57 tasks is
Average count is 1662.088
Not quite the speed up I expected but I don't have the measurement baseline of a single task counter in native mode. It all depends on the relative overhead of the sleep library call in each task and how long it takes to start the next task.
dkinzer wrote:The attached code is modified to allow 1, 2, or 4-byte counters.
Using a based variable helps but still doesn't remove the need for conditionally compiled code. I had the same problem in my original code and had tp use RamPoke, RamPokeWord and the SizeOf functions.

I noticed you went back to allocating all the counters in one go. As reported earlier this doesn't work for devices with extended RAM when the countersSize > the size of device RAM. Did you now remove that limitation for ZVM devices?

Code: Select all

countersSize = numberTasks * COUNTER_SIZE
counters = System.Alloc(countersSize)
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: Native Mode "ManyTasks"

Post by dkinzer »

mikep wrote:It all depends on the relative overhead of the sleep library call in each task and how long it takes to start the next task.
A task switch is "expensive" in native mode devices because the context that must be saved/restored is so large - 37 bytes vs 6 bytes. This difference in overhead is most noticeable when the task doesn't do much as is the case here.
mikep wrote:Did you now remove that limitation for ZVM devices?
The current development version of the VM has this limitation removed. It also implements the same concept of a "heap limit" that the native devices have along with the same mechanisms for setting the heap limit. These changes prevent the heap from encroaching on the Main() task stack.
- Don Kinzer
Locked