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.
Native Mode "ManyTasks"
Is a ZX24n a mega644 with no external RAM?
Within the 49-task example, the segment below, Option.ExtRamEnabled is false?
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.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
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
Re: Native Mode "ManyTasks"
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.dkinzer wrote:thus arriving at the 49 task mark.
Last edited by mikep on 29 January 2008, 8:12 AM, edited 1 time in total.
Mike Perks
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.stevech wrote:Is a ZX24n a mega644 with no external RAM?
The ZX-24n has the external serial EEPROM installed but it isn't used for anything since Program Memory is in internal Flash.
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.stevech wrote:Within the 49-task example, the segment below, Option.ExtRamEnabled is false?
- Don Kinzer
Re: Native Mode "ManyTasks"
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.mikep wrote:Can you post some sample output?
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
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 attached code is modified to allow 1, 2, or 4-byte counters.
- Attachments
-
- manytasks.bas
- (5.05 KiB) Downloaded 5 times
- Don Kinzer
Re: Native Mode "ManyTasks"
Not necessarily for a byte counter of course.dkinzer wrote:This keeps the counters from rolling over.
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:ZX-24n with 57 tasks is
Average count is 5405.088
ZX-24a with 57 tasks is
Average count is 1662.088
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.dkinzer wrote:The attached code is modified to allow 1, 2, or 4-byte counters.
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
Re: Native Mode "ManyTasks"
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: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.
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.mikep wrote:Did you now remove that limitation for ZVM devices?
- Don Kinzer