Announcement

Collapse
No announcement yet.

DIRTYCALL convention 2

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • DIRTYCALL convention 2

    Here is version 2 of DIRTYCALL with a comparison in time to using the
    normal DLL calling technique through the stack. The times show that
    DIRTYCALL is more than twice as fast, even though it has to manually
    dereference the addresses and load 4 global variables with the
    results.

    The speed difference is due to DIRTYCALL having no stack overhead, even
    though it manually converts the values.

    Regards,

    [email protected]

    Code:
      The calling code.
      
                Case  51
      
                TheProc& = PassStruct
      
                Rct.nLeft   = 1
                Rct.nTop    = 2
                Rct.nRight  = 3
                Rct.nBottom = 4
      
                tc& = getTickCount()
      
                cntr& = 100000000
      
                lbl1:
                  ! lea eax, Rct    ; load EAX with address of structure RECT
                  ! call TheProc&
                  ! dec cntr&
                  ! jnz lbl1
      
                tc1& = GetTickCount() - tc&
      
                MessageBox hWin,ByCopy str$(tc1&),"PassStruct", _
                           %MB_OK or %MB_ICONINFORMATION
      
      
                Case  52
      
                tc& = getTickCount()
      
                cntr& = 100000000
      
                lbl2:
                  NormalCall 1,2,3,4
                  ! dec cntr&
                  ! jnz lbl2
      
                tc1& = GetTickCount() - tc&
      
                MessageBox hWin,ByCopy str$(tc1&),"NormalCall", _
                           %MB_OK or %MB_ICONINFORMATION
      
      The two DLL functions for comparison.
      
      '##########################################################################
      
      FUNCTION PassStruct ALIAS "PassStruct" () EXPORT as LONG
      
          ! jmp over_it
        ' ----------------------------
        ' struct address in EAX
        ' ----------------------------
          LandHere:
      
          GLOBAL var1 as LONG
          GLOBAL var2 as LONG
          GLOBAL var3 as LONG
          GLOBAL var4 as LONG
          GLOBAL retv as LONG
      
          ! mov edx, [eax]
          ! mov var1, edx
      
          ! mov edx, [eax+4]
          ! mov var2, edx
      
          ! mov edx, [eax+8]
          ! mov var3, edx
      
          ! mov edx, [eax+12]
          ! mov var4, edx
      
        ' ------------------------------------
        ' commented out to test calling speed
        ' ------------------------------------
          ' retv = (var1 * var2) ^ (var3 + var4)
          ' ! mov eax, retv
      
          ! ret
        ' ----------------------------
          over_it:
      
          FUNCTION = CodePtr(LandHere)
      
      END FUNCTION
      
      ' #########################################################################
      
      FUNCTION NormalCall ALIAS "NormalCall" (ByVal a as LONG, _
                                              ByVal b as LONG, _
                                              ByVal c as LONG, _
                                              ByVal d as LONG) EXPORT as LONG
      
        ' -------------------------------------
        ' empty function to test calling speed
        ' -------------------------------------
      
          FUNCTION = 0
      
      END FUNCTION
      
      ' #########################################################################
    [This message has been edited by Steve Hutchesson (edited February 09, 2001).]
    hutch at movsd dot com
    The MASM Forum

    www.masm32.com

  • #2
    I tested the same approach in EXE.
    Results are nice, but how safety is it ?
    With Register None - probably, yes; without - very doubt.

    Code:
       #Compile Exe
       #Register None
       #Dim All
       #Include "Win32Api.Inc"
       
       %WithCode = %False
       %cntr = 10000000
    
       Global var1 As Long
       Global var2 As Long
       Global var3 As Long
       Global var4 As Long
       Global retv As Long
      
       Sub No1
       
    LoadHere:
    
    $If %WithCode
         ! mov edx, [eax]
         ! mov var1, edx
    
         ! mov edx, [eax+4]
         ! mov var2, edx
    
         ! mov edx, [eax+8]
         ! mov var3, edx
    
         ! mov edx, [eax+12]
         ! mov var4, edx
         
      
         retv = var1 + var2 + var3 + var4
    $EndIf
         ! ret
    ' ----------------------------
       End Sub
    
       Sub No2(rc As RECT)
          
    $If %WithCode
         var1 = rc.nLeft
         var2 = rc.nRight
         var3 = rc.nTop
         var4 = rc.nBottom
         retv = var1 + var2 + var3 + var4
    $EndIf
       End Sub
    
       Function PbMain
         Dim TheProc As Dword, rc As RECT, tc As Long, tc1 As Long, cntr As Long
    
         ! LEA EAX, LoadHere
         ! MOV TheProc, EAX
    
         Rc.nLeft = 1
         Rc.nTop = 2
         Rc.nRight = 3
         Rc.nBottom = 4
    
         tc = getTickCount()
    
         cntr = %cntr
    
    lbl1:
         ! lea eax, Rc ; load EAX with address of structure RECT
         ! call TheProc
         ! dec cntr
         ! jnz lbl1
    
         tc1 = GetTickCount()
    
         MessageBox 0, Format$(0.001 * (tc1 - tc), "#.##"), "PassStruct", %MB_OK Or %MB_ICONINFORMATION
    
    
         tc = getTickCount()
    
         cntr = %cntr
         Dim i As Long
         For i = 1 To cntr
         Call No2(rc)
         Next
       
         tc1 = GetTickCount()
    
        MessageBox 0, Format$(0.001 * (tc1 - tc), "#.##"), "PassStruct", %MB_OK Or %MB_ICONINFORMATION
    
    End Function



    ------------------
    E-MAIL: [email protected]

    Comment


    • #3
      The problem here is that PowerBASIC code requires the stack to be set up
      properly. It is likely to be safe to use DIRTYCALL if the called function
      is entirely in assembly language. If you use any BASIC code, you run the
      risk that some runtime routine will overwrite the stack at a location it
      expected to be able to use.

      ------------------
      Tom Hanlin
      PowerBASIC Staff

      Comment


      • #4
        I agree with both comments, the calling code must have #REGISTER NONE set
        otherwise there can be a register conflict with any register variables
        that are used by the compiler.

        Working outside a stack frame can be risky and in most instances, the normal
        sub/function method of calling remote code is the best way to do it. The
        purpose of bypassing the normal method was to avoid the stack overhead for
        highly recursive calls where the overhead slowed down the execution.

        The use of GLOBAL variables is safe as it does not use the stack and there
        should be no difference from making a CALL/RET within a normal function, the
        address of the label in the DLL function does not change so the call is just
        another in memory jump with the return value on the stack like normal.

        Still, its an interesting way of sneaking some extra performance with highly
        recursive calls in a language that has enough grunt to actually do it.

        Regards,

        [email protected]

        ------------------
        hutch at movsd dot com
        The MASM Forum

        www.masm32.com

        Comment


        • #5
          Yes, it should be mentioned that although you can declare LOCAL vars, you should never try to use them with this type of construct.

          It has been pointed out elsewhere by Bob Zale that Basic just works that way. It's better not to even try Hutch's trick if you're uncomfortable or unsure about this.

          I agree that it's probably only safe to use globals and inline assembler.


          Peter.


          ------------------
          [email protected]
          [email protected]

          Comment

          Working...
          X