![]() |
Forum Index : Microcontroller and PC projects : CSUB for string reversal
Author | Message | ||||
Nimue![]() Guru ![]() Joined: 06/08/2020 Location: United KingdomPosts: 425 |
As a sanity exercise, I play with numbers (things like primes etc). Usually this happens in Python due to being able to code at work in lunch etc. Recently I've been busting out a 2040 PicoMite to code in MMBasic as an attemt to unwind after work. Currently playing with palindromic numbers and was scanning though 10,000 plus to find palindromic "cyclops" numbers "1230321" with a zero in the middle digit. Compound that to find palindromic, cyclops primes and the calculations sprial. This led me to testing string reversal in MMBASIC and its "slow" - not a moan given what's going on, but it was a bottle neck. So I thought I'd have a go at writing a CSUB. My C is not great and it took a fair few attempts and Google to get it working. To compile to ARM I asked ChatGPT (and especially for the "endian" and 32bit words etc). But it works... So here's the CSUB and a benchmark program. C code at the end. '========================================================= ' Reverse benchmark: BASIC vs CSUB ' - Assumes you already defined: CSUB REVERSE output$, input$ ' - Measures 10000 reversals on the same input string '========================================================= OPTION EXPLICIT CONST NITER = 10000 ' Number of iterations. My case testing 10000 ' ---------- Pure BASIC reverse (sub writes into dest$) ---------- SUB ReverseBasic dest$, src$ LOCAL i% dest$ = SPACE$(LEN(src$)) ' pre-allocate for speed FOR i% = LEN(src$) TO 1 STEP -1 MID$(dest$, LEN(src$)-i%+1, 1) = MID$(src$, i%, 1) NEXT i% END SUB ' ---------- Benchmark helper ---------- SUB BenchReverse method$, elapsed_ms% LOCAL i%, s$, r$, t0% s$ = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#" r$ = "" ' output buffer ' one warm-up to stabilize any first-use effects - added for consistency IF method$ = "BASIC" THEN ReverseBasic r$, s$ ELSEIF method$ = "CSUB" THEN ON ERROR SKIP 1 : REVERSE r$, s$ : IF MM.ERRNO <> 0 THEN PRINT "CSUB REVERSE not available. Skipping CSUB test." elapsed_ms% = -1 EXIT SUB ENDIF ENDIF TIMER = 0 ' reset millisecond timer IF method$ = "BASIC" THEN FOR i% = 1 TO NITER ReverseBasic r$, s$ NEXT i% ELSE FOR i% = 1 TO NITER REVERSE r$, s$ NEXT i% ENDIF elapsed_ms% = TIMER ' elapsed ms since reset END SUB ' ---------- Run both tests ---------- DIM t_basic%, t_csub% BenchReverse "BASIC", t_basic% BenchReverse "CSUB", t_csub% ' ---------- Report ---------- PRINT "Iterations: "; NITER IF t_basic% >= 0 THEN PRINT "BASIC total: "; t_basic%; " ms (~"; INT(NITER * 1000 / t_basic%); " ops/s)" IF t_csub% >= 0 THEN PRINT "CSUB total: "; t_csub%; " ms (~"; INT(NITER * 1000 / t_csub% ); " ops/s)" IF t_basic% > 0 AND t_csub% > 0 THEN PRINT "Speed-up (CSUB / BASIC): x"; (t_basic% / t_csub% * 100) ELSEIF t_csub% = -1 THEN PRINT "Note: CSUB timing skipped (REVERSE command not found)." ENDIF CSUB REVERSE string, string 'useage REVERSE output$, input$ 00000000 780CB530 190B1C42 D1014299 BD307004 3B01781D 32017015 0000E7F6 END CSUB This gives 500ms for the CSUB to reverse 10,000 strings compared to 65s for pure MMBasic. x12,000% speedup if my math works. I am sure my C can be improved: // REVERSE dest$, src$ (RP2040 / Cortex-M0+; length at index 0) // Offset added to get string length, capped to n=255 void REVERSE(char *dest, const char *src) { unsigned int n = ((const unsigned char*)src)[0]; // length at offset 0 if (n > 255) n = 255; // reverse: src[1 .. n] -> dest[1 .. n] for (unsigned int i = 0; i < n; i++) { dest[1 + i] = src[1 + (n - 1 - i)]; } // tell MMBasic how many chars are valid ((unsigned char*)dest)[0] = (unsigned char)n; } Note - no error trapping (other than basic string length) inside the C. Now for me, the other bottle neck is probably converting integers to strings in the first place and I'll have a go at doing something specific for ReverseInteger (where it takes an integer, converets inside the C to a string and spits out the integer) - but that's my fringe case use. This string reverse CSUB itself might be useful elsewhere. My C is rudimentary and still learning, so any insight greatly recieved. Happy Sunday. Entropy is not what it used to be |
||||
Nimue![]() Guru ![]() Joined: 06/08/2020 Location: United KingdomPosts: 425 |
Likewise for directly taking an integer and checking for palindrome without text conversion first: Useage: ISPALINDROM boolean_out%, integer% Need to Dim boolean_out% first ;-) CSUB ISPALINDROM integer, integer 00000000 680BB5F0 9303B085 684E2300 001A0007 DB0C429E 20000034 9D032100 4322002A 9B03D109 4043404E 42594333 603B414B B005607A 220ABDF0 F0002300 220AF833 91019000 00210028 F0002300 0005F807 9800000C 18809901 E7DF4159 D1152B00 D1132A00 DB062900 2800DC01 2000D006 084143C0 2180E002 20000609 4802B407 1840A101 BD039002 000000B5 4668B403 9802B501 F834F000 469E9B01 BC0CB002 46C04770 46CEB5F0 0C034699 0413469C 0C1B4647 000E001D 04044661 B5800C24 0C100007 4365434B 43604341 18C00C2C 468C1824 D90342A3 025B2380 44C44698 43794649 0C234356 0C2D042D 44631989 19600424 BCC018C9 46B046B9 46C0BDF0 46CEB5F8 B5804647 46984691 000D0004 F816F000 000E0007 000B0002 46414648 FFC0F7FF 418D1A24 00389B08 601C0031 BCC0605D 46B046B9 46C0BDF8 46C04770 4645B5F0 465746DE B5E0464E 9200B083 000D9301 9A019900 2D000004 0006DB61 2A00002F 9C00DB0C 42BD9D01 2000D91A B0032100 46BBBCF0 46A946B2 BDF046A0 424C2500 42BD4195 D100D8F1 2301E0AE 4699425B 9A00E00B 25009B01 419D4254 D8E442BD D10042BD 2300E09C 00294699 F0000020 4680F8C3 00300039 F8BEF000 1A1B4643 3B204698 E080D500 40990021 000B469A 46400021 000A4081 D82D42BB 4651D02A 419F1AB6 DA002900 2100E09B 24012000 91019000 408C4651 24019401 40AC4645 46449400 D11E2C00 99019800 2B00464B 0003D0AD 2100000C 41A14258 2700E7A7 41AF4266 DBB42A00 9D019C00 D89C42BD E059D1AB D9D242B1 21002000 90004644 2C009101 07DCD0E0 466146A4 46C40854 085D430C 42BDE014 42B4D101 0032D812 1B12003B 260141AB 415B1892 18B62700 2301415F 469B425B 466344DC D00A2B00 D9E842BD 425B2301 44DC469B 19B64663 2B00417F 9A00D1F4 46519B01 417B1992 DB252900 003D003C 464140CC 465140CD DB2D2900 408D0025 46450029 002040AC 418B1A12 93019200 4642E79E 469A0028 00212320 1A9B4090 000340D9 E777430B D90042B4 E75EE741 D80042B4 E73CE74D 21204640 00381A09 00344088 46400001 003D40C4 4641430C 465140CD DAD12900 21204640 40850026 40CE1A09 43310029 4641E7CB 25012420 20001A64 40E52100 91019000 E7609501 2900B510 F000D103 3020F807 0008E002 F802F000 46C0BD10 2301211C 4298041B 0C00D301 0A1B3910 D3014298 39080A00 4298091B 0900D301 A2023904 18405C10 46C04770 02020304 01010101 00000000 00000000 7FFFFDB0 00000001 END CSUB Works out about x9 - x10 quicker than native. Fun activity learning how to make CSUBs and use in MMBASIC. Entropy is not what it used to be |
||||
Plasmamac![]() Guru ![]() Joined: 31/01/2019 Location: GermanyPosts: 594 |
Cool , like it Plasma |
||||
JohnS Guru ![]() Joined: 18/11/2011 Location: United KingdomPosts: 4096 |
The C compiler's optimiser probably did it anyway - depending on what level of optimisation you had. I suppose you could use something like: for (unsigned int i = 1; i <= n; i++) { dest[i] = src[(n - i + 1)]; } Or use pointers, but fair chance any fairly modern C compiler in effect already does. You could get the ASM from the tool chain and look to see :) You may find the ASM easier to read than you expect - but would be tougher to write. John |
||||
Volhout Guru ![]() Joined: 05/03/2018 Location: NetherlandsPosts: 5357 |
Hi Nimue, I think I am missing the point. I looked at your "ispalindrome" list of hex numbers. And these are not palindromes in decimal nor hex notation. As for cyclops palindromes, why not simply generate them in the notation you envision ? 'cyclops palindromes decimal ASCII For a=48 To 57 For b=48 To 57 For c=48 To 57 Print Chr$(a);Chr$(b);Chr$(c);"0";Chr$(c);Chr$(b);Chr$(a);" "; Next :Next :Next Volhout Edited 2025-10-13 23:30 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Bleep Guru ![]() Joined: 09/01/2022 Location: United KingdomPosts: 666 |
Hi Harm, I think the 'list of hex numbers' you refer to is the compiled Csub, for you to put into some of your own basic code and try out? :-( Regards. |
||||
phil99![]() Guru ![]() Joined: 11/02/2018 Location: AustraliaPosts: 2782 |
Or at the command line. > dim n%,P% Don't even need to disturb the existing program on the Pico. Just add the CSub to the end of it in the editor.> for n%=1 to 10000 :ISPALINDROM P%,n% :if P% then :? n%, :endif :next 1 2 3 4 5 6 7 8 9 11 22 33 44 55 66 77 88 99 101 111 121 131 141 151 161 171 181 191 202 212 222 232 242 252 262 272 282 292 303 313 323 333 343 353 363 373 383 393 404 414 424 434 444 454 464 474 484 494 505 515 525 535 545 555 565 575 585 595 606 616 626 636 646 656 666 676 686 696 707 717 727 737 747 757 767 777 787 797 808 818 828 838 848 858 868 878 888 898 909 919 929 939 949 959 969 979 989 999 1001 1111 1221 1331 1441 1551 1661 1771 1881 1991 2002 2112 2222 2332 2442 2552 2662 2772 2882 2992 3003 3113 3223 3333 3443 3553 3663 3773 3883 3993 4004 4114 4224 4334 4444 4554 4664 4774 4884 4994 5005 5115 5225 5335 5445 5555 5665 5775 5885 5995 6006 6116 6226 6336 6446 6556 6666 6776 6886 6996 7007 7117 7227 7337 7447 7557 7667 7777 7887 7997 8008 8118 8228 8338 8448 8558 8668 8778 8888 8998 9009 9119 9229 9339 9449 9559 9669 9779 9889 9999 > Using Volhout's method to create it as a number. p% = val(Chr$(a)+Chr$(b)+Chr$(c)+"0"+Chr$(c)+Chr$(b)+Chr$(a)) :? p% Edited 2025-10-14 15:41 by phil99 |
||||
![]() |
![]() |
The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |