ZXSimulator

Anything QL Software or Programming Related.
User avatar
NormanDunbar
Forum Moderator
Posts: 1041
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Postby NormanDunbar » Sat Mar 21, 2020 3:27 pm

Yo can get the screen address for any channel given its channel id. You do need to call the SD_EXTOP call in QDOS - I can't remember its name in SMSQ - with something similar to the following:


Code: Select all

*--------------------------------------------------------------------*
* EXTOP routine used by SCREEN_BASE, returns address of the screen   *
* for the given channel number. On return, A0.L reverts back to the  *
* channel id as per the TRAP parameters set up above.                *
*--------------------------------------------------------------------*
* ENTRY                         | EXIT                               *
*                               |                                    *
* A0.L = Base of defn block     | D0.L = Zero                        *
* D1.L = Offset in defn block   | D1.W = Returned data               *
* REST = Who cares ?            | REST = Preserved                   *
*--------------------------------------------------------------------*
sb_extop   move.l  0(a0,d1.w),d1       Get data required
           moveq   #0,d0               No errors
           rts                         Finished


My code has a couple of routines which use the above, so there's the channel id in A0 and an offset into the channel definition block in D1, to get the base address of the screen this would be SD_SCRB. On return, D1.L will be holding the screen's address.

Calling the above is as follows:

Code: Select all

sb_open
           ; Assumes SCR_ or CON_ channel ID in A0.L.   
           moveq   #sd_extop,d0        Prepare to use the channel
           move.w  #SD_SCRB,d1       Get the offset required
           moveq   #-1,d3                  Take all the time in the world
           lea.l   sb_extop,a2             The routine to call
           trap    #3                           Do it
           tst.l   d0                            Did it work ? Zero = yes.
           ...


Sorry formatting is a bit off there, I think I have tabs and spaces!


Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals - https://www.amazon.co.uk/Arduino-Softwa ... 1484257898, https://www.apress.com/gb/book/9781484257890
User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Sat Mar 21, 2020 6:10 pm

Thanks again. First off, please note that I'm not ignoring your very helpful advice and I will peruse this eventually since I do want it to run as fast as possible and adding some machine code instructions will likely be needed. I will have to figure out either a) if Digital 'C' let's me do that and b) if not, do I move to C68 or manipulate it somehow indirectly in Digital 'C' via traps. My eventual hope is to put this out on Github and if I am unable to speed it up someone else will hopefully help and jump in -- in that case I may need to convert it to C68 and remove the Digital 'C' idiosyncrasies. This is also to ask, once I get there, if I can post some of your comments/code samples as well to get others started?

However, in the meantime, since this is still my spring break (towards the end of it), I'm having a bit of fun finding ways to optimize a really crappy compiler in Digital 'C' on a really slow machines. I just improved the grey stipple pattern by 15% (still slow but getting closer to character speed of about 6x real-time -- the stipple is the slowest since I'm doing 32 block calls). Here is my new crazy idea, instead of pixel drawing each character, I line draw them. Looking at each character, most will average 5 block calls and it will be much faster vs scanning the 40 bit binary string to see what is on and what is off. The "X" will require the most at 9 block calls since the block call can only go horizontal or vertical and not diagonal. I basically create a big switch statement and then embed relative block calls giving me lines going either horizontal or vertical (basically drawing them as a person would). I can also reduce code by combining '0', 'O', and 'Q' since they share the 'O' and only add either a diagonal line for the zero or a small dot for the letter q. Same goes with "F" and "E" with the latter adding an extra line.

This should speed things up a few fold I'm thinking and will make it workable. I think on average I do about 16 block calls for most characters and now I go to almost 1/3rd plus I remove the two nested loops checking to see if I need to do a block call at all. How much code will it add vs the reduction in 275 bytes of no longer needing to store the bitmap fonts plus all the support code will be interesting and important since I want my code to run on 128K. I'm thinking it could be ok as I'm adding over 250 function calls which needs to set up the 5 parameters each time so if that takes 10 bytes a piece that would add about 2K to the executable. Should have it done this afternoon.


User avatar
NormanDunbar
Forum Moderator
Posts: 1041
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Postby NormanDunbar » Sat Mar 21, 2020 6:29 pm

Hello again.

I didn't think you were ignoring me, so no worries on that front. ;)

All my code is free for any use you see fit. Help yourself.

I.ve had a quick look at my Digital C SE manual, and while the library gives the three traps and vector calls, you can write your own assembly routines and link them in according to chapter 5 of the manual. I haven't done so myself - yet. Looks relatively simple at first glance.

Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals - https://www.amazon.co.uk/Arduino-Softwa ... 1484257898, https://www.apress.com/gb/book/9781484257890
User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Sat Mar 21, 2020 8:19 pm

Woohoo, it worked. The code grew by about 3.5K but it sped things up almost 3 fold. Note that my original QL Emulator timings were off when I was running the TEST2_BAS loop (with input 25) at around 8.5 seconds, which was about 2.5 seconds faster than on an unexpanded QL. So 8.5 seconds under QLAY2 was the equivalent to 11 seconds on the unexpanded QL. I now have it down to around 4 seconds on the unexpanded QL by drawing the characters with lines and it looks just the same. Haven't done them all yet but will soon. So at 4-to-1 realtime it's getting pretty close to optimal. I think even if I use QL fonts the best currently with the BASIC interpreter is a little under 3-to-1 (the same program ran in 2.89 seconds on the BASIC using QL fonts). The two ZX81 emulators (EightOne and the online version) run it around 1.1 seconds.


User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Sun Mar 22, 2020 1:25 am

Here is the latest and greatest. The speed is much more usable now on an unexpanded QL. Here are the times for TEST2_BAS (input 25) on my unexpanded QL

  • EightyOne - ZX81 emulator running on windows - 1.10 seconds
  • JtyOne - online ZX81 emulator in Firefox - 1.19 seconds
  • cl - my original BASIC - 2.10 seconds
  • zx (previous version) - 11.1 seconds
  • zx (current version) - 3.79 seconds


So almost 3 times faster than the previous version and running at about 3x realtime. Here is the code that replaced the previous ZXputc. The previous one would go through 40 bits of information to figure out if a bit is on or off and, on average, would do 16+ block calls whereas the new one, on average, does 5 block calls. A good example of revisiting a bad algorithm and improving it...I kept thinking there had to be a better way and it dawned on me this morning.

Code: Select all

/* prints single ZX81 font character */
ZXputc (ch, ink, pap)
   char ch; int ink, pap;
{
   block(stdout, 8, 8, col, row, pap);

   switch (ch)
   {
      case '"':
         block(stdout, 1, 2, col+3, row+1, ink);
         block(stdout, 1, 2, col+6, row+1, ink);
         return;
      case '#':
         block(stdout, 3, 1, col+3, row+1, ink);
         block(stdout, 1, 4, col+2, row+2, ink);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 4, 1, col+1, row+3, ink);
         block(stdout, 6, 1, col+1, row+6, ink);
         return;
      case '$':
         block(stdout, 5, 5, col+2, row+2, ink);
         block(stdout, 4, 1, col+3, row+3, pap);
         block(stdout, 4, 1, col+2, row+5, pap);
         block(stdout, 1, 7, col+4, row+1, ink);
         return;
      case ':':
         block(stdout, 1, 1, col+3, row+3, ink);
         block(stdout, 1, 1, col+3, row+6, ink);
          return;
      case '(':
         block(stdout, 1, 1, col+5, row+1, ink);
         block(stdout, 1, 4, col+4, row+2, ink);
         block(stdout, 1, 1, col+5, row+6, ink);
         return;
      case ')':
         block(stdout, 1, 1, col+2, row+1, ink);
         block(stdout, 1, 4, col+3, row+2, ink);
         block(stdout, 1, 1, col+2, row+6, ink);
         return;
      case '>':
         block(stdout, 1, 1, col+2, row+2, ink);
         block(stdout, 1, 1, col+3, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 1, col+3, row+5, ink);
         block(stdout, 1, 1, col+2, row+6, ink);
         return;
      case '<':
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 1, col+4, row+3, ink);
         block(stdout, 1, 1, col+3, row+4, ink);
         block(stdout, 1, 1, col+4, row+5, ink);
         block(stdout, 1, 1, col+5, row+6, ink);
         return;
      case '=':
         block(stdout, 5, 1, col+2, row+3, ink);
         block(stdout, 5, 1, col+2, row+5, ink);
         return;
      case '+':
         block(stdout, 1, 5, col+4, row+2, ink);
      case '-':
         block(stdout, 5, 1, col+2, row+4, ink);
         return;
      case '_':
         block(stdout, 5, 1, col+2, row+6, ink);
         return;
      case '*':
         block(stdout, 1, 3, col+4, row+3, ink);
         block(stdout, 5, 1, col+2, row+4, ink);
         block(stdout, 1, 1, col+3, row+2, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 1, col+3, row+6, ink);
         block(stdout, 1, 1, col+5, row+6, ink);
         return;
      case '/':
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 1, 1, col+5, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 1, col+3, row+5, ink);
         block(stdout, 1, 1, col+2, row+6, ink);
         return;
      case ';':
         block(stdout, 1, 1, col+3, row+2, ink);
         block(stdout, 1, 2, col+3, row+5, ink);
         block(stdout, 1, 1, col+2, row+7, ink);
         return;
      case ',':
         block(stdout, 1, 2, col+4, row+5, ink);
         block(stdout, 1, 1, col+3, row+7, ink);
         return;
      case '.':
         block(stdout, 2, 2, col+3, row+5, ink);
         return;
      case ' ':
         return;

      /* NUMBERS */

/*->  case '0': <-- moved before letter O */
      case '1':
         block(stdout, 2, 1, col+3, row+1, ink);
         block(stdout, 1, 1, col+2, row+2, ink);
         block(stdout, 1, 4, col+4, row+2, ink);
         block(stdout, 5, 1, col+2, row+6, ink);
         return;
      case '2':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 1, col+1, row+2, ink);
         block(stdout, 1, 2, col+6, row+2, ink);
         block(stdout, 4, 1, col+2, row+4, ink);
         block(stdout, 1, 1, col+1, row+5, ink);
         block(stdout, 6, 1, col+1, row+6, ink);
         return;
      case '3':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 1, col+1, row+2, ink);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 2, 1, col+4, row+3, ink);
         block(stdout, 1, 2, col+6, row+4, ink);
         block(stdout, 1, 1, col+1, row+5, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
      case '4':
         block(stdout, 1, 6, col+4, row+1, ink);
         block(stdout, 1, 1, col+3, row+2, ink);
         block(stdout, 1, 1, col+2, row+3, ink);
         block(stdout, 1, 1, col+1, row+4, ink);
         block(stdout, 6, 1, col+1, row+5, ink);
         return;
      case '5':
         block(stdout, 6, 1, col+1, row+1, ink);
         block(stdout, 1, 2, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+3, ink);
         block(stdout, 1, 2, col+6, row+4, ink);
         block(stdout, 1, 1, col+1, row+5, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
      case '6':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 4, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+3, ink);
         block(stdout, 1, 2, col+6, row+4, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
      case '7':
         block(stdout, 6, 1, col+1, row+1, ink);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 1, 1, col+5, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 2, col+3, row+5, ink);
         return ;
      case '8':
         block(stdout, 4, 6, col+2, row+1, ink);
         block(stdout, 6, 4, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+2, pap);
         block(stdout, 4, 2, col+2, row+4, pap);
         block(stdout, 1, 1, col+1, row+3, pap);
         block(stdout, 1, 1, col+6, row+3, pap);
         return ;
      case '9':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 2, col+1, row+2, ink);
         block(stdout, 1, 4, col+6, row+2, ink);
         block(stdout, 4, 1, col+2, row+4, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;

      /* LETTERS */

      case 'A':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 5, col+1, row+2, ink);
         block(stdout, 1, 5, col+6, row+2, ink);
         block(stdout, 4, 1, col+2, row+4, ink);
         return;
      case 'B':
         block(stdout, 5, 6, col+1, row+1, ink);
         block(stdout, 4, 1, col+2, row+2, pap);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 4, 2, col+2, row+4, pap);
         block(stdout, 1, 2, col+6, row+4, ink);
         return;
/*->*/case 'G':
         block(stdout, 3, 1, col+4, row+4, ink);
      case 'C':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 4, col+1, row+2, ink);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 1, 1, col+6, row+5, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
      case 'D':
         block(stdout, 4, 1, col+1, row+1, ink);
         block(stdout, 1, 4, col+1, row+2, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 2, col+6, row+3, ink);
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 4, 1, col+1, row+6, ink);
         return;
      case 'E':
         block(stdout, 5, 1, col+2, row+6, ink);
      case 'F':
         block(stdout, 6, 1, col+1, row+1, ink);
         block(stdout, 1, 5, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+3, ink);
         return;
/*->  case 'G': <-- moved before letter C */
      case 'H':
         block(stdout, 1, 6, col+1, row+1, ink);
         block(stdout, 4, 1, col+2, row+3, ink);
         block(stdout, 1, 6, col+6, row+1, ink);
         return;
      case 'I':
         block(stdout, 5, 1, col+2, row+1, ink);
         block(stdout, 1, 4, col+4, row+2, ink);
         block(stdout, 5, 1, col+2, row+6, ink);
         return;
/*->*/case 'U':
         block(stdout, 1, 3, col+1, row+1, ink);
      case 'J':  /* adds extra block call 3 vs 4 */
         block(stdout, 1, 2, col+1, row+4, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         block(stdout, 1, 5, col+6, row+1, ink);
         return;
      case 'K':
         block(stdout, 1, 6, col+1, row+1, ink);
         block(stdout, 1, 1, col+5, row+1, ink);
         block(stdout, 1, 1, col+4, row+2, ink);
         block(stdout, 2, 1, col+2, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 1, 1, col+6, row+6, ink);
         return;
      case 'L':
         block(stdout, 1, 6, col+1, row+1, ink);
         block(stdout, 5, 1, col+2, row+6, ink);
         return;
      case 'M':
         block(stdout, 1, 6, col+1, row+1, ink);
         block(stdout, 1, 6, col+6, row+1, ink);
         block(stdout, 1, 1, col+2, row+2, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 2, 1, col+3, row+3, ink);
         return;
      case 'N':
         block(stdout, 1, 6, col+1, row+1, ink);
         block(stdout, 1, 1, col+2, row+2, ink);
         block(stdout, 1, 1, col+3, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 1, 6, col+6, row+1, ink);
         return;
/*->*/case '0': /* zero */
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 1, col+4, row+3, ink);
         block(stdout, 1, 1, col+3, row+4, ink);
         block(stdout, 1, 1, col+2, row+5, ink);
      case 'O':
         letter_O:
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 4, col+1, row+2, ink);
         block(stdout, 1, 4, col+6, row+2, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
/*->*/case 'R':
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 1, 1, col+6, row+6, ink);
      case 'P':
         block(stdout, 5, 1, col+1, row+1, ink);
         block(stdout, 1, 5, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+4, ink);
         block(stdout, 1, 2, col+6, row+2, ink);
         return;
      case 'Q':
         block(stdout, 1, 1, col+3, row+4, ink);
         block(stdout, 1, 1, col+4, row+5, ink);
         goto letter_O;
/*->  case 'R': <-- moved before letter P */
      case 'S':
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 1, col+1, row+2, ink);
         block(stdout, 4, 1, col+2, row+3, ink);
         block(stdout, 1, 2, col+6, row+4, ink);
         block(stdout, 1, 1, col+1, row+5, ink);
         block(stdout, 4, 1, col+2, row+6, ink);
         return;
      case 'T':
         block(stdout, 7, 1, col, row+1, ink);
         block(stdout, 1, 5, col+3, row+2, ink);
         return;
/*->  case 'U': <-- moved before letter J */
      case 'V':
         block(stdout, 1, 4, col+1, row+1, ink);
         block(stdout, 1, 4, col+6, row+1, ink);
         block(stdout, 1, 1, col+2, row+5, ink);
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 2, 1, col+3, row+6, ink);
         return;
      case 'W':
         block(stdout, 1, 5, col+1, row+1, ink);
         block(stdout, 1, 5, col+6, row+1, ink);
         block(stdout, 2, 1, col+3, row+5, ink);
         block(stdout, 1, 1, col+2, row+6, ink);
         block(stdout, 1, 1, col+5, row+6, ink);
         return;
      case 'X':
         block(stdout, 1, 1, col+1, row+1, ink);
         block(stdout, 1, 1, col+6, row+1, ink);
         block(stdout, 1, 1, col+2, row+2, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 2, 2, col+3, row+3, ink);
         block(stdout, 1, 1, col+2, row+5, ink);
         block(stdout, 1, 1, col+5, row+5, ink);
         block(stdout, 1, 1, col+1, row+6, ink);
         block(stdout, 1, 1, col+6, row+6, ink);
         return;
      case 'Y':
         block(stdout, 1, 1, col, row+1, ink);
         block(stdout, 1, 1, col+6, row+1, ink);
         block(stdout, 1, 1, col+1, row+2, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 1, col+2, row+3, ink);
         block(stdout, 1, 1, col+4, row+3, ink);
         block(stdout, 1, 3, col+3, row+4, ink);
         return;
      case 'Z':
         block(stdout, 6, 1, col+1, row+1, ink);
         block(stdout, 1, 1, col+5, row+2, ink);
         block(stdout, 1, 1, col+4, row+3, ink);
         block(stdout, 1, 1, col+3, row+4, ink);
         block(stdout, 1, 1, col+2, row+5, ink);
         block(stdout, 6, 1, col+1, row+6, ink);
         return;
      default:
         block(stdout, 4, 1, col+2, row+1, ink);
         block(stdout, 1, 1, col+1, row+2, ink);
         block(stdout, 1, 1, col+6, row+2, ink);
         block(stdout, 1, 1, col+5, row+3, ink);
         block(stdout, 1, 1, col+4, row+4, ink);
         block(stdout, 1, 1, col+4, row+6, ink);
         return;
   }
}


So I'm basically drawing each character as if I were using pen and paper...pretty neat. I also fixed the stipple pattern and am using paper 246 and 248 with block (so 32 block calls are now 2 block calls). It previously took 20 seconds to print a half-screen full and now takes about 3.5 seconds.

zx.zip
(21.13 KiB) Downloaded 9 times


stevepoole
Gold Card
Posts: 305
Joined: Mon Nov 24, 2014 2:03 pm

Re: ZXSimulator

Postby stevepoole » Sun Mar 22, 2020 6:46 am

Hi Bwinkel67,

There is another trick to print smaller charcters fast :

1 : Using standard font characteristics :
2 : Define your font characters, placing your pixels in the top left-hand corner.
3 : Print, using CURSOR for justification. (Beware of margins).

4 : The rom drivers then print at normal speed....

I use this and it works !

Regards,
Steve.


User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Sun Mar 22, 2020 10:11 pm

Wow, I forgot how simple ZX81 BASIC is. My interpreter can do this:

Code: Select all

10 LET MSG$="HELLO"
20 PRINT MSG$;
30 GOTO 20


But the ZX81 can only do single character variables for strings. Looks like you can have multiple character/number identifiers for numeric variables:

Code: Select all

LET COUNT = 10


...but not for strings. So the above can only be:

Code: Select all

10 LET M$="HELLO"
20 PRINT M$;
30 GOTO 20


So I get to strip out some of my string variable coe...


User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Sun Mar 22, 2020 11:25 pm

New timings. So even though my character output is 3 times slower than the base ZX81, it turns out fundamental operations like assignment slow down drastically on the ZX81. The program below gives the following results on the online ZX81 emulator (which runs almost at identical speed to the EightyOne emulator):

Code: Select all

10 LET M$="HELLO"
20 PRINT M$
30 LET COUNT=1
40 PRINT COUNT


  • EightyOne - 1.15 seconds
  • ZXSimulator - 0.35 seconds

So even though the graphics part on the ZXSimulator is slower by a factor of 3 it does the internal stuff so much more efficiently (like variable memory management) that it runs 3x faster (or 1/3rd realtime). So I should now focus on implementing more BASIC features to see what a bigger ZX81 program looks like. It may take longer to print some graphics but in between those, doing computations, ZXSimulator should catch up nicely.


User avatar
NormanDunbar
Forum Moderator
Posts: 1041
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: ZXSimulator

Postby NormanDunbar » Mon Mar 23, 2020 4:03 pm

Hi bwinkel67,

a couple of options on the speeding up front, which perhaps might be useful:

instead of calling streql() in the command decoding function, BASICcmds(), why not use lexcmp() or strcmp()? They are built in, probably written in assembler (but don't quote me) and if you really need a result of 1 for equal strings, do this:

Code: Select all

if (!strcmp("BEEP", cmd) {
    ...
}


or:

Code: Select all

if (!lexcmp("BEEP", cmd) {
    ...
}


That does case significant comparisons, lexcmp() does case insensitive comparisons. Strcmp() is probably going to be quicker as it doesn't have to do any case conversion of the parameters, possibly even copying them to temporary memory before changing them.

Maybe if you tokenise the Basic Command keywords somehow, you could then use a case statement rather than a nested if - that way you could also short circuit the number of string comparisons you have to do, by comparing the first letter and on a match, only then comparing with other known commands which also begin with that letter. For example, if the first letter is an 'E', you only need compare the rest for 'ELSE','END','EDIT' or 'EXIT' and you can ignore the remainder of the commands as they simply will not apply.

You could also attempt to speed this up by analysing many ZX-81 programs to see which commands get used most often, then check for the user's input in that order, if you follow?

Just a thought. It's a shame you can't define an array of pointers in Digital C SE, however, I'm sure that an unsigned int array could masquerade as an array of function pointers allowing you to create a jump table of command functions that can be jumped to when you figure out the command you have been passed?

Have fun.


Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals - https://www.amazon.co.uk/Arduino-Softwa ... 1484257898, https://www.apress.com/gb/book/9781484257890
User avatar
bwinkel67
Gold Card
Posts: 409
Joined: Thu Oct 03, 2019 2:09 am

Re: ZXSimulator

Postby bwinkel67 » Mon Mar 23, 2020 6:55 pm

I actually wrote my own because I didn't trust strcmp to be implemented optimally and this is considered to be one of the quicker ways.

Code: Select all

streql (s,t)
   char *s, *t;
{
   while (*s == *t) { if (*s == '\0') return 1; s++; t++; }
   return 0;
}   /* end streql */


I think I ran tests back in the day and this was the same as strcmp in Digital 'C' SE. I kept streql since I was eventually porting it to the Mac and didn't want to re-run those test on that platform :-/ I agree with you that lexcmp will be inefficient. I think for the ZXSimulator I just upcase everything when I read it in so that's a one-and-done time hit.

The tiered case statement is a very good idea and another way some recursive descent parsing is done, though in the end you do have to check the entire string token to see if it is correct, otherwise you could have prinx instead of print. It can get you quicker to the right command but in the end you do as many compares. My fear is in how efficient the switch is implemented in Digital 'C' SE. My tests with the graphics switch statement seem to suggest it is not optimal.

The best way is to create an internal representation in numeric form (what I call tokenize) so when you see a string token once yo convert it to a numeric token and then can do numeric comparisons (integer). The problem is that it takes up additional space since you have to keep the tokenized version internally for running and text version internally for editing (so now you are creating a parse tree). If you try to untokenize when editing it adds a lot of code so you lose space either way. I want to have this run on an unexpanded QL so it's the classic battle of space vs time :-)

The other means would be to use a hash. You still need to run through the entire string of each string token, add them up, and then use the numeric value to access an array that could hold "goto labels" that you then jump to. That may not add too much overhead and could speed things up and is something I will look at. I think I saw somewhere a long time ago that this was a way to efficiently implement a switch but I'm sure there are others.

Anyway, that is the fun of implementing a program language and I love optimization problems. Thanks for taking a peek at my interpreter. Keep the suggestions coming. Dialog like this will only help in making it faster. BTW, I believe I use a hash for my variable look-up (I know the final version on the Mac had it but I'm guessing I did it here first) and maybe the ZX81's ROM is not and why the LET COUNT=1 takes so long.



Who is online

Users browsing this forum: Google [Bot] and 4 guests