DLLStructGetSize Problem

  • Hallo ich hab mal wieder ein Problem mit den DLLStructs ;)

    Sagen wir wir haben folgenden Quellcode:

    [autoit]


    ;uint = 4 Byte
    ;char = 1Byte
    $struct1 = DLLStructCreate("uint length;char content[7];char terminator")
    MsgBox(0,"Größe struct1","Erwartet: 4+7+1= "&4+7+1&"Byte"& @CRLF &"Tatsächlich: "&DllStructGetSize($struct1)&"Byte");richtig

    [/autoit][autoit][/autoit][autoit]

    $struct2 = DLLStructCreate("uint length;char content[12];char terminator")
    MsgBox(0,"Größe struct2","Erwartet: 4+12+1= "&4+12+1&"Byte"& @CRLF &"Tatsächlich: "&DllStructGetSize($struct2)&"Byte") ;unerwartet..

    [/autoit]

    Nun die Frage an was liegt das?

    Mfg

    €dit:
    Nach dem Tipp von bastel123 sieht das ganze nun so aus und funktioniert thx :)
    DLLStructCreate("align 1;uint length;char content[12];char terminator")

    Hatte dort auchschon geschaut allerdings nicht ganz durchgeblickt was dies tut, aber funktioniert :)

    Einmal editiert, zuletzt von Death (27. Dezember 2011 um 13:17)

  • Strukturen werden meist so angeordnet das der Zugriff auf die Einzelelemente so flott wie möglich geht.
    In der Regel werden die Werte in 32Bit-Blöcken aus dem Speicher geladen.
    Ein Direktzugriff ist also nur aller 4 Byte möglich.
    Nehmen wir mal als Beispiel "uint length;char content[2];char terminator"
    length steht dann in den ersten 4 Bytes der Struktur, content in den ersten 2 Bytes des nächsten 32Bit-Blockes und terminator dann im 3.
    Um an den Wert zu kommen muss der Prozessor den Block komplett nehmen ein Bitand auf das 3. Byte des Blocks durchführen und dann diesen Wert um 2 Bytes verschieben.
    Würde terminator am Anfang des nächsten Bits stehen entfiele die Verschiebeoperation.

    Ich hoffe es ist damit erstmal bisschen nachvollziehbar warum die Struktur so im Speicher angeordnet wird.
    Nun ist es allerdings so das das gar nicht stimmen muss was ich gerade erzählt habe ;)
    Ich weiß nur das die Strukturen aus Performance-Gründen so angeordnet werden - ob die beschriebenen Operationen wirklich so ablaufen weiß ich nicht - hab nur geraten ;)
    Hier gibt es aber einige die mich schnell berichtigen werden wenn ich Mist erzählt haben sollte. :)

    Einmal editiert, zuletzt von AspirinJunkie (27. Dezember 2011 um 14:00)

  • Zitat

    Ein Direktzugriff ist also nur aller 4 Byte möglich.


    (Achtung: Unverstandenes Wikipedia-Wissen) Ich habe das jetzt so verstanden, dass der Datenbus 32-Bit groß ist und deshalb können nur 4 Bytes geladen werden. Aber was ist bei 64-Bit PC's? Die müssten dann ja eigentlich nur alle 8 Bytes schnell zugreifen können. (?)
    Ausserdem: Warum ist der Standardwert zur Speicherausrichtung in Autoit 8 und nicht 4?
    Ich versteh das noch nicht wirklich...
    Und weshalb ist diese Struktur 16 Bytes groß?

    [autoit]

    $struct = DllStructCreate("double;short")

    [/autoit]


    Schliesslich verbraucht double die ersten 8 Bytes und short kann dann (auf einer Adresse, welche durch 4 und 8 teilbar ist) die letzten beiden Bytes einnehmen.
    Hat das manuelle Ausrichten mit align nicht auch den Nachteil, dass langsamer auf die Adressen zugegriffen wird?
    mfg @night@

  • Ich habe das jetzt so verstanden, dass der Datenbus 32-Bit groß ist und deshalb können nur 4 Bytes geladen werden.


    Nun es können und werden immer 4 Byte (bei x86, bei x64=8 Byte) zusammen geladen - egal ob alle Bytes benötigt werden oder nicht.
    Wenn nun z.B. ein char-Wert gleich am Anfang des Byte-Blocks liegt kann direkt mit dem Wert weitergearbeitet werden. Liegt er irgendwo mitten in diesem Block muss er erst verschoben werden bevor er benutzt werden kann.

    Ausserdem: Warum ist der Standardwert zur Speicherausrichtung in Autoit 8 und nicht 4?

    Ich vermute(!) da in der Regel x64-Prozessoren verwendet werden und diese mit 8Byte-Blöcken arbeiten wurde der Standardwert an diese angepasst.

    Und weshalb ist diese Struktur 16 Bytes groß?

    Da "align" standardmäßig 8 ist wird der short-Wert in einen kompletten Block geschrieben (wodurch dann in diesem 6 Byte frei bleiben)

    Hat das manuelle Ausrichten mit align nicht auch den Nachteil, dass langsamer auf die Adressen zugegriffen wird?


    Testen wir es doch einmal:

    Spoiler anzeigen
    [autoit]

    Global $align[5] = [1, 2, 4, 8, 16]
    Global $N = 1000000

    [/autoit] [autoit][/autoit] [autoit]

    For $a In $align
    $s = DllStructCreate("align " & $a & ";double;char[11];short[7]")

    [/autoit] [autoit][/autoit] [autoit]

    $iT = TimerInit()
    For $i = 1 To $N
    $t = DllStructGetData($s, 3, 4)
    DllStructSetData($s, 3, $t, 4)
    Next
    $iT = TimerDiff($iT) / $N
    ConsoleWrite("Zeitverbrauch mit align=" & $a & ": " & Round($iT, 6) & " ms" & @CRLF)
    Next

    [/autoit]
  • Hi,
    Der Grundgedanke ist bei euch allen richtig, jedoch nicht konsequent umgesetzt.
    Je nach größe des Datentyps muss die Ausrichtung im Speicher anders sein. Das liegt aber nicht (direkt) an der Breite des Datenbusses, sondern am Layout des (Arbeits)speichers. Die einzelnen Bits eines RAM-Bausteins sind in Matrizen angeordnet. Diese sind bei DDR wie der Datenbus 64 bit breit.
    Eine Zeile der Matrix kann nun problemlos auf einmal gelesen werden. Auch ein einzelnes Byte oder eine halbe Zeile (= 4 byte) oder 2 byte innerhalb der Zeile sind problemlos lesbar.
    Wenn deine Speicherstelle nun nicht an diesen Zeilen ausgerichtet ist, wird für jede belegte Zeile ein Speicherzugriff benötigt (jeweils vom Prozessor angestoßen), was langsam und unnötig ist.
    Also werden char und byte an 1byte-Grenzen ausgerichtet; short and 2 byte; int, dword usw. and 4 byte und int64 und größer an 8 byte.
    Arrays von Datentypen werden unabhängig von der Arraygröße auch an den Grenzen des Datentyps angelegt.

    Nun noch zum Beispiel aus dem ersten Post: Die DLLStruct besitzt die Typen uint (Align 4 byte) und char (Align 1 byte). Die gesamte Struktur wird also an 4 byte ausgerichtet. Der Grund dafür ist einfach: Nehmen wir an, wir erstellen ein Array aus der Struktur, dann muss jedes Element an einer 4byte-Grenze beginnen. Der Einfachheit halber wird die Struktur daher schon mit Füllbytes auf die passende Länge gebracht. Daher ist $struct2 4+12+1 +3= 13 +3 = 16 bytes groß. Bei $struct1 passt es zufälligerweise.

  • Hi,
    bitte beachten, daß "align 16" KEINE korrekten Adressen zurückliefert!
    Bei Verwendung von Assembler- und C++-Programmen (sowohl "inline" als auch als DLL) in Kombination mit AutoIt hatte ich dieses Problem schon öfter!
    Beim Laden eines SSE-Registers (z.B. MOVDQA ) wird zwingend eine durch 16 teilbare Speicheradresse vorrausgesetzt. Ist das nicht so, führt das zu einem Absturz bzw Exception.

    /EDIT/Der Grund ist, wie von prog@andy vorzüglich erklärt :thumbup: , das Layout des Arbeitsspeichers.

    Testscript:

    [autoit]

    Dim $a[1100]

    [/autoit][autoit][/autoit][autoit]

    For $i = 1 To 1000
    $a[$i] = DllStructCreate("align 16;int64["&$i&"]");verschieden große structs erzeugen
    $ptr = Number(DllStructGetPtr($a[$i]))
    If Mod($ptr, 16)<>0 Then;adresse aligned?
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ptr <> align16 ' & $ptr & " " & $i & " " & Mod($ptr, 16) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    EndIf
    Next

    [/autoit]


    Btw ist es egal, welcher Datentyp beim Erstellen der struct verwendet wird!

    Demnach ist der folgende Satz aus der Hilfe zu dllstructcreate() definitiv nicht zutreffend:
    Erlaubte Werte sind 1, 2, 4, 8 und 16. Die Ausrichtung eines Elements wird entweder an der Grenze eines Vielfachen von n oder an der eines Vielfachen der Größe eines Elements sein, je nach dem welches kleiner ist. Dies ist wie bei der "#pragma pack option" mit dem Microsoft Visual C++ Compiler.

    Die #pragma pack option aller (von mir ausprobierten ) C-Compiler funktioniert selbstverständlich einwandfrei!

    Abhilfe für 16-Byte alignment:

    Spoiler anzeigen
    [autoit]

    #include <Memory.au3>
    #include <MemoryConstants.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Dim $a[1100]

    [/autoit] [autoit][/autoit] [autoit]

    ;~ For $i = 1 To 1000
    ;~ $a[$i] = DllStructCreate("align 16;int64["&$i&"]");verschieden große structs erzeugen
    ;~ $ptr = Number(DllStructGetPtr($a[$i]))
    ;~ If Mod($ptr, 16)<>0 Then;adresse aligned?
    ;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ptr <> align16 ' & $ptr & " " & $i & " " & Mod($ptr, 16) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    ;~ EndIf
    ;~ Next

    [/autoit] [autoit][/autoit] [autoit]

    ;~ msgbox(0,0,"...jetzt mit 16Byte-alignment")

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To 1000
    $a[$i] = _DllStructCreate16("int64["&$i&"]");verschieden große structs erzeugen
    $ptr = number(DllStructGetPtr($a[$i]))
    If Mod($ptr, 16)=0 Then;adresse aligned?
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ptr = align16 ' & $ptr & " " & $i & " " & Mod($ptr, 16) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func _dllstructcreate16($struct) ;align auf 16-byte adresse
    local $temp = DllStructCreate($struct)
    local $tempsize = DllStructGetSize($temp) + 16
    $temp = 0;struct löschen
    local $mem = _MemGlobalAlloc($tempsize + 16, BitOR($GMEM_FIXED, $GMEM_ZEROINIT)) ;pointer auf speicher
    ;rest div 16 adresse = offset
    return DllStructCreate($struct, (Number($mem) - Mod(Number($mem), 16) + 16)) ;auf 16 alingned pointer
    EndFunc ;==>_dllstructcreate16

    [/autoit]

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (27. Dezember 2011 um 21:11)

  • Andy: So weit ich weiß, verwendet AutoIt malloc oder etwas vergleichbares zum Allokieren des Speichers. Das garantiert aber nur ein 8byte-Alignment des Speicherbereichs (bei Windows), die Angabe align 16 bezieht sich auf die strukturinterne Zählung und ist daher falsch vermute ich.

    Einmal editiert, zuletzt von progandy (27. Dezember 2011 um 22:49)

  • Zitat

    Andy: So weit ich weiß, verwendet AutoIt malloc oder etwas vergleichbares zum Allokieren des Speichers.

    Ja, da bin ich mir sicher.

    Zitat

    die Angabe align 16 bezieht sich auf die strukturinterne Zählung

    hmm, das würde bedeuten, dass das "align" nur für das Auffüllen von Füllbytes (padding) innerhalb der Struct gut wäre, aber nicht für das Ausrichten an Speicheradressen.
    Wobei zugegebenermaßen ein align 16 recht selten vorkommt.