Test piece using the SafeArray system for a dynamic array.

Started by hutch--, May 03, 2008, 02:31:07 AM

Previous topic - Next topic

hutch--

I was reasonably happy with the dynamic array code but in comparison to other systems that use similar methods the memory usage was too high and the original array allocation was simply too slow. I have been playing with the SafeArray system for OLE and it appears to have a much more efficient set of methods for allocating, reallocating and destroying a dynamic that the published interfaces for other methods have which makes the array creation time much faster while using a lot less memory.

This is the bare proof of concept test piece, I have yet to work out if the array lock method is required with the member allocation method, in the examples I have seen it is not used but whether it needs to be used is something I have not worked out yet.


; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    include \masm32\include\masm32rt.inc
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

comment * -----------------------------------------------------
                        Build this  template with
                       "CONSOLE ASSEMBLE AND LINK"
        ----------------------------------------------------- *

    SAFEARRAYBOUND STRUCT
      cElements dd ?
      lLbound   dd ?
    SAFEARRAYBOUND ENDS

  ; -----------------------------------------------------------------
  ; this is not a system defined structure, the trailing "1" is to
  ; designate that it is a one dimension array, the system allows
  ; multidimensional arrays which requre an additional SAFEARRAYBOUND
  ; structure for each dimension to store the bounds information in.
  ; -----------------------------------------------------------------
    SAFEARRAY1 STRUCT
      cDims         dw ?                ; Count of dimensions in this array.
      fFeatures     dw ?                ; Flags used by the SafeArray
      cbElements    dd ?                ; Size of an element of the array
      cLocks        dd ?                ; Number of times the array has been locked
      pvData        dd ?                ; Pointer to the data
      rgsabound     SAFEARRAYBOUND <>   ; bounds structure
    SAFEARRAY1 ENDS

    .code

start:
   
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    call main
    inkey
    exit

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

main proc

    LOCAL parr  :DWORD
    LOCAL pdat  :DWORD
    LOCAL lcnt  :DWORD
    LOCAL sarb  :SAFEARRAYBOUND
    LOCAL sarr  :SAFEARRAY1

    push ebx
    push esi
    push edi

    mov sarb.cElements, 10000000                            ; set the initial array count
    mov sarb.lLbound, 0

    invoke SafeArrayCreate,VT_BSTR,1,ADDR sarb              ; create the empty array
    mov parr, eax

    mov eax, parr
    mov ecx, (SAFEARRAY1 PTR [eax]).rgsabound.cElements     ; display the element count from the descriptor struct
    print str$(ecx)," element count",13,10

    mov eax, parr
    mov ecx, (SAFEARRAY1 PTR [eax]).pvData                  ; get the pointer to the array data
    mov pdat, ecx

    print str$(pdat)," pointer to data",13,10

    mov esi, pdat                                           ; place the array pointer in ESI
    mov ebx, 1024                                           ; place the array index in EBX

  ; --------------------------------------------------
  ; make a test string allocation at array member 1024
  ; --------------------------------------------------
    .data
      arr1024 db "This is array member 1024",0
    .code

    fn SysAllocStringByteLen,[esi+ebx*4],LENGTHOF arr1024   ; allocate a string of the required length
    mov [esi+ebx*4], eax                                    ; write its handle to the array
    fn szCopy,OFFSET arr1024,[esi+ebx*4]                    ; write the data to the array member
  ; --------------------------------------------------

    mov sarb.cElements, 20000000                            ; reset the array count
    invoke SafeArrayRedim,parr,ADDR sarb                    ; resize the array

    mov eax, parr
    mov ecx, (SAFEARRAY1 PTR [eax]).rgsabound.cElements     ; display the new element count from the descriptor struct
    print str$(ecx)," element count",13,10

    mov sarb.cElements, 10000000                            ; reset the array count
    invoke SafeArrayRedim,parr,ADDR sarb                    ; resize the array

    mov eax, parr
    mov ecx, (SAFEARRAY1 PTR [eax]).rgsabound.cElements     ; display the new element count from the descriptor struct
    print str$(ecx)," element count",13,10

  ; ---------------------------
  ; get the array address again
  ; ---------------------------
    mov eax, parr
    mov ecx, (SAFEARRAY1 PTR [eax]).pvData                  ; get the pointer to the array data
    mov pdat, ecx

  ; -----------------------------------------------
  ; get the member address and display its contents
  ; -----------------------------------------------
    mov esi, pdat                                           ; place the array pointer in ESI
    mov ebx, 1024                                           ; place the array index in EBX
    print [esi+ebx*4],13,10                                 ; display the content of the array member

    invoke SafeArrayDestroy,parr                            ; delete the array

    pop edi
    pop esi
    pop ebx

    ret

main endp

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

end start
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

Can you explain this part?

    fn SysAllocStringByteLen,[esi+ebx*4],LENGTHOF arr1024   ; allocate a string of the required length
    mov [esi+ebx*4], eax                                    ; write its handle to the array
    fn szCopy,OFFSET arr1024,[esi+ebx*4]                    ; write the data to the array member


In my understanding, you can pass either 0 as first param to allocate a non-initialised string of len 1024, or directly pass the source:
    fn SysAllocStringByteLen,OFFSET arr1024, LENGTHOF arr1024   ; allocate a string of the required length
    mov [esi+ebx*4], eax                                    ; write its handle to the array

I am sure you have a good reason to do so, but I simply don't understand it - excuse my ignorance  :red

hutch--

jj,

What I have to test is if the array as allocated BEFORE any string data is allocated to it with SysAllocStringByteLen() is whether it returns a null string by using the address of an empty member. If so it means the empty strings don't have to be allocated with SysAllocStringByteLen() when the array is created which makes the original array creation much faster. My impression is so far that the safe array creation methods interface a lot lower down in the memory alocation scheme and both create and destroy the array much faster than looping through SysAllocStringByteLen() and SysFreeString().

RE your question, the idea is so it reallocates the string freeing the old string rather than just creating a new string.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

hutch--

Sad to say this approach is more problematic than its worth. I have a new test piece under development allocated with GlobalAlloc() that has a pointer to a null string written at each member so it is compatible with the behaviour of an OLE string. This method is much faster than allocating an empty OLE string and uses a lot less memory.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

Hutch,
Among many other roles, you are the SysAlloc guru over here:

invoke SysStringByteLen, esi
.if eax==0 || eax>=80000000h || eax>32768
mov eax, len(esi)
.endif

This is my clumsy workaround for a macro that may receive as args a BSTR or an ordinary .data item. MSDN does not foresee a test if esi is a valid BSTR, so I try to check for implausible string lens... it works but I don't trust it. Any ideas?

hutch--

jj,

I have the next version on the way, it has solved the problem of slow array creation and seems to better manage memory as well. I have the basic alloc / dealloc stuff done and some of the existing procedures work with it but I have to rewrite the rest. Its testing so far a lot faster and does not appear to have any other associated problems so its going OK at the moment.

The main change is not allocating empty OLE strings as this wasted a lot of memory and slowed things down. It uses a null string in the .data section with a zero 4 bytes below it for the length which is much faster than allocating an empty string just so the spec of returning a null string was covered. I changed the SysAllocStringByteLen() usage as you had suggested to load the string as the first arg. It internally uses movsd/movsb so its probably fast enough.

When I get enough of it done I will post it so you can have a good look at it. Perhaps you will be able to see ways of making it faster again.

An unrelated question, I have seen in your code examples the use of a leading "gfa" with some function names. A long time ago I used a dialect of basic from GFA Systemtecnik that was an excellent 16 bit version of basic. Did you work for them back that long ago ?
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

Quote from: hutch-- on May 05, 2008, 11:24:10 PM
An unrelated question, I have seen in your code examples the use of a leading "gfa" with some function names. A long time ago I used a dialect of basic from GFA Systemtecnik that was an excellent 16 bit version of basic. Did you work for them back that long ago ?
No, I am neither Sjouke H. nor Frank O.  :8)
But as a matter of fact, I seem to be the last fossil using that dialect for a serious project; and given that the days of 16-bit Windows are counted, I have started working on a cross-compiler. That's why you see those strange prefixes  :green