How to make Harbour EXE faster than Clipper EXE?

502 views
Skip to first unread message

ZooCary

unread,
Aug 28, 2023, 4:11:54 PM8/28/23
to Harbour Users
We have a very simple Windows speed test PRG/EXE that counts from 1 - 100,000...

...and the Clipper EXE runs 5 - 10x faster than the Harbour EXE. : )

Why?:)

- - - -> Question: How can we speed up the Harbour EXE so that it is at least as fast as the Clipper EXE?


Notes:

* Clipper EXE preparation
The Clipper EXE is created with a simple typical DOS command sequence: "clipper speed.prg /b/n/w", and then "blinker fi speed, cmx52.lib"


* The Harbour Test
We tested the harbour EXE on multiple desktops and laptops, running different versions of Windows (2000 and newer).
-> Harbour EXE is always slower than the Clipper EXE results on older Windows computers, between 5x and 10X slower


* The Clipper Test
The Clipper EXE could only be tested on older Windows (2000, etc.) that support Clipper EXEs.
We were able to test the Harbour EXE on the same older desktops that also run the Clipper EXE.  
On the older Windows that can run both Habour and Clipper, the Clipper EXE is about 10x faster.


* Attached
-> PRG code;
-> Harbour HBP file used to create the Harbour EXE.


--------------------------> start: PRG code;

PROCEDURE Speed

LOCAL x, nStartTime

CLEAR SCREEN

@8,10 SAY "SPEED TEST!!"

@ 10,10 SAY "Count to 100,000 - See how fast the computer can go!!"

nStartTime = SECONDS()

FOR x = 1 TO 100000
    @ 12,10 SAY ALLTRIM( STR( x ) ) + " / 100,000"
NEXT x

@ 14,10 SAY "Seconds: " + ALLTRIM( STR( SECONDS() - nStartTime ) )

@ 16,10 SAY "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
@ 18,10 SAY "8/24/2023 - Clipper on Old Computer: 1.76 - 1.82 Seconds"
@ 19,10 SAY "8/24/2023 - Harbour on Laptop: 7.25 - 10.85 Seconds"
@ 20,10 SAY "8/24/2023 - Harbour on Desktop: 5.73 - 5.92 Seconds"

?
?

RETURN

--------------------------> end: PRG code;

--------------------------> start: Harbour HBP file used to create the Harbour EXE.

#
# Harbour link file - speed.hbp
#

-ospeednew

# slower
# -fullstatic
# -shared  
# -static
# -mt
# -st
# -std
# -full
# -nulrdd
# -Os
# -lhbblink
# -lhbwin    
# -info
# -gui  

# no obvious affect
# -nodebug
# -noimplib  
# -notrace
# -nohbcppmm      
# -nolibgrouping
# -jobs=1
# -nominipo
# -hb10
# -hb20
# -xhb
# -hbc    
# -exospace    
# -platform=win|dos; -plat=mingw64
# -cpu=win|dos
# -lhbrtl
# -lhbct
# -lhbcairo
# -inc
# -debug            REMOVED 8/24, improved speed
# -exospace
# -lpng
# -lhbzlib

# FASTER by a bit!!!!!
-b
-optim
-nocpp
-nomap
-nomiscsyslib
-strip  

# SMALL EXE FILE SIZE
-compr=max

SPEED
--------------------------> end: Harbour HBP file used to create the Harbour EXE.

ZooCary

unread,
Aug 28, 2023, 4:16:46 PM8/28/23
to Harbour Users
hbmk2 info:

G:\database>hbmk2 speed.hbp -info
hbmk2: Autodetected platform: win
hbmk2: Autodetected C compiler: mingw
hbmk2: Using Harbour: C:\harbour\hb30\bin C:\harbour\hb30\include
       C:\harbour\hb30\lib\win\mingw C:\harbour\hb30\bin
hbmk2: Using C compiler: C:\harbour\hb30\comp\mingw\bin
Harbour 3.0.0 (Rev. 16951)
Copyright (c) 1999-2011, http://harbour-project.org/
Compiling 'SPEED.prg'...
Lines 64, Functions/Procedures 1
Generating C source output to 'C:\Users\AppData\Local\Temp\hbmk_ltinew.dir\SPEED.c'... Done.
hbmk2: Linking... speednew.exe
                       Ultimate Packer for eXecutables                                                    Copyright (C) 1996 - 2010                             UPX 3.07w       Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 08th 2010                                                                                           File size         Ratio      Format      Name                              --------------------   ------   -----------   -----------                       1156608 ->    435712   37.67%    win32/pe     speednew.exe                                                                                                   Packed 1 file.                                                 

Rafa Pabd

unread,
Aug 28, 2023, 4:46:48 PM8/28/23
to Harbour Users
suppress packer upx

Hansmarc

unread,
Aug 28, 2023, 4:51:11 PM8/28/23
to Harbour Users
Hi Zoocary,

I don't think that your test is a real life example.

By experience a noticed that the 'SAY' statement is the only and i repeat the only one responsable for this overhead of time in the loop.
The 'for next' loop takes only 0.02 sec to execute without it.
And i takes only 0.08 sec. if you replace the SAY with by example the next line,    s := ALLTRIM( STR( i ) ) + " / 100,000".
This proves that the SAY is responsable for the overhead.
I think that this overhead is caused by the graphical lib used to display data as a old DOS window.

So if you want to display a progress while doing something in a loop or do while do the following :
   use a extra counter to display some info.
   In your example i would refresh the display only 100000/10 steps, thus each 10000 steps.
   The display would thus be refreshed only 10 times.

Regards
Hans

Hansmarc

unread,
Aug 28, 2023, 5:02:38 PM8/28/23
to Harbour Users
Hi again,

forgot to say that a loop with 100000 iterations and with the SAY statement took 240sec on my portable.
Config of my laptop, Intel i7 from 5 years ago, 16GB ram.

Zoocary, i don't know how you got your values ???
@ 19,10 SAY "8/24/2023 - Harbour on Laptop: 7.25 - 10.85 Seconds"
@ 20,10 SAY "8/24/2023 - Harbour on Desktop: 5.73 - 5.92 Seconds"

Regards
Hans

alexwi

unread,
Aug 28, 2023, 5:03:20 PM8/28/23
to Harbour Users
I agree with Hans. The overhead of  displaying anything is greater than most people expect.

It's not noticeable until the computer has to display a few million lines of text. I have a robocopy task that must copy a lot of files and the time it takes when I turn off logging and display and just send its output to a file is orders of magnitude smaller than if I want to watch what's going on.

Same for something as simple as "dir *.* /s" and dir *.* /s > outputfile.txt"

Alex

José M. C. Quintas

unread,
Aug 28, 2023, 5:05:50 PM8/28/23
to harbou...@googlegroups.com

Change test to do this, and check result comparing Clipper and Harbour

nStartTime = SECONDS()
cTime := Time()


FOR x = 1 TO 100000

   IF Time() != cTime


       @ 12,10 SAY ALLTRIM( STR( x ) ) + " / 100,000"

       cTime := Time()
    ENDIF
NEXT x

@ 14,10 SAY "Seconds: " + ALLTRIM( STR( SECONDS() - nStartTime ) )

José M. C. Quintas

--
You received this message because you are subscribed to the Google Groups "Harbour Users" group.
Unsubscribe: harbour-user...@googlegroups.com
Web: https://groups.google.com/group/harbour-users
---
You received this message because you are subscribed to the Google Groups "Harbour Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to harbour-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/harbour-users/94a441e9-628f-4bff-a96c-70dade4fc3e6n%40googlegroups.com.

Eric Lendvai

unread,
Aug 29, 2023, 12:45:41 AM8/29/23
to Harbour Users
I had to same problem in the past with the @ SAY.
But Harbour is one of the best language to create web apps (FastCGI)
And for desktop apps, you also may want to check out the following:  https://github.com/EricLendvai/Harbour_WebView
,including the youtube video that shows a demo.

Antonio Linares

unread,
Aug 29, 2023, 2:10:37 AM8/29/23
to Harbour Users
Clipper uses pure assembler in many core parts of it. This provides the fastest speed though it makes its low level code not portable at all.

Harbour uses C language, really fast, but not as efficient as highly optimized assembler though the advantage is portable code (32, 64, Windows, Linux, Mac, etc)

ZooCary

unread,
Aug 29, 2023, 4:34:06 PM8/29/23
to Harbour Users
We do reduce the EXE size with this hbmk2 command: " -compr=max"

The resulting EXE is truly much smaller, but it doesn't cause a noticeable reduction in processing speed. : ) 

ZooCary

unread,
Aug 29, 2023, 5:04:40 PM8/29/23
to Harbour Users
José,

Thank you for your idea! : ) 

We made the changes to the PRG.. and then recompiled the EXE with both Harbour and Clipper...

There truly is no noticeable difference from the previous PRG / EXE  : ) :

Harbour EXE on "New Windows 10 computer, 12 GB RAM":   +/- 8.10 seconds
Harbour EXE on "Old Windows 2000 computer, about 0.5 GB RAM":  +/- 13.80 seconds
Clipper EXE on the _same_ "Old Windows 2000 computer, about 0.5 GB RAM" :  +/- 1.76 seconds

: ) 

Antonio's response: "Clipper uses pure assembler in many core parts of it. This provides the fastest speed though it makes its low level code not portable at all.
Harbour uses C language, really fast, but not as efficient as highly optimized assembler though the advantage is portable code (32, 64, Windows, Linux, Mac, etc)"...

... does really sum it up quite well.  That really is the truth.  It is what it is.  It is essentially Clipper low-level assembly language vs. Harbour... which might not be a very fair race test.  It's not the whole story.  There are many obvious Harbour things that Clipper is simply not capable of...  : )  

We still continue to be amazed at the _stark_ difference in speed running times... I assumed an older Windows 2000 box running a Clipper version would not be faster, and certainly not _so much_ faster (!!!), than a newer Windows box running a Harbour version...

The differences were so dramatic, that we began do doubt ourselves internally and how we were compiling our Harbour EXEs! : )   We assumed that we must be doing something inefficiently, and that there must be a smarter hbmk2/HBP way that would result in a Harbour EXE that would be at least neck-and-neck in running times.

We did make some mods to the hbmk2/HBP setup that reduce the times... and this is where we are at now.: ) 

We have run the Harbour improved HBP EXE on some of our fastest  : )  in-house machines, and we can get the speed test time down to about +/- 4.4 seconds.   That's where we are so far... : ) 

: ) 

ZooCary

Francesco Perillo

unread,
Aug 29, 2023, 5:19:22 PM8/29/23
to harbou...@googlegroups.com
Have you tried switching GT ?

And I think nobody ever said that Harbour should be quicker than native Clipper :-)


José M. C. Quintas

unread,
Aug 29, 2023, 5:33:10 PM8/29/23
to harbou...@googlegroups.com
Same test on my old computer: 0.07s

an old Intel i5 2320, harbour 3.2 Windows 10

nStartTime = SECONDS()
cTime := Time()
FOR x = 1 TO 100000
   IF Time() != cTime
       @ 12,10 SAY ALLTRIM( STR( x ) ) + " / 100,000"
       cTime := Time()
    ENDIF
NEXT x

@ 14,10 SAY "Seconds: " + ALLTRIM( STR( SECONDS() - nStartTime ) )

José M. C. Quintas


jlca...@gmail.com

unread,
Aug 29, 2023, 5:34:02 PM8/29/23
to Harbour Users
I run Clipper created .EXEs on Windows 11 using MSDOS Player.

I've written a thread on using MSDOS Player on Windows 11, with examples.

The source code for MSDOS Player is available, so you can customize it if necessary.

I've never done testing between a Clipper .EXE and a Harbour .EXE, so YMMV.

Joe

alexwi

unread,
Aug 29, 2023, 7:44:58 PM8/29/23
to Harbour Users
Regardless of how academically interesting this experiment might be, am I the only one who finds it absurd?

If you're going to run benchmarks, it would make sense to run those based on the products' intended functionality, which is data, not displaying arbitrary text on the screen, which, in turn, regardless of the outcome, is still faster than what is practically required.

ZooCary, why don't you give us some context to make this exercise a little bit less moot?

Alex

Francesco Perillo

unread,
Aug 30, 2023, 3:41:00 AM8/30/23
to harbou...@googlegroups.com

I think that this difference is quite normal and also expected.

Let's call C the executable compiled with clipper and H the executable compiled with Harbour.

C is a MSDOS program. It know nothing about winows, GDI, GUI, fonts, rasters and whatever. When he executes @ 0,0 say "A" it just puts 65 in the first byte of the TEXT MODE video buffer and probably another byte for foreground/background color. We are talking about CGA/EGA/VGA in TEXT MODE.

When you start C inside Windows, it creates a sort of virtual machines that mimicks the old MSDOS and some of the hardware.

I'm quite sure that C just continues to write bytes in the fake TEXT MODE video buffer and then Windows, with his own timings, updates the screen.  It is an async process. You may try to use other GT systems in clipper, but I never used others.


H is actually a real windows program and behaves in a completely different way. I'm quite sure that it uses a video buffer to keep track of what is on screen (for savescreen() for example) but then it may be possible that it syncs to the screen immediately. And writing a char on the screen is way way way more expensive than in text mode. You don't just put bytes in video ram, you call windows functions that takes care of drawing the chars on screen, with antialising and a lot of other stupid issues that fonts have.

I'm not sure 100% since I just browsed qtqtc source code... You may have a look in the other GTs source code and test different ones.


AL67

unread,
Aug 30, 2023, 7:44:24 AM8/30/23
to Harbour Users
Because Harbor refreshes the screen after every SAY or QOUT operation.
For Clipper, Windows refreshes the screen only a few times per second.

See what happens if we do the same in Harbor:     :-)

------------------------------
PROCEDURE main
LOCAL i,t
LOCAL xx:="Try to edit this                          "

CLS
@ 0,0 SAY PADC("Test extension console - autorefresh inside DISPBEGIN",MAXCOL()+1) COLOR "R+/B"

t:=SECONDS()
FOR i:= 1 TO 100000       // ONLY 100,000
  @ 1,0 SAY "Normal   100.000 (only) no DISPBEGIN"
  @ 1,40 SAY i
NEXT
@ 1,50 SAY SECONDS()-t ; ?? "  (x 10)"

t:=SECONDS()
FOR i:= 1 TO 1000000       // 1,0000,000
DISPBEGIN()
  @ 2,0 SAY "Normal 1.000.000 with DISPBEGIN"
  @ 2,40 SAY i
DISPEND()
NEXT
@ 2,50 SAY SECONDS()-t

t:=SECONDS()
DISPBEGIN()
FOR i:= 1 TO 1000000
  @ 3,0 SAY "Normal 1.000.000 -  ALL inside DISPBEGIN"
  @ 3,40 SAY i
NEXT
DISPEND()
@ 3,50 SAY SECONDS()-t

@ 4,0 SAY "Normal GET:" GET xx
READ


// START EXTENSION  :-)
DISPBEGIN()
AutoConsoleRefresh()  //default 50ms

t:=SECONDS()
FOR i:= 1 TO 1000000    // 1,000,000  !!!
  @ 5,0 SAY "1.000.000 DISPBEGIN + AutoRefrash 50ms"
  @ 5,50 SAY i
NEXT
@ 5,50 SAY SECONDS()-t

xx:="Try to edit this (I think work OK)        "
@ 6,0 SAY "GET inside DISPBEGIN + AutoRefrash 50ms:" GET xx
READ

AutoConsoleRefresh(200)   //set 200ms
t:=SECONDS()
FOR i:= 1 TO 1000000    // 1,000,000  !!!
  @ 7,0 SAY "1.000.000 DISPBEGIN + AutoRefrash 200ms"
  @ 7,40 SAY i
NEXT
@ 7,50 SAY SECONDS()-t
xx:="Try to edit this (I think work no good)   "
@ 8,0 SAY "GET inside DISPBEGIN + AutoRefrash 200ms:" GET xx
READ

//END EXTENSION
AutoConsoleRefresh(0)          //OFF and kill thread
DISPEND()

@ 10,0 SAY "test complete"
INKEY(0)



RETURN


*****************************************
*****************************************

#pragma BEGINDUMP


#include <windows.h>

#include "hbapi.h"
#include "hbapigt.h"


//***************************************
DWORD  MyThreadProc( LPDWORD lpParameter )
{
  static DWORD nWait ;
  static BOOL lLoop = TRUE ;
  static BOOL lRun = FALSE ;
  if ( !(*lpParameter) )
  {
     lLoop = FALSE ;
  }
  else
  {
     nWait = (DWORD) *lpParameter ;
     lLoop =  TRUE ;
     if ( !lRun )
     {
        lRun = TRUE ;
        while ( lLoop )
        {
           Sleep( nWait );
           if ( hb_gtDispCount() == 1 )
           {
              hb_gtDispEnd() ;
              hb_gtDispBegin() ;
           }
        }
        lRun = FALSE ;
     }
  }
  return 0 ;
}
//**************************************

HB_FUNC ( AUTOCONSOLEREFRESH )
{
  static HANDLE hThread = NULL ;
  DWORD  ThreadId ;
  static DWORD  nWait = 50 ;
  static lRun = FALSE ;
  if ( HB_ISNUM( 1 ) )
  {
     nWait = (DWORD) hb_parnl( 1 ) ;
  }
  else
  {
     nWait = 50 ;    //default value
  }
  if ( nWait > 0 )    // > 0
  {
    if ( lRun )
     {
       MyThreadProc(&nWait) ;       //send new value
     }
     else
     {                                        //make thread
       hThread = CreateThread(NULL, NULL,
          (LPTHREAD_START_ROUTINE) MyThreadProc ,
          &nWait, NULL, &ThreadId);
       lRun = !(hThread == NULL) ;
       hb_retl( lRun ) ;
     }
  }
  else      //nWait <= 0
  {
     if ( nWait==0  &&  lRun )
     {
       ThreadId = 0 ;
       MyThreadProc(&ThreadId) ;   //send info "break loop"
       WaitForSingleObject(hThread, INFINITE ) ; //wait for end thread
       lRun = FALSE ;
       CloseHandle(hThread) ;
       hThread = NULL ;
       hb_retl( TRUE ) ;
     }
     else
     {
       hb_retl( FALSE ) ;
     }
  }
}

#pragma ENDDUMP


--------------------------------------
Adam

Charly 9000

unread,
Aug 30, 2023, 1:40:07 PM8/30/23
to Harbour Users
Big performance AL67

C.

ZooCary

unread,
Aug 30, 2023, 3:29:54 PM8/30/23
to Harbour Users
*****************************

Good afternoon!

Sincerely: Thank you to everyone for their guidance and responses!

And, __SINCERELY__: Sorry!!!!

We are sorry for our unawareness of the unique difference between Clipper and Harbour when it comes to the nuance of screen displays.

We were unaware that the way Clipper and Harbour display text on the screen is so drastically different...

AL67: "Because Harbor refreshes the screen after every SAY or QOUT operation.

For Clipper, Windows refreshes the screen only a few times per second."

Francesco Perillo: "Clipper is a MSDOS program. It know nothing about windows, GDI, GUI, fonts, rasters and whatever. When he executes @ 0,0 say "A" it just puts 65 in the first byte of the TEXT MODE video buffer and probably another byte for foreground/background color. We are talking about CGA/EGA/VGA in TEXT MODE.  ...  Harbour is actually a real windows program and behaves in a completely different way. "

... and now it all makes sense. : )  It makes sense that running a loop of 1,000,000 displays on a screen will really highlight this difference.  And of course, a loop of 1,000,000 displays is moot and pointless in and of itself.  This will rarely (never?) happen in real life.  : )

So our team is truly sorry for presenting this rabbit hole. : )

Our initial intention was honoroable: Create a simple speed test between Harbour and Cliipper...  Use this speed test as a baseline to try to improve and optimize the Harbor EXE creation to maximize the speed, and remove unecessary components.  What we initially saw in our initial speed test was a shock: Clipper seemed faster (!!!) than Harbour.  So that became our goal - try to improve the Harbour speed to at least catch up to Clipper.

We thought we were comparing apples to apples with a simple timed loop.  : ) But now we understand that we were comparing apples to oranges due to the difference in display techniques.

Soo...  we rewrote our initial test loop, so that it just displays a screen update every 50,000th marker:

nStartTime = SECONDS()
nCount = 0

FOR x = 1 TO 1000000

    IF x / 50000 = INT( x / 50000 )

       @ 12,10 SAY ALLTRIM( STR( nCount ) ) + " screen displays: " + ALLTRIM( STR( x ) ) + " / 1,000,000"
        nCount ++

    ENDIF


NEXT x

@ 14,10 SAY "Seconds: " + ALLTRIM( STR( SECONDS() - nStartTime ) )

And these were the results:

Clipper EXE on a Windows 2000 box: 10.6 seconds
Harbour EXE on a Windows 2000 box: 2.36 seconds
Harbour EXE on a Windows 10 laptop: 0.23 seconds


***** ->>>>  Soo...  Harbour is at least 5X as fast as Clipper. :))   !!!!! ******


Alexwi: "Regardless of how academically interesting this experiment might be, am I the only one who finds it absurd?

If you're going to run benchmarks, it would make sense to run those based on the products' intended functionality, which is data, not displaying arbitrary text on the screen, which, in turn, regardless of the outcome, is still faster than what is practically required."

Alexwi, you're quite right: Our intiial test was absurd, and unintentionally misleading.

You're right.  We were wrong. : ))

We sincerely apologize!!!!!!!

Our intention was simply to create simple speed test, and use it as a way to optimize our Harbour EXE compilation.

The net result is that we now do have a better understand of the display differences between Clipper and Harbour...   and it is true that the experiment of a 1-1,000,000 display loop does nto exist in the real world, and was moot. : )

Our "speed test", and the resulting group discussion here was useful, and may be useful for future users. : ))

ZooCary

*****************************

alexwi

unread,
Aug 30, 2023, 4:51:03 PM8/30/23
to Harbour Users
There's nothing to apologize for. Thanks to you, now we know who gets easily distracted and has extra time in their hands in this group ;-)

Would be interesting to see benchmark comparisons with other database products, such as vfp and some of the client/server back-ends. I remember going to PC Expo (when it still ran) and seeing fox (and then MS) run queries for "Elm Street" against a table that supposedly contained every street in the US.

Alex

Francesco Perillo

unread,
Sep 3, 2023, 4:12:55 AM9/3/23
to harbou...@googlegroups.com
Hi ZooCary,
I run your new test and I got a variable result between 0.20 and 0.23.

Since today I have some spare time I did a few tests.
First of all, variables are automatically defined PRIVATE and so are handled in a specif way. Declare them LOCAL:

LOCAL nStartTime
LOCAL nCount
LOCAL x

and my run time is now 0.16-0.19

So I changed the algorithm (why TWO division ??!!!??!!!) a bit and lowered time to 0.10-0.12.

But I made one error, did not define the variable LOCAL... Now I did and running time is less than 0.1...

Useless test of course :-)))) Nobody will care if an action lasts 0.2 or 0.1 seconds but if you have to perform the action millions of times....

procedure main

LOCAL nStartTime
LOCAL nCount
LOCAL x
LOCAL nDisplay

nStartTime = SECONDS()
nCount = 0

nDisplay := 0


FOR x = 1 TO 1000000

    IF nDisplay == 0


       @ 12,10 SAY ALLTRIM( STR( nCount ) ) + " screen displays: " + ALLTRIM( STR( x ) ) + " / 1,000,000"
       nCount ++
       nDisplay := 50000
    ENDIF
    nDisplay--


NEXT x

@ 14,10 SAY "Seconds: " + ALLTRIM( STR( SECONDS() - nStartTime ) )

The results on screen are not 1:1 on your example, I like optimizing code, but I don't have so much time to spare :-)))))



--
You received this message because you are subscribed to the Google Groups "Harbour Users" group.
Unsubscribe: harbour-user...@googlegroups.com
Web: https://groups.google.com/group/harbour-users
---
You received this message because you are subscribed to the Google Groups "Harbour Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to harbour-user...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages