Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[Funktionen] LoadLibrary, FreeLibrary + Schnellere DllCalls


  • Please log in to reply
13 replies to this topic
Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

Hallo,

 

Ist mal wieder soweit, dass ich ein Script "tunen" will.

Dazu hab ich die Funktion LoadLibrary geschrieben, die gleich die Adressen der kompletten Exporttabelle in einem Array zurückgiebt.

Damit Arbeiten die DllCalls etwa 1.4x schneller.

 

Allerdings sollte man folgende Information der Dokumentation im Hinterkopf behalten...

If DllCall's first parameter is a literal string such as "MulDiv"
and the DLL containing the function is ordinarily loaded before the script
starts, the string is automatically resolved to a function address. This
built-in optimization is more effective...

Dies gilt für Funktionen der Dlls "User32.dll", "Kernel32.dll", "ComCtl32.dll" und "Gdi32.dll"

 

Die Funktionen enthalten zudem einen Referenzcounter:

Ruft man zB. 2x LoadLibrary und danach 2x FreeLibrary auf wird die Library erst beim 2. Aufruf entladen.

Bzw. auch wenn das zurückgegebene Objekt überschrieben oder gelöscht wird.

LoadLibrary(filename)
{
  static ref := {}
  if (!(ptr := p := DllCall("LoadLibrary", "str", filename, "ptr")))
    return 0
  ref[ptr,"count"] := (ref[ptr]) ? ref[ptr,"count"]+1 : 1
  p += NumGet(p+0, 0x3c, "int")+24
  o := {_ptr:ptr, __delete:func("FreeLibrary"), _ref:ref[ptr]}
  if (NumGet(p+0, (A_PtrSize=4) ? 92 : 108, "uint")<1 || (ts := NumGet(p+0, (A_PtrSize=4) ? 96 : 112, "uint")+ptr)=ptr || (te := NumGet(p+0, (A_PtrSize=4) ? 100 : 116, "uint")+ts)=ts)
    return o
  n := ptr+NumGet(ts+0, 32, "uint")
  Loop, % NumGet(ts+0, 24, "uint")
  {
    if (p := NumGet(n+0, (A_Index-1)*4, "uint"))
    {
      o[f := StrGet(ptr+p, "cp0")] := DllCall("GetProcAddress", "ptr", ptr, "astr", f, "ptr")
      if (Substr(f, 0)==((A_IsUnicode) ? "W" : "A"))
        o[Substr(f, 1, -1)] := o[f]
    }
  }
  return o
}

FreeLibrary(lib)
{
  if (lib._ref.count>=1)
    lib._ref.count -= 1
  if (lib._ref.count<1)
    DllCall("FreeLibrary", "ptr", lib._ptr)
}

 

Für das Benchmark hab ich folgendes Script verwendet... (1 Mio. x auslesen, des 1. Pixels)

loops := 1000000
SetBatchLines, -1
global gdiplus := LoadLibrary("gdiplus")
VarSetCapacity(bin, 20, 0)
NumPut(1, bin, 0, "int")
DllCall(gdiplus.GdiplusStartup, "ptr*", token, "ptr", &bin, "ptr", 0)
DllCall(gdiplus.GdipCreateBitmapFromScan0, "int", 1, "int", 1, "int", 0, "int", 0x26200A, "ptr", 0, "ptr*", pBitmap)

start := A_TickCount
Loop, % loops
  DllCall("gdiplus\GdipBitmapGetPixel", "ptr", pBitmap, "int", 1, "int", 1, "uint*", col)
timeA := A_TickCount-start

start := A_TickCount
Loop, % loops
  DllCall(gdiplus.GdipBitmapGetPixel, "ptr", pBitmap, "int", 1, "int", 1, "uint*", col)
timeB := A_TickCount-start

DllCall(gdiplus.DisposeImage, "ptr", pBitmap)
DllCall(gdiplus.Cleanup, "ptr", token)
MsgBox, % "Normal:`n" timeA "`n`nMit LoadLibrary:`n" timeB "`n`n" timeA/timeB
 


Bei mir kommt dabei folgendes raus...

Normal:
2043

Vorher geladen:
1420

1.438732
... also etwa 1.4x schneller.
 
Auch die Exporttabelle kann man sich ansehen... (als kleines extra wink.png )
global gdiplus := LoadLibrary("gdiplus")
for name, proc in gdiplus
{
  if (name!="_ptr" && name!="_ref" && name!="__delete")
  MsgBox, % name
}

 



just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

Hallo Bentschi,

 

das ist wirklich gut. Wenn Du noch für just me und Andere, die die interne Struktur von DLLs nicht kennen, ein paar Kommentare/Anmerkungen liefern könntest, wäre es sogar noch besser! cool.png


Prefer ahkscript.org for the time being.


Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

Was genau ist unklar?

Im prinzip wird die DLL geladen, die Exporttabelle ausgelesen und per GetProcAddress die ptr für die Funktionen rausgefunden, die dann in einem Array gespeichert werden. wink.png



IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007

Es handelt sich einfach um einen Look-Up Cache von exportierten Methoden-Namen zu deren Pointer, richtig?

Wie genau erfährst du, wie viele Funktionen exportiert werden? (Evtl hat das auch just me gemeint?)

 

Man könnte diesen Look-Up Cache ja auch insofern erweitern, dass der Cache Lazy aufgebaut wird; d.h. bei der ersten Anforderung einer Funktion wird diese mit GetProcAddress geholt, dann aber im Cache zwischengespeichert. Hätte den Vorteil, dass die initiale Startupzeit sowie das Memory etwas geschont werden, wenn nur wenige der exportierten Funktionen verwendet werden.



Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

Die Anzahl der Exports werden hier ermittelt...

Loop, % NumGet(ts+0, 24, "uint")

Die Dokumentation besagt... (pecoff_v83.docx)

Number of Name Pointers (The number of entries in the name pointer table. This is also the number of entries in the ordinal table.)

ts ist das Offset zur Exporttable.

 

Natürlich dachte ich daran, dass ich auch nur die wirklich verwendeten Funktionen auslesen könnte.

Allerdings wäre dann wieder eine Abfrage nötig, ob die Funktion bereits geladen wurde. wink.png

Ich dachte ich schei*** auf den Arbeitsspeicher und optimiere was auch immer von der Geschwindikeit her möglich ist.

Und selbst eine DLL mit 5000 funktionen fällt kaum auf (weder Geschwindigkeit, noch so übermäßiger Speicherverbrauch), wenn diese von der Funktion geladen wird.

Da benötigt die DLL selbst schon viel mehr speicherplatz als den Index, den ich anlege.



just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

Interessant: pecoff_v83.docx !
Wenn ich das richtig lese, wird beim Laden auch eine Adresstabelle angelegt. Die Reihenfolge entspricht der Reihenfolge in der "name pointer" Tabelle. Kann man die nicht anstelle von "GetProcAddress"  nutzen?
if (Substr(f, 0)==(A_IsUnicode ? "W" : "A"))


Prefer ahkscript.org for the time being.


Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

GetProcAddress arbeitet genau so mit dieser Tabelle.

Ich lade allerdings nur die Namen, und den ptr mit GetProcAddress.

Zuvor hatte ich es anders, was auch großteils die ptr korrekt geladen hat, aber es gibt in den Dlls auch Funktionen, die auf andere Dlls verweisen können, und daher hab ich mich für GetProcAddress entschieden, was letztendlich auch eigentlich so schnell sein sollte wie die NumGets.

 

if (Substr(f, 0)==(A_IsUnicode) ? "W" : "A")

ist auch so korrekt.

Ich mach mir bei einem Ternary einfach nur die if-Anweisung immer in Klammer, was hier vllt. auch ein klein wenig doof aussieht.

Die Vergleichsoperatoren werden sowieso als leztes abgearbeitet, also kannst du zB die Roten Klammern auch ganz entfernen.



just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

if (Substr(f, 0)==(A_IsUnicode) ? "W" : "A")

 

funktioniert bei mir nicht (U32):

 

#NoEnv
DLL := "Gdiplus.dll"
Gui, Margin, 20, 20
Gui, Add, ListView, w610 r20 Grid, #|Name|Address
LV_ModifyCol(1, "Integer 50")
LV_ModifyCol(2, "350")
LV_ModifyCol(3, "200")
global gdiplus := LoadLibrary(DLL)
I := 0
for name, proc in gdiplus
{
  if (name!="_ptr" && name!="_ref" && name!="__delete")
      LV_Add("", ++I, name, proc)
}
Gui, Show, , %DLL%
Return
GuiCLose:
ExitApp

Prefer ahkscript.org for the time being.


Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

Sorry, war ein denkfehler



nnnik
  • Members
  • 1625 posts
  • Last active: Jan 24 2019 02:19 PM
  • Joined: 28 Jul 2012
1
Ich würde gerne etwas schreiben doch AHK hat grade meinen gefühlt 1000 (300) Zeichen Post gefressen.

Visit the new forum ahkscript.org.

http://ahkscript.org


just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

Sorry, wenn es dann ein denkfehler war, würdest Du dann bitte Dein Skript im OP korrigieren?


Prefer ahkscript.org for the time being.


Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

Das hab ich doch schon vor ewigkeiten erledigt
wink.png



just me
  • Members
  • 1496 posts
  • Last active: Nov 03 2015 04:32 PM
  • Joined: 28 May 2011

Na ja, dann haben wir eben unterschiedliche Auffassungen von "vor ewigkeiten".


Prefer ahkscript.org for the time being.


Bentschi
  • Moderators
  • 120 posts
  • Last active: Sep 05 2014 02:12 AM
  • Joined: 26 Nov 2008

ewigkeiten = 2 Tage