 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Sat May 12, 2007 1:14 pm Post subject: |
|
|
Another thing that is noteworthy is that the keys in the dictionary are case sensitive. Laszlo showed it with his test examples, but I just realized it now, since it wasn't empasized. _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4510 Location: Boulder, CO
|
Posted: Sat May 12, 2007 4:49 pm Post subject: |
|
|
SetCompareMode() allows to set case sensitivity. Its use is also demonstrated in the example code.
At the first call NextKey creates a list, returned in the ByRef parameter "penum". In subsequent calls the handle "pdic" to the directory is not needed, so you can omit it if you want. In the example NextKey appears in a loop, so extra instructions were necessary to handle the first call differently. For simplicity, the "pdic" parameter is present, even if it is superfluous at later calls.
I understand that you can simulate modules with keys, like "Global.x", which might be sufficient for your particular application, but in general it is not a good idea. You can delete or clear whole dictionaries, count or list their entries separately; set different compare mode for each; pass them as an argument or make them local or static to functions, etc. You loose all of these with a global or hidden static dictionary, which is not even simpler to use: Add("Global.x",1) is the same long as Add(Global,"x",1). |
|
| Back to top |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Sat May 12, 2007 6:31 pm Post subject: |
|
|
| Laszlo wrote: | | SetCompareMode() allows to set case sensitivity. Its use is also demonstrated in the example code. | Sorry, my mistake, you are right, but that mode has to be set right after creation, otherwise it has no effect. Then it could be put into the creation function.
| Laszlo wrote: | | At the first call NextKey creates a list, returned in the ByRef parameter "penum". | Ok, I think I undstand it so far. But what happens, if the list is never searched through completly. To me it seems that the NextKey function needs to go through the complete list to free the memory. Am I right? | Code: | DllCall(Dic_VTable(penum,2), UInt,penum) ; END: destroy key-list
pnum = | Will an earlier exit be a memory leak?
| Laszlo wrote: | | You can delete or clear whole dictionaries, count or list their entries separately; set different compare mode for each; pass them as an argument or make them local or static to functions, etc. | Yes, these are all features that it offers, but at the cost of having to pass the pdic to every function or set it global in every function. And it would be mandatory to define for each dictionary call.
| Laszlo wrote: | | Add("Global.x",1) is the same long as Add(Global,"x",1). | True, but then it is optional not mandatory. And I would need to add an additional parameter to each function or add a "global pdic" to each function.
I'm not against the very flexible way your code provides. I just wanted to show a different way that is simpler to use but with the sacrifice of some features.
In the script I'm currently working on I only need one global storage, that holds all the data. Individual dictionaries will complicate the whole code without giving much benefit (no need to count,remove whole individual dic).
The only thing I have to think about is how to remove some branches inside a tree. E.g. I have a list of items. Each item has several data fields. Now I want to remove a special item from the dictionary with all its fields. Since a dictionary is not an array, I guess I have to loop through the keylist, compare the keyname with the item identifier and remove all that match. That method would be independant of the number of data fields. Or I need to know the names of all data fields, then I can loop over them and remove the data field for that specific item. This method would be faster but needs more bookkeeping. _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4510 Location: Boulder, CO
|
Posted: Sat May 12, 2007 8:36 pm Post subject: |
|
|
Toralf: Good points. (You seem to be the only one looking into the code. There might be other problems waiting to be discovered.)
I did not know that you cannot change compare mode after you touched the dictionary (one can verify it by calling GetCompareMode). As you suggested, I moved SetCompareMode into CreateDictionary: one less function to call at initialization.
It looks like there is no memory leak, if the key list is not destroyed. It exists in the COM memory space, which seems to be destroyed at exit. It is similar with undestroyed dictionaries, too. I tried it with | Code: | Loop 100000
pdic%A_Index% := CreateDictionary() | The free system memory became 0, but after the script exits, it gets back to the normal 60%. (XP SP2)
For the sake of clean coding, I added an explicit option to the NextKey function. If its 2nd parameter is 0 or a substring of "Clear", the penum list will be destroyed. It could be done before exit.
I don't know enough about your tree data structure, but have you thought about using sub-dictionaries as values? You can prune branches by deleting a single key, but in large applications you have to provide some garbage collection, otherwise you run out of memory, soon. |
|
| Back to top |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Sat May 12, 2007 9:17 pm Post subject: |
|
|
Thanks for the clarification and the update. _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 2192
|
Posted: Sat May 12, 2007 11:54 pm Post subject: |
|
|
| toralf wrote: | | Will the dictonary destroy itself when the script exists, or is it mandatory to call the two commands? |
As the COM server in this case lives in the process space of AHK, at least its resources associated with AHK will be cleared up at the exit of AHK.
But, I recommend building a habit to always Release and CoUninitialize explicitly for a graceful exit.
It's not terribly well documented what CoInitialize is really carrying out at the initialization, and there could be problems with the COM server residing in the remote process if omitting the two commands at the exit of the script.
BTW, although I've never faced it with AHK, I had experiences with other app stucking in the middle of the exit of the app after using COM. |
|
| Back to top |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Tue May 15, 2007 12:45 pm Post subject: |
|
|
Is there a way, to determine if a dictionary exists?
Lets say I have a var "pdic" and want to find out if it already points to a dictionary. Is this possible? I do not know the keys in the dictionary, thus I will not be able to check for them. _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 2192
|
Posted: Tue May 15, 2007 2:13 pm Post subject: |
|
|
| toralf wrote: | Is there a way, to determine if a dictionary exists?
Lets say I have a var "pdic" and want to find out if it already points to a dictionary. Is this possible? I do not know the keys in the dictionary, thus I will not be able to check for them. |
The simlest method could be using AddRef/Release: checking whether the returned value(s) > 0, and ErrorLevel and A_LastError.
However, this method is not safe if pdic was already released etc.
BTW, VB has a function IsObject() which checks whether a variable is valid object or not.
I don't really know how it does the job, but I'm suspecting it just keeps the track of the object privately.
So, my conclusion is: just carefully keeping track of the variable of the object like pdic as to not become invalid one.
If you release it in the middle of the script, just set it to 0 to indicate that it doesn't point to a vaild object. |
|
| Back to top |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Wed May 23, 2007 2:41 pm Post subject: |
|
|
I'm using dictionaries now very extensively and came to find that they are relatively slow when used often in a loop. To test this i created a small test script: | Code: | #NoEnv
#SingleInstance ignore
OnExit, OnExit
SetBatchLines, -1
;initialize dictionary functionality
Dic("initialize")
;create global dictionary
myDic := Dic("insensitive")
;set value
Dic_Set(MyDic, "Value", False)
Start := A_TickCount
Loop, 10000 { ; needs 2375 ms
If Dic_Get(MyDic, "Value")
Continue
ID++
}
Time1 := A_TickCount
Loop, 10000 { ; needs 280 ms
If (Mod(A_Index, 10) = 0 AND Dic_Get(MyDic, "Value"))
Continue
ID++
}
Time2 := A_TickCount
Loop, 10000 { ; needs 65 ms
If (Mod(A_Index, 200) = 0 AND Dic_Get(MyDic, "Value"))
Continue
ID++
}
Time3 := A_TickCount
Value := Dic_Get(MyDic, "Value")
Loop, 10000 { ; needs 25 ms
If Value
Continue
ID++
}
Time4 := A_TickCount
Time4 := Time4 - Time3
Time3 := Time3 - Time2
Time2 := Time2 - Time1
Time1 := Time1 - Start
MsgBox,
(Ltrim
%Time1% ms with Dic_Get() on every loop
%Time2% ms with Dic_Get() on every 10th loop
%Time3% ms with Dic_Get() on every 200th loop
%Time4% ms without Dic_Get() in loop
)
Return
;################################ End of Auto-Exec Section
#Include ..\Dic\Dic.ahk
OnExit: ; Clean up on Exit
;remove dictionary
Dic("delete", myDic)
;uninitialize dictionary functionality
Dic("uninitialize")
ExitApp ;real exit
Return
| The result | Quote: | 2494 ms with Dic_Get() on every loop
300 ms with Dic_Get() on every 10th loop
60 ms with Dic_Get() on every 200th loop
20 ms without Dic_Get() in loop
|
Now that I look at the dictionary code it is clear that it takes longer then pure variable handling. In my real code where I had it using global arrays and a single dictionary, the dictionary was about 4 to 5 times slower. With reducing the check to every 200th loop i could reduce the overhead to 10%.
I also tested loadlibrary and freelibrary, but that did't improved speed. Does anyone have an idea how the dictionary could be speed up, other then using it less frequently? _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4510 Location: Boulder, CO
|
Posted: Wed May 23, 2007 3:35 pm Post subject: |
|
|
I have the following timing in my version of Sean's script (10,000 iterations):
Add(pdic, A_Index, ID) → 844 ms
Get(pdic, "NOT_IN") → 500 ms
Get(pdic, "Test1") → 875 ms (Test1 is a key)
There is always some way to speed the code up. E.g., if you search repeatedly for the same key, you can keep the old BString. What is the application, where getting the value of a key in 0.05…0.3 ms is too long? |
|
| Back to top |
|
 |
toralf
Joined: 31 Jan 2005 Posts: 3906 Location: Bremen, Germany
|
Posted: Wed May 23, 2007 3:46 pm Post subject: |
|
|
It will be an issue when you need to handle lots of data, which is stored in a dictionary.
In my case I created a special version of ActiveGoTo for SciTE. Thus I had to scan a AHK script. The longest script I have is about 5000 lines. Thus it is a loop of 5000 iterations. This takes about 0.8 seconds to finish. With an global array it only takes 0.7 seconds. That is not much of a difference anymore. Since I need the ability to stop the loop in its process, I have to check a certain status variable. For the global array I checked it every iteration, for the dictionary I now check it every 200th iterations. Checking it at every iteration took the script 3.0 seconds to finish.
Since the amout of data I extract from the file is low the speed to handle the rest of the data doesn't have a big influence. But imagine you do some funny mathematical matrice calculations. Then getting and setting values becomes an issue. _________________ Ciao
toralf  |
|
| Back to top |
|
 |
Sean
Joined: 12 Feb 2007 Posts: 2192
|
Posted: Wed May 23, 2007 5:12 pm Post subject: |
|
|
| toralf wrote: | | It will be an issue when you need to handle lots of data, which is stored in a dictionary. |
I think most of times are spent to prepare the data type of COM, variant in this case, which I believe the weakest point of COM, especially IDispatch types.
Some programmers argue that they incurred this to let VB programmers develop it.
Anyway, you may separate the processes to speed up in this case: Create Variant and Query the item.
| Code: | SysAllocString(sValue)
VariantInitiate(ByRef var, pValue)
GetItem(pdic, ByRef var)
SysFreeString(pValue)
|
But this comes with cost, of course. Writing codes would become tedious. |
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 4114 Location: Belgrade
|
Posted: Mon Jun 04, 2007 5:39 pm Post subject: |
|
|
I want to know how fast dictionary is. Have anybody measured ?
MY problem is this.
When encapsulating 3th party controls sometimes I have set of functions dealing with lot of variables. Lets say there are 10 functions and each need to have access to some large number of variables
Check this out for one func
| Code: | SS_SetCell(hCtrl, col, row, o1="", o2="", o3="", o4="", o5="", o6="", o7="", o8="", o9="", o10=""){
static SPRM_SETCELLDATA=0x483
static EMPTY=0x000, COLHDR=0x001, ROWHDR=0x002, WINHDR=0x003, TEXT=0x004, TEXTMULTILINE=0x005, INTEGER=0x006, FLOAT=0x007, FORMULA=0x008, GRAPH=0x009, HYPERLINK=0x00A, CHECKBOX=0x00B, COMBOBOX=0x00C, EXPANDED=0x00F, BUTTON=0x010, WIDEBUTTON=0x020, FORCETEXT=0x044 ;types
static SPRIF_TYPE=0x40,SPRIF_DATA=0x200,SPRIF_WIDTH=0x80,SPRIF_BACKCOLOR=1,SPRIF_TEXTCOLOR=2,SPRIF_TEXTALIGN=4,SPRIF_HEIGHT=0x100,SPRIF_STATE=0x20,SPRIF_FONT=0x10,SPRIF_IMAGEALIGN=8,SPRIF_COMPILE=0x80000000
static LEFT=0x10, CENTER=0x20, RIGHT=0x30, MIDDLE=0x40, BOTTOM=0x80, GLOBAL=0xF0, MASK=0xF0, XMASK=0x30, YMASK=0xC0 ;formats
static LOCKED=0x01, HIDDEN=0x02, REDRAW=0x08, ERROR=0x10, DIV0=0x20, UNDERFLOW=0x30, OVERFLOW=0x40, RECALC=0x80, ERRMASK=0xF0 ;states
|
Now, I need to repeat this code in 5 other functions and I don't like that. Another thing I don't like is to use globals. So I thought that I can use Dicts if they are fast enough. You know that this has to be as fast as possible (AFAP) as you usualy fill such controls in loop.
Not so complex controls don't have such problems as any of the functions deals with just few variables which can be then declared as static. But repeating the statics isn't nice... _________________
 |
|
| Back to top |
|
 |
Laszlo
Joined: 14 Feb 2005 Posts: 4510 Location: Boulder, CO
|
Posted: Mon Jun 04, 2007 8:37 pm Post subject: |
|
|
I cannot imagine com dictionaries are very fast: they convert strings, hash keys, etc., but they offer much more than this application needs. Instead of passing the dictionary handle, you can pass the variable (ByRef) containing your terms (or have this one variable global), and use simple string manipulations to get the values. (Even RegEx match seems to be slower.) Something like this: | Code: | Const=,SPRM_SETCELLDATA=0x483,EMPTY=0x000,COLHDR=0x001,ROWHDR=0x002,WINHDR=0x003,TEXT=0x004,TEXTMULTILINE=0x005,
name = Empty
L := StrLen(name)+2
p := InStr(Const,"," name "=")
v := SubStr(Const,p+L,InStr(Const,",",0,p+1)-p-L)
MsgBox %v% |
|
|
| Back to top |
|
 |
majkinetor
Joined: 24 May 2006 Posts: 4114 Location: Belgrade
|
Posted: Mon Jun 04, 2007 9:41 pm Post subject: |
|
|
The usage is not the problem. The problem is sharing. I already do something similar: _________________
 |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|