mal rein aus prinzipiellem Interesse;
Wie werden Strings in VB intern gespeichert/organisiert?
"VarPtr(s)" zeigt auf einen Pointer, der seinerseits auf die
eigentlichen Daten zeigt, "StrPtr(s)" zeigt direkt auf die Daten. Man
kann also auf zwei Wegen die String-Daten (Unicode) in ein Array
kopieren:
CopyMemory b(0), ByVal StrPtr(s), count
oder aber:
CopyMemory vLng, ByVal VarPtr(s), 4&
CopyMemory b(0), ByVal vLng, count
Logisch wäre nun, daß unter VarPtr(s) eine Struktur gespeichert wäre,
die nicht nur den pointer auf die eigentlichen Daten enthält, sondern
auch zumindest die information über die Länge des Strings.
Wenn man aber mit
oldP = StrPtr(s)
CopyMemory ByVal VarPtr(s), b(0), 4&
...
CopyMemory ByVal VarPtr(s), oldP, 4&
den internen Zeiger auf die Daten eines ByteArrays "verbiegt", kann man
zwar über die Stringvariable auf diese Daten (das Array) zugreifen, die
Länge des Strings bleibt jedoch nicht erhalten. Also werden scheinbar
auch VB-Strings mit einem speziellen (Unicode)-Zeichen terminiert (Wie
C-Strings mit chr(0) terminiert werden.) Ist das so richtig? (Ich hatte
immer gedacht, VB würde die Länge seiner Strings "intern" speichern")
Sinn des ganzen wäre es, aus performancegründen den Zeiger eines Strings
auf einen bestehenden Speicherbereich zu "verbiegen", ohne die Daten von
dort in den String kopieren zu müssen. Es würde dann ein binärer
Stringvergleich durchgeführt, und der zeiger wieder zurückgebogen...
(Also Länge und Daten des Strings nicht verändert, damit kein
Speicherleck entsteht)
Leider fällt mir für VB keine andere Möglichkeit ein, größere
Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
vergleichen.
Aber vermutlich muß ich diesmal doch auf C umsteigen :)
Viele Grüße,
Andreas
Die Länge des BSTR-Strings ('Length prefixed string') steht in einem DWORD
>vor< den String-Daten, also an StrPtr(String) - 4 ('Header'). Die
Längenangabe gehört zu dem ('OLE'/)BSTR-String, nicht zu der VB-Referenz
auf den String.
Zusätzlich (VB-Doku): "BSTRs enden normalerweise mit einem zwei Byte langen
Nullzeichen."
> den internen Zeiger auf die Daten eines ByteArrays "verbiegt", kann man
> zwar über die Stringvariable auf diese Daten (das Array) zugreifen, die
> Länge des Strings bleibt jedoch nicht erhalten. Also werden scheinbar
> auch VB-Strings mit einem speziellen (Unicode)-Zeichen terminiert (Wie
> C-Strings mit chr(0) terminiert werden.) Ist das so richtig? (Ich hatte
> immer gedacht, VB würde die Länge seiner Strings "intern" speichern")
Wird durch das oben Gesagte geklärt. Du müßtest in den Elementen 0-3 des
Byte-Arrays die Länge des Strings im Byte-Array speichern und den Zeiger
auf das 4. Element verwenden.
Aber Vorsicht mit diesem verbiegen: Die String-Verwaltung von BASIC
erfordert unter bestimmten Bedingungen eine Garbage Collection. Wann VB
eine derartige Garbage Collection durchführt, liegt außerhalb Deiner
Kontrolle. Auf diese Weise können sich die Adressen von Speicherbereichen
ändern. U.a. deswegen arbeitet man in VB ja in der Regel auch nicht mit
Zeigern, weil VB die Speicherorganisation übernimmt.
> Sinn des ganzen wäre es, aus performancegründen den Zeiger eines Strings
> auf einen bestehenden Speicherbereich zu "verbiegen", ohne die Daten von
> dort in den String kopieren zu müssen. Es würde dann ein binärer
> Stringvergleich durchgeführt, und der zeiger wieder zurückgebogen...
> (Also Länge und Daten des Strings nicht verändert, damit kein
> Speicherleck entsteht)
Vermutlich ist Dir entgangen, daß man vielen VB-Funktionen, die mit Strings
arbeiten, genausogut auch ein Byte-Array (mit leerem Klammerpaar) übergeben
kann.
> Leider fällt mir für VB keine andere Möglichkeit ein, größere
> Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
> vergleichen.
Wenn Dir die Möglichkeiten von Byte-Arrays in vermeintlichen
Nur-String-Funktionen nicht ausreichen, könntest Du auch Funktionen wie
CompareString(), lstrcmp() und lstrcmpi() des Windows-API verwenden.
--
----------------------------------------------------------------------
THORSTEN ALBERS Universität Freiburg
albers@
uni-freiburg.de
----------------------------------------------------------------------
die Längenangabe zum String wird in einem Long direkt *vor* StrPtr(s)
gespeichert. Der String ist ohne abschließende Null. VB Strings sind
übrigens OLE-Strings.
Um auf die Zeichen eines Strings direkt (ohne Umkopierereien) zugreifen
zu können, könntest Du dir
http:\\www.prosource.de\Downloads\VB_Quickies\Strings.zip
ansehen.
Ulrich
--
VB tips and tricks -> http://www.proSource.de/Downloads/
Beispiele für "String-Zeiger-Verbiegungen" findest du hier:
- Inhalte von String-Variablen schnell tauschen
http://vb-tec.de/swapstr.htm
- InStrRev - Letztes Vorkommen suchen
http://vb-tec.de/instrrev.htm
- Vorkommen von Strings in Text schnell zählen
http://vb-tec.de/strcount.htm
Viele Grüße
Jost aus Soest - <http://VB-Tec.de/> - VB-KnowHow und mehr.
--
Sollte ausgerechnet der Staat, der alle Arten der Massen-
vernichtung schon eingesetzt hat, Weltpolizist spielen?
http://www.schwider.de/vietnam.htm - Impressionen+Gedanken
> mal rein aus prinzipiellem Interesse;
>
> Wie werden Strings in VB intern gespeichert/organisiert?
Das wird ausführlichst und teilweise sogar bebildert in der MSDN dargestellt
und erklärt.
Suche mal in der MSDN nach den Begriffen VarPtr und StrPtr.
Du wirst mit Informationen zum Thema überhäuft. U.a. gibt es dabei einen
Artikel mit dem Titel "LightningStrings", der die Speicherorganisation bei
Strings und auch anderen Typen sehr anschaulich bebildert erklärt.
.... schnipp ....
>
> Aber vermutlich muß ich diesmal doch auf C umsteigen :)
Nein, musst Du nicht.
Du brauchst eigentlich nur die Dokumentation (MSDN -> VarPtr, StrPtr) lesen.
Gruß aus St.Georgen
Peter Götz
p.g...@gssg.de
www.gssg.de (mit VB-Tips u. Beispielprogrammen)
> Vermutlich ist Dir entgangen, daß man vielen VB-Funktionen, die mit
Strings
> arbeiten, genausogut auch ein Byte-Array (mit leerem Klammerpaar)
übergeben
> kann.
Wobei aber nur das eingebaute Casting vor der tatsächlichen Übergabe zur
Wirkung kommt - die Funktion selbst erhält doch nur einen String.
> > Leider fällt mir für VB keine andere Möglichkeit ein, größere
> > Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
> > vergleichen.
> Wenn Dir die Möglichkeiten von Byte-Arrays in vermeintlichen
> Nur-String-Funktionen nicht ausreichen, könntest Du auch Funktionen wie
> CompareString(), lstrcmp() und lstrcmpi() des Windows-API verwenden.
Auch ein auch ein gecastetes Byte-Array enthält immer nur die Zeichen und
keine Zusatzinformationen...
Viele Grüße
Harald M. Genauck
ABOUT Visual Basic - das Webmagazin
http://www.aboutvb.de
> mal rein aus prinzipiellem Interesse;
> ...
> Leider fällt mir für VB keine andere Möglichkeit ein, größere
> Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
> vergleichen.
>
> Aber vermutlich muß ich diesmal doch auf C umsteigen :)
Ich hoffe, Du hast das Curland-Buch zur Hand? Dann: Kapitel 17.
Ansonsten:
"Advanced Visual Basic 6", Matthew Curland,
- mehr unter: http://www.aboutvb.de/res/lit/resbchadwadvancedvb6.htm
Läßt sich leider weder falsifizieren noch verifizieren - jedenfalls nicht
ohne Disassembling. Intern nutzt z.B. die VB-Funktion StrComp()
offensichtlich die Windows-API-Funktion CompareString(). Ein Casting ist
also nicht unbedingt notwendig. Es kommt nur darauf an, was der jeweilige
Programmierer bei VB dafür gecodet hat.
> Auch ein auch ein gecastetes Byte-Array enthält immer nur die Zeichen und
> keine Zusatzinformationen...
???
> > Wobei aber nur das eingebaute Casting vor der tatsächlichen Übergabe
zur
> > Wirkung kommt - die Funktion selbst erhält doch nur einen String.
> Läßt sich leider weder falsifizieren noch verifizieren - jedenfalls nicht
> ohne Disassembling. Intern nutzt z.B. die VB-Funktion StrComp()
> offensichtlich die Windows-API-Funktion CompareString(). Ein Casting ist
> also nicht unbedingt notwendig. Es kommt nur darauf an, was der jeweilige
> Programmierer bei VB dafür gecodet hat.
Eigentlich würde ich ja sagen: Durch einen String-Parameter passtnichts
anderes als ein BSTR.
Aber tatsächlich: Bei StrComp und StrConv sind die Parameter lediglich als
Variant deklariert. Es könnte also durchaus ein Byte-Array ungecastet
durchflutschen und als solches erkannt und direkt verarbeitet werden
können.
> > Auch ein auch ein gecastetes Byte-Array enthält immer nur die Zeichen
und
> > keine Zusatzinformationen...
> ???
Schon gut, einmal "auch ein" hätte gereicht...
;-)
Das war darauf gemünzt, dass ein zu einem Byte-Array konvertierter String
sämtliche Zusatzinformationen des Strings verliert, sondern nur noch die
Zeichen enthält. Wobei das Byte-Array als SafeArray natürlich im
Hintergrund wiederum andere Zusatzinformationen mitschleppt.
> Die Länge des BSTR-Strings ('Length prefixed string') steht in
> einem DWORD >vor< den String-Daten, also an StrPtr(String) - 4
> ('Header'). Die Längenangabe gehört zu dem ('OLE'/)BSTR-String,
> nicht zu der VB-Referenz auf den String.
> Zusätzlich (VB-Doku): "BSTRs enden normalerweise mit einem zwei
> Byte langen Nullzeichen."
Danke, so scheints jetzt zu funktionieren.
>> [...]
> Aber Vorsicht mit diesem verbiegen: Die String-Verwaltung von
> BASIC erfordert unter bestimmten Bedingungen eine Garbage
> Collection. Wann VB eine derartige Garbage Collection durchführt,
> liegt außerhalb Deiner Kontrolle. Auf diese Weise können sich die
> Adressen von Speicherbereichen ändern. U.a. deswegen arbeitet man
> in VB ja in der Regel auch nicht mit Zeigern, weil VB die
> Speicherorganisation übernimmt.
Darf man annehmen, daß die Garbage Collection nur außerhalb
synchronisierter VB-Threads zuschlägt? Ich meine damit, ob die
Speicheradressen während der Abarbeitung eines "Event-Threads" auf
jeden Fall bestehen bleiben (sofern nicht durch DoEvents oder
ähnlichs unterbrochen)?
>> Sinn des ganzen wäre es, aus performancegründen den Zeiger eines
>> Strings auf einen bestehenden Speicherbereich zu "verbiegen",
>> ohne die Daten von dort in den String kopieren zu müssen. Es würde
> Vermutlich ist Dir entgangen, daß man vielen VB-Funktionen, die mit
> Strings arbeiten, genausogut auch ein Byte-Array (mit leerem
> Klammerpaar) übergeben kann.
Nein :-)
Ich habe leider kein Byte-Array, ich habe nur einen Zeiger auf einen
Speicherbereich, indem mehrere Megabyte Daten liegen (z.B. Bitmaps oder
Sample-Daten). Wenn ich diese erst ein ein VB-ByteArray oder in einen
String verschieben muß, dauert das zu lange. Wenn ich mit Copymemory auf
die einzelnen Bytes in diesem Speicherblock zugreife, ebenso.
>> Leider fällt mir für VB keine andere Möglichkeit ein, größere
>> Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
>> vergleichen.
> Wenn Dir die Möglichkeiten von Byte-Arrays in vermeintlichen
> Nur-String-Funktionen nicht ausreichen, könntest Du auch Funktionen
> wie CompareString(), lstrcmp() und lstrcmpi() des Windows-API
> verwenden.
Hab ich grad mal ausprobiert:
lstrcmp ist zwar schnell, benutzt jedoch nullterminierte Strings. Ist
daher nicht für reine Binärdaten geeignet.
Mit CompareString geht es zwar, weil man die Länge direkt angeben kann,
aber diese Funktion braucht in etwa die gleiche Zeit, wie das Umkopieren
der Daten in ein VB-String mit nachfolgendem Stringvergleich.
Die Stringzeiger zu verbiegen bringt bei mir die beste Performance.
Hab schon überlegt, Assembler einzubauen, aber ich glaube nicht, daß das
wesentlich schneller ist, als der VB-Stringvergleich.
Viele Grüße,
Andreas
>> Wie werden Strings in VB intern gespeichert/organisiert?
> Das wird ausführlichst und teilweise sogar bebildert in der MSDN
> dargestellt und erklärt.
> Suche mal in der MSDN nach den Begriffen VarPtr und StrPtr.
> Du wirst mit Informationen zum Thema überhäuft. U.a. gibt es dabei
> einen Artikel mit dem Titel "LightningStrings", der die
> Speicherorganisation bei Strings und auch anderen Typen sehr
> anschaulich bebildert erklärt.
Danke für den Hinweis, der Artikel war sehr hilfreich.
An dieser Stelle auch Dank an alle anderen für Eure Antworten!
Viele Grüße,
Andreas
>> Wobei aber nur das eingebaute Casting vor der tatsächlichen
>> Übergabe zur Wirkung kommt - die Funktion selbst erhält doch
>> nur einen String.
> Läßt sich leider weder falsifizieren noch verifizieren -
> jedenfalls nicht ohne Disassembling. Intern nutzt z.B. die
> VB-Funktion StrComp() offensichtlich die Windows-API-Funktion
> CompareString(). Ein Casting ist also nicht unbedingt notwendig.
Ich hab mal einen Zeitvergleich gemacht:
16 MB Speicherblöcke, je 10 Durchläufe, Angaben in ms.
Es gibt keine gravierenden Unterschiede, ob der Test in der
IDE läuft, oder kompiliert, oder sogar optimiert wurde.
API-CompareStringA: 2023
str1 = str2: 1242
VB-StrComp/strings: 1242
VB-StrComp/byteArrays: 3925
Das umkopieren aller Daten dauert im Vergleich 1572,
das Casting von ByteArray nach String 2684 ms.
Es sieht zumindest so aus, als würden bei der Übergabe von byteArrays
an StrComp diese auf jeden fall in einen String überführt und dazu
umkopiert. (2684+1242~3925)
Warum CompareString langsamer ist als StrComp erschließt sich mir
momentan nicht.
Viele Grüße,
Andreas
Hat da doch wieder einmal ein MS-Programmierer geschlampt...
> Warum CompareString langsamer ist als StrComp erschließt sich mir
> momentan nicht.
Da müßte man den Code sehen, den Du verwendest.
Ich würde sagen (auch angesichts der Casting-Frage im anderen Teil-Tread):
Bei VB darf man gar nichts annehmen... :->
> Ich habe leider kein Byte-Array, ich habe nur einen Zeiger auf einen
> Speicherbereich, indem mehrere Megabyte Daten liegen (z.B. Bitmaps oder
> Sample-Daten). Wenn ich diese erst ein ein VB-ByteArray oder in einen
> String verschieben muß, dauert das zu lange. Wenn ich mit Copymemory auf
> die einzelnen Bytes in diesem Speicherblock zugreife, ebenso.
Du bräuchtest ja weder den gesamten Speicherbereich in ein Byte-Array
kopieren noch jedes einzelne Byte: Wenn Du bei dieser Methode bleiben
wolltest, könntest Du auch zwei Byte-Arrays als Puffer verwenden, wobei Du
Dir eine ideale Größe dieser Puffer 'kallibrierst'. Dann verschiebst Du
immer Teile aus den Speicherbereichen in diese Puffer-Byte-Arrays und
vergleichst anhand der Byte-Arrays (am besten nicht mit den
VB-Vergleichsmethoden, sondern 'händisch'). Nur im ungünstigsten Fall
würdest Du so nach und nach die gesamten Speicherbereiche durch den Puffer
jagen.
Ansonsten würde ich tatsächlich - wie Du selbst ursprgl. überlegt hattest -
ganz von den VB-Vergleichsmethoden absehen und gleich zu API-Funktionen
greifen, sei es, daß Du Dir das API selbst baust mit den entsprechenden
C-Funktionen, sei es, daß Du geeignete Funktionen des Windows-API
verwendest (lstrcmp() etc.).
> lstrcmp ist zwar schnell, benutzt jedoch nullterminierte Strings. Ist
> daher nicht für reine Binärdaten geeignet.
Doch, wenn Du folgendes beachtest:
- Hinter dem letzte Byte des jew. fragl. Speicherbereiches brauchst Du ein
NULL-Byte.
- lstrcmp() kann m.W. nur Strings von max. 64K länge bearbeiten; d.h. Du
mußt (wenn das tatsächlich zutrifft) an den 64K Grenzen das jew. Byte
auslesen, stattdessen ein NULL-Byte setzen, vergleichen und anschließend
das Original-Byte wieder hinschreiben.
- Du mußt die Speicherbereiche 'in Portionen' vergleichen, also immer bis
zum nächsten 'Pseudo'-Stringende.
Du könntest auch zunächst erst einmal mit lstrlen() prüfen, ob die
'Strings' gleich lang sind, d.h. an derselben Stelle ein NULL-Byte haben.
> Mit CompareString geht es zwar, weil man die Länge direkt angeben kann,
> aber diese Funktion braucht in etwa die gleiche Zeit, wie das Umkopieren
> der Daten in ein VB-String mit nachfolgendem Stringvergleich.
Da müßte man Deinen Code sehen, um das zu verstehen...
> Die Stringzeiger zu verbiegen bringt bei mir die beste Performance.
> Hab schon überlegt, Assembler einzubauen, aber ich glaube nicht, daß das
> wesentlich schneller ist, als der VB-Stringvergleich.
Wenn Du den Assemblercode in das VB-Programm einbauen kannst, wäre das mit
Sicherheit schneller, denn es würden sämtliche Funktions-Aufrufe entfallen.
Ansonsten solltest Du, wie gesagt, tatsächlich auf eine DLL ausweichen und
die geeigneten C-Funktionen verwenden (memcmp() bzw. memicmp() o.dgl.).
>> Warum CompareString langsamer ist als StrComp erschließt sich mir
>> momentan nicht.
> Da müßte man den Code sehen, den Du verwendest.
ret = CompareString(0, 0, ByVal h1 + 4, le, ByVal h2 + 4, le)
wobei h1 und h2 die Zeiger auf den BSTR-Block sind, mit der Größe le+6.
Die anderen Vergleiche s.u. (ist natürlich recht primitiv,
da nur ein schneller Test).
Viele Grüße,
Andreas
'###########################################################
Private Declare Function GlobalFree Lib "kernel32.dll" ( _
ByVal hMem As Long) As Long
Private Declare Function GlobalAlloc Lib "kernel32.dll" ( _
ByVal wFlags As Long, ByVal dwBytes As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias _
"RtlMoveMemory" (Destination As Any, Source As Any, _
ByVal Length As Long)
Private Declare Function CompareString Lib "kernel32.dll" _
Alias "CompareStringA" ( _
ByVal Locale As Long, ByVal dwCmpFlags As Long, _
lpString1 As Any, ByVal cchCount1 As Long, _
lpString2 As Any, ByVal cchCount2 As Long) As Long
Private Declare Function GetTickCount Lib "kernel32.dll" () As Long
Private Const GMEM_FIXED As Long = &H0
Private Const GMEM_ZEROINIT As Long = &H40
Private Const GPTR As Long = (GMEM_FIXED Or GMEM_ZEROINIT)
Private Sub Main()
Const n As Long = 10
Dim m As Long
Dim t(1 To 6) As Long 'time diffs
Dim ret As Long
Dim s1 As String, s2 As String
Dim op1 As Long, op2 As Long 'old StrPtr-val's
Dim b1() As Byte, b2() As Byte
Dim h1 As Long, h2 As Long 'BSTR pointers
Dim le As Long 'data cnt
le = &H1000000 '16 "MebiByte"
h1 = GlobalAlloc(GPTR, le + 6)
h2 = GlobalAlloc(GPTR, le + 6)
CopyMemory ByVal h1, le, 4&
CopyMemory ByVal h2, le, 4&
CopyMemory ByVal h1 + 4 + le, 0&, 2&
CopyMemory ByVal h2 + 4 + le, 0&, 2&
t(1) = GetTickCount
For m = 1 To n
ret = CompareString(0, 0, ByVal h1, le, ByVal h2, le)
Next m
t(1) = GetTickCount - t(1)
s1 = " ": s2 = " ": op1 = StrPtr(s1): op2 = StrPtr(s2)
CopyMemory ByVal VarPtr(s1), h1 + 4, 4&
CopyMemory ByVal VarPtr(s2), h2 + 4, 4&
t(2) = GetTickCount
For m = 1 To n
ret = (s1 = s2)
Next m
t(2) = GetTickCount - t(2)
t(3) = GetTickCount
For m = 1 To n
ret = StrComp(s1, s2, vbBinaryCompare)
Next m
t(3) = GetTickCount - t(3)
CopyMemory ByVal VarPtr(s1), op1, 4&
CopyMemory ByVal VarPtr(s2), op2, 4&
GlobalFree h1: GlobalFree h2
ReDim b1(le - 1): ReDim b2(le - 1)
t(4) = GetTickCount
For m = 1 To n
ret = StrComp(b1(), b2(), vbBinaryCompare)
Next m
t(4) = GetTickCount - t(4)
t(5) = GetTickCount
For m = 1 To n
CopyMemory b1(0), b2(0), le
CopyMemory b2(0), b1(0), le
Next m
t(5) = GetTickCount - t(5)
t(6) = GetTickCount
For m = 1 To n
s1 = b1()
s2 = b2()
Next m
t(6) = GetTickCount - t(6)
MsgBox CStr(t(1)) & " (CompareString)" & vbCrLf & _
CStr(t(2)) & " (s1 = s2)" & vbCrLf & _
CStr(t(3)) & " (StrComp/String)" & vbCrLf & _
CStr(t(4)) & " (StrComp/ByteArray)" & vbCrLf & _
CStr(t(5)) & " (CopyMemory)" & vbCrLf & _
CStr(t(6)) & " (String = ByteArray)"
End Sub
Ich habe Deinen Code 'mal durchlaufen lassen - ja, tatsächlich,
API-CompareString() ist langsamer als VB-StrComp(). CompareString() wird
zwar um ein gutes Stück schneller, wenn man den Optimierungs-Angaben in der
Doku folgt, d.h. indem man für die String-Länge -1L übergibt, aber dann
besteht wieder das Problem, daß sie beim ersten NULL-Byte auf jeden Fall
aufhört.
Fragt sich allerdings noch, ob der Zeitverlust >in der Funktion< liegt oder
in dem >Aufruf der Funktion<.
Immerhin scheint das eindeutig zu zeigen, daß die VB-Funktion StrComp()
intern doch nicht auf die API-Funktion zurückgreift, sondern offenbar eine
eigene Implementation hat - obwohl (zumindest die VB 5.0-Runtime) die
Kernel-Funktion CompareString() importiert.
Der nächste Vergleich muß 'mal anhand einer DLL gemacht werden, welche die
passenden C-Befehle verwendet...
> Ich würde sagen (auch angesichts der Casting-Frage im anderen
> Teil-Tread): Bei VB darf man gar nichts annehmen... :->
*ROTFL*
> Du bräuchtest ja weder den gesamten Speicherbereich in ein Byte-Array
> kopieren noch jedes einzelne Byte: Wenn Du bei dieser Methode bleiben
> wolltest, könntest Du auch zwei Byte-Arrays als Puffer verwenden,
> wobei Du Dir eine ideale Größe dieser Puffer 'kallibrierst'. Dann
> verschiebst Du immer Teile aus den Speicherbereichen in diese
> Puffer-Byte-Arrays und vergleichst anhand der Byte-Arrays (am besten
> nicht mit den VB-Vergleichsmethoden, sondern 'händisch'). Nur im
> ungünstigsten Fall würdest Du so nach und nach die gesamten
> Speicherbereiche durch den Puffer jagen.
Das stimmt, ändert aber auch nichts daran, daß durch das Verschieben der
tatsächlich verglichenen Bytes ungefähr die doppelte Zeit benötig wird,
wie beim alleinigen Vergleich.
>> lstrcmp ist zwar schnell, benutzt jedoch nullterminierte Strings. Ist
>> daher nicht für reine Binärdaten geeignet.
> Doch, wenn Du folgendes beachtest:
> - Hinter dem letzte Byte des jew. fragl. Speicherbereiches brauchst Du
> ein NULL-Byte.
> - lstrcmp() kann m.W. nur Strings von max. 64K länge bearbeiten; d.h.
> Du mußt (wenn das tatsächlich zutrifft) an den 64K Grenzen das jew.
> Byte auslesen, stattdessen ein NULL-Byte setzen, vergleichen und
> anschließend das Original-Byte wieder hinschreiben.
> - Du mußt die Speicherbereiche 'in Portionen' vergleichen, also immer
> bis zum nächsten 'Pseudo'-Stringende.
> Du könntest auch zunächst erst einmal mit lstrlen() prüfen, ob die
> 'Strings' gleich lang sind, d.h. an derselben Stelle ein NULL-Byte
> haben.
Hört sich recht umständlich an.
Außerdem würde letztlich durch strlen und lstrcmp der String zweimal
durchlaufen, dann kann ich auch die Daten in kleineren Blöcken kopieren,
wie Du oben geschrieben hast.
> Ansonsten solltest Du, wie gesagt, tatsächlich auf eine DLL
> ausweichen und die geeigneten C-Funktionen verwenden (memcmp() bzw.
> memicmp() o.dgl.).
gerade geschehen: memcmp() ist noch ca 15% schneller als der reine
VB-Stringvergleich. (benötigt 1080 ms, zum Vgl. mit den anderen Zahlen).
Viele Grüße,
Andreas
Ist auch umständlich. Aber "zweimal durchlaufen" stimmt nicht, denn das
terminierende NULL-Byte verhindert das (oder meinst Du lstrlen()? Da stimmt
das natürlich).
Inzwischen habe ich auch lstrcmp() etc. gemessen; das ist zwar besser als
CompareString(), bleibt aber auch auch noch ein gutes Stück hinter
VB-StrCmp() zurück - also auch das vergessen...
> gerade geschehen: memcmp() ist noch ca 15% schneller als der reine
> VB-Stringvergleich. (benötigt 1080 ms, zum Vgl. mit den anderen Zahlen).
Interessant: Ich habe es ebenfalls gerade bei mir versucht (PENTIUM III 733
MHz/ SDRAM 133, WIN 95, VC++ 5, VB 5) - und da ist auch memcmp()
>langsamer< als StrCmp(), allerdings ist der Unterschied nicht mehr allzu
groß. Auf was für einem System hast Du gemessen?
Einläutung der nächsten Runde:
1.) Speichervergleich in einer simplen C-Schleife (dürfte allerdings kaum
Unterschiede zu
memcmp() bringen.
2.) Inline-Assembler in C.
>> gerade geschehen: memcmp() ist noch ca 15% schneller als der reine
>> VB-Stringvergleich. (benötigt 1080 ms, zum Vgl. mit den anderen
>> Zahlen).
> Interessant: Ich habe es ebenfalls gerade bei mir versucht
> (PENTIUM III 733 MHz/ SDRAM 133, WIN 95, VC++ 5, VB 5) - und da ist
> auch memcmp() >langsamer< als StrCmp(), allerdings ist der
> Unterschied nicht mehr allzu groß. Auf was für einem System hast
> Du gemessen?
PIII 866 MHz, SDRAM 133, WIN 2000, VC++ 6, VB 6
Allerdings verhält sich bei mir memcmp() genau umgekehrt zu den
optimize-Einstellungen des Compilers. Je mehr ich auf Geschwindikgeit
optimiere, desto langsamer wird es (bis faktor 6). Seltsam.
> Einläutung der nächsten Runde:
> 1.) Speichervergleich in einer simplen C-Schleife (dürfte allerdings
> kaum Unterschiede zu memcmp() bringen.
Dachte ich auch. Aber:
folgende Schleife ist bei mir ca 30% schneller als StrComp (und etwas
schneller als memcmp(), *freu*). Natürlich alle Compilerdirektiven auf
"fast code" ;-)
Ich habe verschiedene Varianten versucht, diese ist scheinbar die
schnellste, da pro Schleifendurchlauf nur ein Vergleich stattfindet, und
davon auch der Schleifenabbruch abnhängt. In allen anderen Fällen
braucht man einen Vgl. mehr.
Falls man doch noch etwas optimieren kann, dann her damit :)
Bin in C/C++ leider nicht so fit...
/*##########################################################*/
__declspec(dllexport) int __stdcall cMemLoop(
int *hMem1,
int *hMem2,
int len) // len is assumed dword-multiple !
{
register int *i; // damned, VC++ ignores "register"
register int *j;
int lm1;
int l;
int *l1;
int *l2;
i = hMem1;
j = hMem2;
// get last dwords and compare them
l = len/4 - 1;
l1 = hMem1+l;
l2 = hMem2+l;
lm1 = *l1;
if (lm1 == *l2)
{
*l1 = ~*l2; // last dwords must be different
while (*i == *j)
{
i++;
j++;
}
*l1 = lm1; // reset changed dword
if (i == l1) {return -1;} else {return 0;}
}
else return 0;
}
/*##########################################################*/
> 2.) Inline-Assembler in C.
Könnte man auf die Schleife beschränken; vermute aber, daß es nicht viel
bringt, da der Compiler hierfür sicher schon optimalen code erzeugt.
Viele Grüße,
Andreas
> Leider fällt mir für VB keine andere Möglichkeit ein, größere
> Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
> vergleichen.
Versuch mal das hier (native kompilieren + alle extended Options):
'****In eine Form
Option Explicit
Private Type SAFEARRAY1D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
cElements As Long
lLbound As Long
End Type
Private Declare Function ArrPtr& Lib "msvbvm60" Alias "VarPtr" _
(Ptr() As Any)
Private Declare Sub RtlMoveMemory Lib "kernel32" _
(Dst As Any, Src As Any, ByVal cBytes&)
Private Sub Form_Click()
Dim T#, B1() As Byte, B2() As Byte, i&
ReDim B1(15999999): ReDim B2(15999999)
' B2(15999999) = 1
DoEvents
T = Timer
For i = 1 To 10
Print BinComp(VarPtr(B1(0)), VarPtr(B2(0)), 16000000)
Next i
Caption = Timer - T
End Sub
Private Function BinComp(ByVal P1&, ByVal P2&, ByVal cB&) As Boolean
Dim L1@(), saL1 As SAFEARRAY1D, L2@(), saL2 As SAFEARRAY1D
Dim i&, Rest&, B1(6) As Byte, B2(6) As Byte
If cB <= 0 Then Exit Function
Rest = cB Mod 8
saL1.cDims = 1: saL1.cbElements = 8: saL1.cElements = (cB \ 8) + 1
saL1.pvData = P1: RtlMoveMemory ByVal ArrPtr(L1), VarPtr(saL1), 4
saL2.cDims = 1: saL2.cbElements = 8: saL2.cElements = (cB \ 8) + 1
saL2.pvData = P2: RtlMoveMemory ByVal ArrPtr(L2), VarPtr(saL2), 4
For i = 0 To UBound(L1) - 1
If L1(i) <> L2(i) Then GoTo ex
Next i
If Rest Then
RtlMoveMemory B1(0), L1(UBound(L1)), Rest
RtlMoveMemory B2(0), L2(UBound(L2)), Rest
For i = 0 To Rest - 1
If B1(i) <> B2(i) Then GoTo ex
Next i
End If
BinComp = True
ex:
RtlMoveMemory ByVal ArrPtr(L1), 0&, 4
RtlMoveMemory ByVal ArrPtr(L2), 0&, 4
End Function
Olaf
>> 2.) Inline-Assembler in C.
>
> Könnte man auf die Schleife beschränken; vermute aber, daß es nicht
> viel bringt, da der Compiler hierfür sicher schon optimalen code
> erzeugt.
Die While-Schleife im vorherigen posting durch assembler-code ersetzt,
bringt bei mir kaum mehr Performance. (ca 2%).
/*############################################*/
__asm
{
push eax;
push ebx;
push ecx;
push edx;
mov ecx, i;
mov edx, j;
mov ebx, 00000000;
jmp stepin
repeat:
add ebx, 00000004;
stepin:
mov eax, dword ptr [ecx+ebx];
cmp eax, dword ptr [edx+ebx];
je repeat;
add ebx, 00000004;
add edx,ebx;
add ecx,ebx;
mov i, ecx;
mov j, edx;
pop edx;
pop ecx;
pop ebx;
pop eax;
}
/*############################################*/
Viele Grüße,
Andreas
Spätestens seit dem 386 kennt der Prozessor eigene 'String'-Befehle. Für
den Vergleich könnte das eine der CMPS*- oder auch SCAS*-Varianten sein,
wobei CMPS* am effektivsten sein dürfte. Die Schleife würde wahrscheinlich
effektiver über eine der REP*-Varianten realisiert.
Die Summe der Taktzyklen, die für die einzelnen Befehle benötigt werden,
dürfte höher sein, als die Taktzyklen für den jeweiligen
'zusammenfassenden' Befehl.
Salopp gesagt: Lahm! Jedenfalls nicht zu vergleichen mit StrComp(). Die
Anweisung "native (...)" habe ich ignoriert, denn es geht ja darum,
StrComp() zu schlagen >ohne< spezielle Anforderungen.
Ich habe noch einmal etwas anderes mit diesem Umkopieren versucht, mit dem
möglichst weitgehend bei VB bleibt (d.h. die Schnelligkeit von StrComp()
ausnutzt), andererseits berücksichtig, daß die Speicherbereiche nicht von
VB verwaltet werden - und keine String-Zeiger verbiegt, was ja eventuell
katastrophal sein könnte.
Schema (h1, h2, le aus Deinem Code):
Const PASSCOUNT As Long = 10
Const CHARCOUNT_SECTION As Long = &H20
Const BYTECOUNT_SECTION As Long = CHARCOUNT_SECTION * 2&
Dim sString1 As String, sString2 As String
Dim lSection As Long, lSections As Long
Dim pString1 As Long, pString2 As Long
Dim lPass As Long, lResult As Long
dim pMem1 As Long, pMem2 As Long
sString1$ = String$(CHARCOUNT_SECTION, vbNullChar)
sString2$ = String$(CHARCOUNT_SECTION, vbNullChar)
pString1& = StrPtr(sString1$)
pString2& = StrPtr(sString2$)
lSections& = le \ BYTECOUNT_SECTION
For lPass& = 1 To PASSCOUNT
pMem1& = h1 + 4
pMem2& = h2 + 4
For lSection& = 1 To lSections&
Call CopyMemory(ByVal pString1&, ByVal pMem1&, BYTECOUNT_SECTION)
Call CopyMemory(ByVal pString2&, ByVal pMem2&, BYTECOUNT_SECTION)
lResult& = StrComp(sString1$, sString2$)
If lResult& Then Exit For
pMem1& = pMem1& + BYTECOUNT_SECTION
pMem2& = pMem2& + BYTECOUNT_SECTION
Next lSection&
Next lPass&
If (le Mod BYTECOUNT_SECTION)
' ein paar Bytes noch anders verlgeichen...
End If
Es wird also immer ein Abschnitt aus dem jew. Speicherbereich in den jew.
n-dimensionierten String kopiert.
Die Zeit kommt an Dein reines StrComp() recht nahe heran, das Umkopieren
scheint relativ 'zeitunaufwendig' zu sein. Auch hier könnte man noch
optimieren - und auch eine sinnvolle Kombination von Puffer(-String)-Größe
und ggfs. restlichen, noch zu vergleichenden Bytes finden.
> Salopp gesagt: Lahm! Jedenfalls nicht zu vergleichen mit StrComp().
Komisch, bei mir läuft das mehr als doppelt so schnell wie StrComp().
> Die Anweisung "native (...)" habe ich ignoriert, denn es geht ja darum,
> StrComp() zu schlagen >ohne< spezielle Anforderungen.
Ah ja, ohne spezielle Anforderungen.
Deswegen also wird VC verwendet mit allen Fast-Code-Optionen +
Inline-Assembler.
Dieselben Compiler-Optionen mit ein paar MouseClicks in VB einzustellen und
das Ganze bei vergleichbarer Geschwindigkeit in der "Muttersprache"
abzuwickeln - wer käme denn auf solch absurde Ideen?
Olaf
War wohl ein bischen falsch ausgedrückt: "Spezielle Anforderungen" in Bezug
auf das VB-Programm im allgemeinen. Wenn Du Deine VB-Anforderungen nimmst,
dann betreffen die immer das gesamte VB-Programm. Mit Kompilierung zu
N-Code z.B. kannst Du aber Probleme mit anderen Teilen des Programmes
heraufbeschwören. Die Verlagerung des Vergleiches in eine DLL z.B. betrifft
nicht das gesamte VB-Programm, erfordert keine spezielle Kompilierung in VB
und kann trotzdem z.T. erheblichen Geschwindigkeitszuwachs bringen.
> War wohl ein bischen falsch ausgedrückt: "Spezielle Anforderungen" in
> Bezug auf das VB-Programm im allgemeinen. Wenn Du Deine VB-Anforderungen
> nimmst, dann betreffen die immer das gesamte VB-Programm.
Nein, stimmt nicht.
> Die Verlagerung des Vergleiches in eine
> DLL z.B. betrifft nicht das gesamte VB-Programm, erfordert keine
> spezielle Kompilierung in VB und kann trotzdem z.T. erheblichen
> Geschwindigkeitszuwachs bringen.
Kann man für VB so hinschreiben:
Die Verlagerung des Vergleiches in eine *native VB-* DLL
betrifft nicht das gesamte VB-Programm, erfordert keine
spezielle Kompilierung in VB und kann trotzdem z.T. erheblichen
Geschwindigkeitszuwachs bringen.
Olaf
Das wiederum erfordert die Mitlieferung, Registrierung etc. einer
zusätzlichen Datei.
> Das wiederum erfordert die Mitlieferung, Registrierung etc. einer
> zusätzlichen Datei.
Mal abgesehen davon, dass eine C-DLL auch mitgeliefert werden müsste, wüsst'
ich nicht, was daran schlecht sein soll, sich eine eigene
VB-Komponenten-Sammlung aufzubauen, und je nach Anwendung die eine oder
andere (native) DLL mit in ein "ordentliches Setup" zu integrieren.
Wer COM-DLLs ohne Registrierung laden möchte, kann dies mit ca. 30 Zeilen
VB-Code über LoadLibrary und DllGetClassObject tun.
Wieso also diese Abneigung gegenüber Lösungen in der "Muttersprache" - wir
sind hier immerhin in einer VB-NG und nicht bei jedem liegt ein VC nebenan
auf der Platte.
Olaf
Ansichtssache! Ich bin grundsätzlich für das geringst mögliche und am
wenigsten aufwendige Maß an Abhängigkeiten. Und eine Standard-DLL ist nun
einmal weniger aufwendig, sowohl in Bezug auf die Mitlieferung als auch auf
die Verwaltung im System.
Aber wie dem auch sei: "Lahm" war ja auch nur unverschämt salopp gesagt,
tatsächlich ist Deine Methode auch in P-Code ordentlich schnell, aber eben
nicht so schnell wie VB-StrComp() oder die C(-Assembler)-Methode.
Hallo Olaf,
>> Leider fällt mir für VB keine andere Möglichkeit ein, größere
>> Speicherbereiche direkt (d.h. ohne die Daten zu kopieren) zu
>> vergleichen.
> Versuch mal das hier (native kompilieren + alle extended Options):
>
> ---/ [snipp]
Ist schon ordentlich schnell, aber leider nicht der *best case*. ;-)
Ich hab mir auch irgendwie in den Kopf gesetzt, die Daten ohne
Umkopieren zu vergleichen, da das Umkopieren auch in etwa die gleiche
Zeit benötigt, wie der Vergleich.
Hab meinen asm code jetzt in VB eingebunden, falls es interessiert:
http://www.abotech.de/down.php?doc=MemCompare.bas&ref=3
(rechter Mausklick -> "Speichern unter...")
Praktische Anwendung des ganzen: (bin. Dateivergleich, die
Geschwindigkeit wird in erster Linie durch die HD begrenzt)
http://www.abotech.de/down.php?doc=FileCompare.bas&ref=3
(rechter Mausklick -> "Speichern unter...")
Viele Grüße,
Andreas
>> Das stimmt, ändert aber auch nichts daran, daß durch das Verschieben
>> der tatsächlich verglichenen Bytes ungefähr die doppelte Zeit
>> benötig wird, wie beim alleinigen Vergleich.
> Ich habe noch einmal etwas anderes mit diesem Umkopieren versucht, mit
> dem möglichst weitgehend bei VB bleibt (d.h. die Schnelligkeit von
> StrComp() ausnutzt), andererseits berücksichtig, daß die
> Speicherbereiche nicht von VB verwaltet werden - und keine
> String-Zeiger verbiegt, was ja eventuell katastrophal sein könnte.
nach einigen Tagen VB-Abstinenz hab ich mir das gestern mal genauer
überlegt und ein typisches Proggi angeschaut.
Die Garbage Collection muß letztlich gezielt ausgelöst werden. z.B. in
Folge eines Aufrufes von Redim, oder der Zuweisung von Strings. Oder
automatisch, wenn das programm IDLE ist, zum Aufräumen reservierter
Speicherbereiche, etc. DLL-Aufrufe bewirken, daß evtl übergebene Strings
(bzw. deren Kopien) oder Arrays zuvor "gelocked" werden, um sie verm.
von der Garbage Collection auszuschließen. Während der DLL-Code
abgearbeitet wird, kann die Garbage Coll. nicht zuschlagen, da dieser
Thread nicht so einfach unterbrochen werden kann, und VB kein
Multithreading betreibt. Das Locken dieser Speicherbereiche hat seinen
Sinn vermutlich darin, bei DLL-Aufrufen im Zusammenhang mit Callbacks
(innerhalb derer die G.C. ja stattfinden könnte) diese Bereiche zu
schützen. Der Aufruf von StrPtr selbst scheint diese Sperre zu bewirken,
solange im aktuellen Gültigkeitsbereich auf den String zugegriffen
werden kann. Werde das aber nochmals nachprüfen. Das Verbiegen selbst
bewirkt leider keine Sperre, da StrPtr nicht aufgerufen wird. Aber ggf.
kann man StrPtr vorher aufrufen, um die Sperre einzuleiten, oder man
ruft die entsprechenden Runtime-Sperrfunktionen explizit auf.
Wenn jedoch das Verbiegen und der Datenvergleich direkt aufeinander
folgen, sehe ich keine Möglichkeit, daß hier eine G.C. eingreifen
könnte. Man muß nur sofort wieder "zurückbiegen", und darf das nicht so
stehen lassen.
> [Code Snipped]
> Es wird also immer ein Abschnitt aus dem jew. Speicherbereich in den
> jew. n-dimensionierten String kopiert.
> Die Zeit kommt an Dein reines StrComp() recht nahe heran, das
> Umkopieren scheint relativ 'zeitunaufwendig' zu sein. Auch hier
> könnte man noch optimieren - und auch eine sinnvolle Kombination von
> Puffer(-String)-Größe und ggfs. restlichen, noch zu vergleichenden
> Bytes finden.
Ist schnell, aber eben nicht optimal.
Ich habs jetzt mit Assembler in VB versucht, funktioniert bei mir
einwandfrei.
Würde mich interessieren, ob's auch in VB5 geht.
http://www.abotech.de/down.php?doc=MemCompare.bas&ref=4
(rechter Mausklick -> "Speichern unter...")
Praktische Anwendung des ganzen: (bin. Dateivergleich, die
Geschwindigkeit wird in erster Linie durch die HD begrenzt)
http://www.abotech.de/down.php?doc=FileCompare.bas&ref=4
>> jmp stepin
>> repeat:
>> add ebx, 00000004;
>> stepin:
>> mov eax, dword ptr [ecx+ebx];
>> cmp eax, dword ptr [edx+ebx];
>> je repeat;
> Spätestens seit dem 386 kennt der Prozessor eigene 'String'-Befehle.
> Für den Vergleich könnte das eine der CMPS*- oder auch
> SCAS*-Varianten sein, wobei CMPS* am effektivsten sein dürfte. Die
> Schleife würde wahrscheinlich effektiver über eine der
> REP*-Varianten realisiert.
> Die Summe der Taktzyklen, die für die einzelnen Befehle benötigt
> werden, dürfte höher sein, als die Taktzyklen für den jeweiligen
> 'zusammenfassenden' Befehl.
Hab ich mal ausprobiert. Egal, wie ichs gemacht habe, meine Variante war
immer schneller. Kann auch daran liegen, daß ich pro Schleifendurchlauf
weniger Operationen habe, als die Variante mit rep* cmps*. (sie muß
immerhin neben dem eigentlichen Vergleich noch ecx auf 0 prüfen.)
Wenn es aber irgendwo code dazu gibt, probier ich es gerne nochmal aus.
Bin in asm nicht so fit, und habe zudem die einzigen Infos zu cmps* und
Co aus den Intel-Specs. Ist dort eher lasch erklärt.
Viele Grüße,
Andreas
> Ist schon ordentlich schnell, aber leider nicht der *best case*. ;-)
> Ich hab mir auch irgendwie in den Kopf gesetzt, die Daten ohne
> Umkopieren zu vergleichen, da das Umkopieren auch in etwa die gleiche
> Zeit benötigt, wie der Vergleich.
Mit ein wenig Inline-Kaskadierung hat native VB6-Code nur noch etwa 6%
Nachteil gegenüber der Assembler-Routine.
Mein Beispiel kopiert auch keine Daten um - es spannt virtuelle Arrays eines
64Bit-Datentyps über die zu analysierenden Bereiche.
> Hab meinen asm code jetzt in VB eingebunden, falls es interessiert:
> http://www.abotech.de/down.php?doc=MemCompare.bas&ref=3
> (rechter Mausklick -> "Speichern unter...")
BTW - Mit Deiner Routine stimmt irgendwas nicht - sie zerschiesst bei mir
die Originaldaten (kurz vor Test-Array-Ende).
Hier nochmal mein Testaufbau:
Option Explicit
Private Type SAFEARRAY1D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
cElements As Long
lLbound As Long
End Type
Private Declare Function ArrPtr& Lib "msvbvm60" Alias "VarPtr" _
(Ptr() As Any)
Private Declare Sub RtlMoveMemory Lib "kernel32" _
(Dst As Any, Src As Any, ByVal cBytes&)
Friend Function BinComp(ByVal P1&, ByVal P2&, ByVal cB&) As Boolean
Dim i&, Rest&, b1(6) As Byte, b2(6) As Byte
Dim L1@(), saL1 As SAFEARRAY1D, L2@(), saL2 As SAFEARRAY1D
If cB <= 0 Then Exit Function
Rest = cB Mod 8
saL1.cDims = 1: saL1.cbElements = 8: saL1.cElements = (cB \ 8) + 1
saL1.pvData = P1: RtlMoveMemory ByVal ArrPtr(L1), VarPtr(saL1), 4
saL2.cDims = 1: saL2.cbElements = 8: saL2.cElements = (cB \ 8) + 1
saL2.pvData = P2: RtlMoveMemory ByVal ArrPtr(L2), VarPtr(saL2), 4
For i = 0 To UBound(L1) - 8 Step 8
If L1(i + 0) <> L2(i + 0) Then GoTo ex
If L1(i + 1) <> L2(i + 1) Then GoTo ex
If L1(i + 2) <> L2(i + 2) Then GoTo ex
If L1(i + 3) <> L2(i + 3) Then GoTo ex
If L1(i + 4) <> L2(i + 4) Then GoTo ex
If L1(i + 5) <> L2(i + 5) Then GoTo ex
If L1(i + 6) <> L2(i + 6) Then GoTo ex
If L1(i + 7) <> L2(i + 7) Then GoTo ex
Next i
For i = i To UBound(L1) - 1
If L1(i) <> L2(i) Then GoTo ex
Next i
If Rest Then
RtlMoveMemory b1(0), L1(UBound(L1)), Rest
RtlMoveMemory b2(0), L2(UBound(L2)), Rest
For i = 0 To Rest - 1
If b1(i) <> b2(i) Then GoTo ex
Next i
End If
BinComp = True
ex:
RtlMoveMemory ByVal ArrPtr(L1), 0&, 4
RtlMoveMemory ByVal ArrPtr(L2), 0&, 4
End Function
Private Sub Form_Click()
Dim T#, b1() As Byte, b2() As Byte, i&
ReDim b1(15999999): ReDim b2(15999999)
DoEvents
T = Timer
For i = 1 To 10
Print BinComp(VarPtr(b1(0)), VarPtr(b2(0)), 16000000)
'Print compareMemory(VarPtr(b1(0)), VarPtr(b2(0)), 16000000)
Next i
Caption = Timer - T
End Sub
Olaf
>> Ist schon ordentlich schnell, aber leider nicht der *best case*. ;-)
>> Ich hab mir auch irgendwie in den Kopf gesetzt, die Daten ohne
>> Umkopieren zu vergleichen, da das Umkopieren auch in etwa die gleiche
>> Zeit benötigt, wie der Vergleich.
> Mit ein wenig Inline-Kaskadierung hat native VB6-Code nur noch etwa 6%
> Nachteil gegenüber der Assembler-Routine.
Ja, native :-)
Habs eben nochmal ausprobiert, ist dann tatsächlich fast gleich. Aber
auch 6% ist noch einiges, hab ja schon versucht, in meinen C-dlls das
eine oder andere prozent mehr rauszuholen ;-)
Gut, in Wirklichkeit ist das Erbsenzählerei...
> Mein Beispiel kopiert auch keine Daten um - es spannt virtuelle Arrays
> eines 64Bit-Datentyps über die zu analysierenden Bereiche.
Habs mir nochmal genauer angesehen. Sorry, stimmt natürlich. Wußte
nicht, daß man auch Arrays "verbiegen" kann, mit Strings hab ich auch
gute Erfahrungen gemacht, allerdings hat mich der Einwand von Thorsten
Albers mit der Garbage Collection doch etwas abgeschreckt.
Der pure String-Vergleich (mit Verbiegen der String-pointer) scheint
sogar langsamer zu sein als Dein Code.
Hab mir auch mal den vom Compiler erzeugten asm code angesehen: sieht
wie der source code "verschwenderisch" aber dennoch performant aus; es
werden immer mehrere (16) Vergleiche hintereinender ausgeführt, ohne
ein Inkrement zu benutzen. Deine Kaskade wird also "1:2" übernommen
("1:2", weil jeder 64-bit-vergleich in 2 32-bit Vergleiche übersetzt
wird. Dürfte also keinen Unterschied bereiten, wenn du als Elementetyp
Long verwendest.
> BTW - Mit Deiner Routine stimmt irgendwas nicht - sie zerschiesst bei
> mir die Originaldaten (kurz vor Test-Array-Ende).
Stimmt, tut es. Sogar absichtlich ;)
Aber nur ein einziges Byte. (Bereits behoben)
Mein Trick war dafür zu sorgen, daß in dem Datenbereich auf jeden Fall
am Ende ein Unterschied ist. Denn so kann ich mir (die Zeit) sparen, die
Länge oder momentane Position im Speicherblock zu überprüfen - ich suche
einfach bis zum ersten Unterschied.
Damit ist mein ASM Code mit Deiner Kaskade vom Zeitverbrauch her
identisch. Ich benutze eine Registervariable als Zähler, bei Dir wird
stattdessen die Addition (i+ ...) benötigt. Unterschied ist nur noch,
daß bei Dir nach 8 bzw 16 Vergleichen zusätzlich ein Inkrement nötig
ist, was bei mir wegfällt.
Um den Fehler im meinem Code zu korrigieren, muß nur das evtl geänderte
Byte wiederhergestellt werden. Dazu vor dem letzten "EndIf" in der Sub
dies dazusetzen:
If bchanged Then
j = ptr2 + cntA - 4
CopyMemory ByVal j, obyte, 1&
End If
Meine Version hat eben den Vorteil, daß es auch in einem
nicht-nativ-compilat und in der IDE fast genauso schnell funktioniert.
Dafür hat man dann leider weniger kontrolle darüber...
Viele Grüße,
Andreas
> Habs mir nochmal genauer angesehen. Sorry, stimmt natürlich. Wußte
> nicht, daß man auch Arrays "verbiegen" kann, mit Strings hab ich auch
> gute Erfahrungen gemacht, allerdings hat mich der Einwand von Thorsten
> Albers mit der Garbage Collection doch etwas abgeschreckt.
Die greift meines Wissens nur bei Strings und sitzt eher in Windows selbst
(OLE-BSTR-Management) als in der VB-Runtime.
> Der pure String-Vergleich (mit Verbiegen der String-pointer) scheint
> sogar langsamer zu sein als Dein Code.
Gut möglich.
> Hab mir auch mal den vom Compiler erzeugten asm code angesehen: sieht
> wie der source code "verschwenderisch" aber dennoch performant aus; es
> werden immer mehrere (16) Vergleiche hintereinender ausgeführt, ohne
> ein Inkrement zu benutzen. Deine Kaskade wird also "1:2" übernommen
> ("1:2", weil jeder 64-bit-vergleich in 2 32-bit Vergleiche übersetzt
> wird. Dürfte also keinen Unterschied bereiten, wenn du als Elementetyp
> Long verwendest.
Ok, dann wäre der VB-Source noch "verschwenderischer" ausgefallen ;-).
>> BTW - Mit Deiner Routine stimmt irgendwas nicht - sie zerschiesst bei
>> mir die Originaldaten (kurz vor Test-Array-Ende).
>
> Stimmt, tut es. Sogar absichtlich ;)
> Aber nur ein einziges Byte. (Bereits behoben)
> Mein Trick war dafür zu sorgen, daß in dem Datenbereich auf jeden Fall
> am Ende ein Unterschied ist.
Hab ich mir schon gedacht.
> Um den Fehler im meinem Code zu korrigieren, muß nur das evtl geänderte
> Byte wiederhergestellt werden. Dazu vor dem letzten "EndIf" in der Sub
> dies dazusetzen:
>
> If bchanged Then
> j = ptr2 + cntA - 4
> CopyMemory ByVal j, obyte, 1&
> End If
Der Reset ist schon wichtig - falls z.B. jemand zwecks schnellen
Dateivergleichs direkt gegen MemoryMappedFile-Pointer arbeiten sollte,
bestünde Gefahr, dass die Platte geschreddert wird.
> Meine Version hat eben den Vorteil, daß es auch in einem
> nicht-nativ-compilat und in der IDE fast genauso schnell funktioniert.
> Dafür hat man dann leider weniger kontrolle darüber...
Bzgl. dieses Themas hab' ich mit Thorsten ja schon einige Argumente
getauscht...
Olaf
Ich kann mir nicht vorstellen, daß StrPtr() ein Sperren des jeweiligen
Speichers bewirkt, denn eine derartige Sperrung würde auch das Entsperren
(GlobalUnlock() etc.) des jeweiligen Speicherbereiches erfordern - und wann
sollte das passieren? Da VB offenbar keinerlei Tracking durchführt zwischen
dem Speicherbereich und der Variablen, welcher der Zeiger zugewiesen wird,
kann dieses Entsperren auch nicht anhand dieser Variable erfolgen, z.B.
wenn sie ungültig wird oder einen neuen Wert erhält.
Beim Zugriff auf Speicher über die entspr. VB-Variablen-Verwaltung dagegen
wird der Speicherbereich sicherlich von VB intern ge- und entsperrt.
> Wenn jedoch das Verbiegen und der Datenvergleich direkt aufeinander
> folgen, sehe ich keine Möglichkeit, daß hier eine G.C. eingreifen
> könnte. Man muß nur sofort wieder "zurückbiegen", und darf das nicht so
> stehen lassen.
Wie ich schon einmal sagte: Ich würde lieber keine Annahmen darüber machen,
wann, wo und wie lange ein Zeiger, der über StrPtr() oder VarPtr()
ermittelt wurde, gültig ist...
Beachte auch, daß alles, was ich hier von "Garbage Collection" gesagt habe
und sage, reine Mutmaßung ist - ich habe in der VB-Doku und zusätzlichem
Material keinerlei Informationen über die Speicherverwaltung gefunden!
> Ist schnell, aber eben nicht optimal.
> Ich habs jetzt mit Assembler in VB versucht, funktioniert bei mir
> einwandfrei.
> Würde mich interessieren, ob's auch in VB5 geht.
Werde es mir einmal anschauen und ggfs. in dem entsprechenden Thread
("schneller String/Datei-vergleich, was meint Ihr?") antworten.
> Wie werden Strings in VB intern gespeichert/organisiert?
das lässt sich am besten anhand eines kleinen Beispiels zeigen. Sorry, die
Antwort ist etwas länger geworden.
Angenommen, Du hast den folgenden Code:
Private Sub Form_Load()
Dim a As Long, b As Long, c As String
c = "Hallo Welt"
a = StrPtr(c)
b = VarPtr(c)
End Sub
Wenn Du das nativ kompilierst, ergibt sich der folgende Assembler-Code:
-----
19: Private Sub Form_Load()
004018F0 push ebp
004018F1 mov ebp,esp
004018F3 sub esp,0Ch
004018F6 push offset ___vbaExceptHandler (004010b6)
004018FB mov eax,fs:[00000000]
00401901 push eax
00401902 mov dword ptr fs:[0],esp
00401909 sub esp,14h
0040190C push ebx
0040190D push esi
0040190E push edi
0040190F mov dword ptr [ebp-0Ch],esp
00401912 mov dword ptr [ebp-8],offset __imp____vbaFreeStr+28h
(004010a0)
00401919 mov eax,dword ptr [Me]
0040191C mov ecx,eax
0040191E and ecx,1
00401921 mov dword ptr [ebp-4],ecx
00401924 and al,0FEh
00401926 push eax
00401927 mov dword ptr [Me],eax
0040192A mov edx,dword ptr [eax]
0040192C call dword ptr [edx+4]
0040192F xor esi,esi
-----
Dies ist nur der allgemeine Prolog, den VB für jede Routine eines Objektes
erstellt. Soweit nichts besonderes.
-----
20: Dim a As Long, b As Long, c As String
21: c = "Hallo Welt"
00401931 mov edx,offset ___vba@001F95A4 (004015e4)
00401936 lea ecx,[c]
00401939 mov dword ptr [c],esi
0040193C call dword ptr [__imp____vbaStrCopy (00401058)]
-----
Hier wird der String "Hallo Welt" kopiert und ein Zeiger auf die Daten der
Kopie in der lokalen Variablen c gespeichert.
-----
22: a = StrPtr(c)
00401942 mov eax,dword ptr [c]
00401945 push eax
00401946 call dword ptr [__imp____vba@001FACCC (00401048)]
-----
Jetzt wird es interessant: der Inhalt von c (= der Zeiger auf die Daten des
kopierten Strings) wird in das eax-Register eingelesen. Dann wird eine
Routine der Laufzeitbibliothek (StrPtr ???) mit eben diesem Wert als
Parameter aufgerufen.
Was ist jetzt der Unterschied zwischen VarPtr und StrPtr? Sehen wir uns die
Zeile "b = VarPtr(c)" einmal an:
-----
23: b = VarPtr(c)
0040194C lea ecx,[c]
0040194F push ecx
00401950 call dword ptr [__imp____vba@001FACCC (00401048)]
-----
Diesmal wird die Adresse von c auf dem Stack gespeichert und der
Laufzeitbibliothek übergeben. __imp____vba@001FACCC ist hier also
gleichbedeutend mit VarPtr. Dieselbe Adresse wurde weiter oben aber schon
für StrPtr benutzt (StrPtr liegt auch an Adresse 0x00401048), StrPtr und
VarPtr sind in ihrem Assembler-Code also identisch! Was macht VarPtr/StrPtr
jetzt eigentlich? Ganz einfach:
-----
VarPtr:
660E2C86 mov eax,dword ptr [esp+4]
660E2C8A ret 4
-----
VarPtr gibt einfach nur den Wert zurück, der für den ersten Parameter
übergeben wurde. Ganz egal, was das genau ist. Für den Prozessor ist es nur
ein 32-Bit-Wert.
Wie kann das sein? Warum kann dieselbe Funktion __imp____vba@001FACCC sowohl
für VarPtr als auch für StrPtr (und, nebenbei gesagt, auch für ObjPtr)
eingesetzt werden? Der Grund liegt in der Art und weise, wie die OLE-Runtime
Strings behandelt.
Die Länge eines Strings steht nicht fest, wenn die Variable dimensioniert
wird. Wenn Du schreibst "Dim c As String" ahnt zu der Zeit niemand, wie
viele Zeichen in c enthalten sein werden (Strings fester Länge ala "As
String * 20" einmal ausgenommen). c selber muss aber eine bestimmte, genau
festgelegte Größe haben, damit es bei Konstrukten wie "Dim a As Long, c As
String, d As String, e As Long [...]" keine Probleme gibt. Alle Variablen
liegen hintereinander im Speicher. Wenn jetzt c und d unterschiedliche
Längen hätten, je nach ihrem Inhalt, wäre der Zugriff auf e äußerst
problematisch: die Laufzeitbibliothek müsste zuerst die Länge der Strings c
und d ermitteln, und daraus dann die Adresse von e zusammenbasteln. Das ist
nicht praktikabel, weshalb man Strings anders behandelt.
c und d speichern in Wirklichkeit nur die Adresse der Daten des Strings. Die
Länge dieses Zeigers ist 32 Bit. Damit ist auch die Adresse der
nachfolgenden Variablen der Funktion genau bekannt.
Zurück zu VarPtr und co. VarPtr gibt die Adresse einer Variablen zurück.
Wenn Du jetzt den "Wert" "VarPtr(c)" benutzt, übergibt VB die Adresse von c
an die Laufzeitbibliothek, die diese unverändert zurückgibt. VarPtr(c) gibt
also, wenn c ein String ist, die Adresse der String-Variablen zurück. Wenn
der String gültige Daten enthält, befindet sich an dieser Stelle ein Zeiger
auf eben diese Daten.
Wenn Du den Zeiger direkt haben möchtest, rufst Du StrPtr auf. Dann übergibt
VB automatisch den *Inhalt* der Variablen c an die Laufzeitbibliothek.
Dieser Inhalt ist die Adresse der String-Daten.
Zum Schluss noch etwas zum Thema Garbage Collection (wobei ich ehrlich
gesagt nicht ganz begriffen habe, wie ihr darauf gekommen seid): .NET hat
sie, JAVA (soweit ich weiß) auch, der Windows-NT-Kernel (eingeschränkt)
auch, VB selber garantiert nicht.
Wie man am obigen Codefragment sehen kann, führt hier niemand Buch darüber,
welche Speicherbereiche reserviert sind und welche nicht. Das kopieren des
Strings (Aufruf von __vbaCopyString) tut auch nichts derartiges, wie ein
Blick in den entsprechenden Assemblercode zeigt. Trotzdem muss der String,
den __vbaCopyString erzeugt hat, irgendwann freigegeben werden. Das ist im
Epilog der Funktion enthalten, der so aussieht:
-----
24: End Sub
00401956 mov dword ptr [ebp-4],esi
$L26:
00401959 push offset $L46 (00401968)
$L25:
0040195E lea ecx,[c]
00401961 call dword ptr [__imp_@__vbaFreeStr (00401078)]
$L44:
00401967 ret
$L46:
00401968 [...]
-----
Zuerst wird [ebp-4] auf 0 gesetzt. Das ist nur ein Hinweis an
__vbaExceptHandler, dass nun kein Basic-Code mehr ausgeführt wird.
Anschließend wird ein Konstrukt ausgeführt, dass den C(++)-lern als
finally-Handler bekannt ist. Etwas Code dazu (natürlich in C ;-):
__try {
// some stuff that can raise
// more stuff that can raise
// even more stuff...
} __finally {
// cleanup
}
Dabei wird zuerst der Code im __try-Block ausgeführt. Wenn der Code im
__try-Block einen Fehler auslöst, wird seine Ausführung abgebrochen und der
__finally-Block wird ausgeführt. Tritt kein Fehler auf, wird zuletzt
ebenfalls der __finally-Block ausgeführt. Die Ausführung des Codes im
__finally-Block ist garantiert, egal, ob (und wo!) im __try-Block ein Fehler
erzeugt wird. Dies ist eine einfache Art und Weise, Aufräumarbeiten
auszuführen.
VB benutzt das gleiche Feature des Betriebssystems, um lokale Variablen
aufzuräumen. Die obige Funktion erzeugt einen String, der irgendwann wieder
freigegeben werden muss. Etwas ähnliches lässt sich beobachten, wenn man
Objektvariablen einsetzt, nur dass dann natürlich ein Aufruf von
IUnknown::Release im __finally-Handler steht.
In diesem Beispiel jedenfalls wird immer, bevor die Routine verlassen wird,
__vbaFreeStr ausgeführt. Diese Routine gibt die Daten des Strings c frei,
wenn ein entsprechender Zeiger in der Variablen enthalten ist. Mit einer
Garbage Collection hat das meiner Meinung nach nichts zu tun. Das ist nur
die übliche Aufräumarbeit, die jeder C-Programmierer selbst machen muss, nur
dass VB es uns abnimmt.
Ich hoffe, ich konnte wenigstens etwas Licht in diese Geschichte bringen.
Viele Grüße,
Jan-Arne Sobania
> In diesem Beispiel jedenfalls wird immer, bevor die Routine verlassen
> wird, __vbaFreeStr ausgeführt. Diese Routine gibt die Daten des Strings c
> frei, wenn ein entsprechender Zeiger in der Variablen enthalten ist. Mit
> einer Garbage Collection hat das meiner Meinung nach nichts zu tun.
Innerhalb von ClassicVB gibt es natürlich keinen GC.
Was ich meinte, ist ein GC-ähnliches Konstrukt (BSTR-Cache) innerhalb der
OLE-Runtime.
Hier ein Auszug aus der MSDN:
"For example, if the application allocates a BSTR and frees it, the free
block of memory is put into the BSTR cache by Automation. If the application
then allocates another BSTR, it can get the free block from the cache. If
the second BSTR allocation is not freed, IMallocSpy will attribute the leak
to the first allocation of the BSTR. You can determine the correct source of
the leak (the second allocation) by disabling the BSTR caching using the
debug version of Oleaut32.dll, and by setting the environment variable
OANOCACHE=1 before running the application."
Olaf
> Was ich meinte, ist ein GC-ähnliches Konstrukt (BSTR-Cache) innerhalb der
> OLE-Runtime.
danke, das kannte ich noch nicht. Man lernt halt auch nach Jahren
VB-Programmierung immer noch etwas dazu ;-).
Für mich sieht das ganze aber trotzdem nicht wie eine GC (oder etwas
GC-ähnliches) aus, sorry. Aus dem Zitat lese ich, dass es sich um eine Art
Lookaside List für BSTRs handelt. Möchte eine Anwendung einen BSTR erzeugen,
schaut die Runtime zuerst in der Liste nach. Wenn ein entsprechender Block
dort vorkommt, bekommt die Anwendung diesen zugewiesen, wenn nicht, wird
neuer Speicher vom System angefordert. Gibt eine Anwendung nun Speicher
frei, wird dieser nicht sofort an das System zurückgegeben, sondern in der
Lookaside List für spätere Reservierungen zwischengeparkt. Im wesentlichen
also nur ein (zusätzlicher) Heap-Manager zur Beschleunigung von Anwendungen,
die häufig BSTRs einsetzen.
Eine GC ist etwas anderes: sie überwacht die Verwendung von Objekten, um
Memory Leaks zu vermeiden. Wenn ein Objekt nicht mehr in Benutzung ist
(etwa, weil das Programm "vergessen" hat, den letzten Verweis zu
dereferenzieren), wird es von der Runtime automatisch freigegeben, *während*
die Anwendung weiterläuft. Eine Anwendung kann also viele Objekte erzeugen
und ziemlich frei mit ihnen herumspielen, ohne Gefahr zu laufen, dass
vergessene Verweise die berüchtigten Memory Leaks erzeugen.
Das Konzept, freigegebenen Speicher nicht direkt an das System
zurückzugeben, ist nicht neu. Selbst die (Debug-)Runtime von VC 6 enthält
einen eingebauten Leak Finder. Dieser kann aber die Verwendung der Objekte
nicht verfolgen. Er kann nur, wenn das Programm beendet wird, die Liste der
reservierten Blöcke durchgehen und aufzeigen, welche nicht freigegeben
wurden. Ich denke einmal, dass ist wirklich nicht das, was man als eine
Garbage Collection versteht ;-)).
Viele Grüße,
Jan-Arne Sobania
Irgendwo in einer Antwort an Andreas habe ich jüngst schon geschrieben, daß
alle Annahmen bezüglich eine GC in VB >reine Annahmen< sind, da ich
zumindest keinerlei Informationen von MS über das Speichermanagment von VB
gefunden habe. Bei Gelegenheit werde ich mir einmal Dein Ausgangs-Posting
in diesem Teil-Thread zu Gemüte führen, da darin die ersten Informationen
dazu enthalten sind, die ich gesehen habe.
BTW: Andere, in diesem Fall Bruce McKinney in "Hardcore Visual Basic",
sehen auch in VB eine GC - alles eine Frage der Definition von GC, die m.W.
nirgendwo eindeutig festgeschrieben ist.
"The C++ new operator allocates storage for a class in much the same way
that New does in Visual Basic or new does in Java. The difference is that
you have to use the delete operator to get rid of that storage in C++.
Visual Basic and Java do the cleanup (sometimes called garbage collection)
for you."
Aber das ist nebensächlich...
>> Was ich meinte, ist ein GC-ähnliches Konstrukt (BSTR-Cache) innerhalb der
>> OLE-Runtime.
> danke, das kannte ich noch nicht. Man lernt halt auch nach Jahren
> VB-Programmierung immer noch etwas dazu ;-).
>
> Für mich sieht das ganze aber trotzdem nicht wie eine GC (oder etwas
> GC-ähnliches) aus, sorry.
Klar, gemeinhin versteht man unter einem GC das, was Du weiter schreibst
(Identifizierung nicht mehr referenzierter Objekte usw. usf).
Allgemein gesehen jedoch identifiziert/verwaltet ein Garbage-Collector
(Speicher-)Müll und ist verantwortlich dafür, diesen dem System nach
erfolgter Freigabe (von Objekten, Strings, oder anderen Strukturen) erneut
zuzuführen.
Charakteristisch ist wohl die indirekte Speicherfreigabe, die durch eine
Zwischeninstanz erfolgt - also zu einem Zeitpunkt, den der Entwickler nicht
genau bestimmen kann.
All diese Merkmale treffen auch auf den BSTR-Cache zu, weshalb ich ihn als
"GC-ähnliches Konstrukt" bezeichnet habe.
Olaf
"Thorsten Albers" <albe...@SPAMuni-freiburg.de> schrieb:
> sehen auch in VB eine GC - alles eine Frage der Definition von GC,
> die m.W. nirgendwo eindeutig festgeschrieben ist.
"Müll einsammeln"
;-)
Grüsse,
Herfried K. Wagner
--
http://www.mvps.org/dotnet
Reden wir jetzt auch in diesem Thread von MAC-Kunden? :-)
>> Wie werden Strings in VB intern gespeichert/organisiert?
> das lässt sich am besten anhand eines kleinen Beispiels zeigen.
> Sorry, die Antwort ist etwas länger geworden.
Kein problem ;-)
Vielen Dank jedenfalls für Deine Ausführungen!
> Was ist jetzt der Unterschied zwischen VarPtr und StrPtr? Sehen wir
> uns die Zeile "b = VarPtr(c)" einmal an:
Das ist mir auch schon aufgefallen. Der einzige, der hier nen
Unterschied kennt ist wohl der Compiler.
Aber letztlich muß ja eh nur [esp+4] zurückgeliefert werden, da der
Linker dafür sorgt, daß varPtr eine Referenz/Pointer auf eine Variable
übergeben wird, und StrPtr der Wert an der Stelle, auf die der Pointer
zeigt... Soweit waren mir die Zusammenhänge schon klar. Was ich nicht
wußte, war, wo die Länge des Strings gespeichert wurde, bzw., daß es
sich um einen BSTR handelte.
> Zum Schluss noch etwas zum Thema Garbage Collection (wobei ich ehrlich
> gesagt nicht ganz begriffen habe, wie ihr darauf gekommen seid): .NET
> hat sie, JAVA (soweit ich weiß) auch, der Windows-NT-Kernel
> (eingeschränkt) auch, VB selber garantiert nicht.
Thorsten Albers hat mich zum ersten mal mit diesem Thema konfrontiert,
und auf Warnungen reagiere ich erstmal sehr interessiert :)
> Wie man am obigen Codefragment sehen kann, führt hier niemand Buch
> darüber, welche Speicherbereiche reserviert sind und welche nicht.
> Das kopieren des Strings (Aufruf von __vbaCopyString) tut auch nichts
> derartiges, wie ein Blick in den entsprechenden Assemblercode zeigt.
> Trotzdem muss der String, den __vbaCopyString erzeugt hat, irgendwann
> freigegeben werden. Das ist im Epilog der Funktion enthalten, der so
> aussieht:
Das war auch das, was ich fälschlicherweise als locken/Freigeben der
Speicherbereiche interpretiert habe. Mir fiel auf, daß oft in Folge
eines __vbaFreeVarList auch __vbaFreeStr aufgerufen wurde. Habe das aber
auch nicht näher untersucht...
> VB benutzt das gleiche Feature des Betriebssystems, um lokale
> Variablen aufzuräumen. Die obige Funktion erzeugt einen String, der
> irgendwann wieder freigegeben werden muss. Etwas ähnliches lässt sich
> beobachten, wenn man Objektvariablen einsetzt, nur dass dann natürlich
> ein Aufruf von IUnknown::Release im __finally-Handler steht.
> In diesem Beispiel jedenfalls wird immer, bevor die Routine verlassen
> wird, __vbaFreeStr ausgeführt. Diese Routine gibt die Daten des
> Strings c frei, wenn ein entsprechender Zeiger in der Variablen
> enthalten ist. Mit einer Garbage Collection hat das meiner Meinung
> nach nichts zu tun. Das ist nur die übliche Aufräumarbeit, die jeder
> C-Programmierer selbst machen muss, nur dass VB es uns abnimmt.
Trotzdem eine Frage:
Ein Programm, welches häufig neue (größere) Strings erzeugt und in loser
Folge wieder freigibt, läßt den Speicherbereich irgendwann wie einen
Schwamm aussehen: Es gibt zwar reichlich Platz, aber durch viele kleine
Strings derart unterbrochen, daß kein größerer Block als ganzes mehr
alloziiert werden kann. Jetzt sollte aufgeräumt werden. D.h., all diese
Strings aneinanderketten, um "weiter hinten" einen größeren freien Block
zu schaffen. Dazu gibt es in Asm ja die Möglichkeit, direkt ganze
Speicherblöcke zu verschieben, und bei GlobalAlloc und Co ein Flag, das
dieses Verschieben entweder erlaubt oder verbietet. Für ein normales
VB-programm wäre das ja auch ganz ungefährlich, da ja nur der Pointer
auf die eigentlichen Stringdaten geändert werden müßte. Und dieser wird
ja den Funktionen nicht übergeben, sondern nur der Pointer auf diesen
Pointer. Ist es nun auszuschließen, daß z.b. infolge einer
Redim-Anweisung oder einer Stringzuweisung eine solche
Speicher-Verschiebung stattfindet?
Die eigentliche Frage ist, welche Anweisungen zwischen dem Aufruf von
StrPtr und dem Zugriff auf den zurückgelieferten Speicherbereich
stattfinden dürfen und welche nicht, falls es passieren kann, daß sich
der speicherbereich ändert.
Viele Grüße,
Andreas
> Für mich sieht das ganze aber trotzdem nicht wie eine GC (oder etwas
> GC-ähnliches) aus, sorry. Aus dem Zitat lese ich, dass es sich um eine
> Art Lookaside List für BSTRs handelt. Möchte eine Anwendung einen
> BSTR erzeugen, schaut die Runtime zuerst in der Liste nach. Wenn ein
> entsprechender Block dort vorkommt, bekommt die Anwendung diesen
> zugewiesen, wenn nicht, wird neuer Speicher vom System angefordert.
> Gibt eine Anwendung nun Speicher frei, wird dieser nicht sofort an
> das System zurückgegeben, sondern in der Lookaside List für spätere
> Reservierungen zwischengeparkt. Im wesentlichen also nur ein
> (zusätzlicher) Heap-Manager zur Beschleunigung von Anwendungen, die
> häufig BSTRs einsetzen.
Der durchaus auch Speicherblöcke "verschieben" könnte, um möglichst viel
zusammenhängenden freien Speicher zu reservieren? hab schon mit meinem
Disassembler nach sowas gesucht, aber hierfür sind meine Kenntnisse
nicht ausreichend.
> Eine GC ist etwas anderes: sie überwacht die Verwendung von Objekten,
> um Memory Leaks zu vermeiden. Wenn ein Objekt nicht mehr in Benutzung
> ist (etwa, weil das Programm "vergessen" hat, den letzten Verweis zu
> dereferenzieren), wird es von der Runtime automatisch freigegeben,
> *während* die Anwendung weiterläuft. Eine Anwendung kann also viele
> Objekte erzeugen und ziemlich frei mit ihnen herumspielen, ohne
> Gefahr zu laufen, dass vergessene Verweise die berüchtigten Memory
> Leaks erzeugen.
Also z.b. innerhalb einer Funktion ein Objekt instanzieren, aber nicht
wieder freizugeben? Z.B. mit "As New"?
Ich hatte vor einiger Zeit heftige probleme mit solchen Objekten.
Seitedem ich sie immer wieder freigebe, sind die Probleme nie mahr
aufgetreten. Beispiel:
Private Sub Test()
Dim obj as cTestObj
set obj = New ctestObj
...
End Sub
Normalerweise (laut VB-Doku) sollte obj automatisch am Ende der Sub
freigegeben (*) werden. Das ist bei mir nicht immer geschehen (es gab
auch keine weiteren oder zyklischen referenzen auf das objekt.) Scheint
also mit meiner GC etwas nicht gestimmt zu haben.
(*) Nach Deiner Erklärung heißt "Freigeben" zwar, daß das Objekt
offiziell nicht mehr existiert, es kann aber durchaus noch für eine evtl
spätere Verwendung im Speicher verbleiben?
Viele Grüße,
Andreas
>>> BTW - Mit Deiner Routine stimmt irgendwas nicht - sie zerschiesst
>>> bei mir die Originaldaten (kurz vor Test-Array-Ende).
>> Mein Trick war dafür zu sorgen, daß in dem Datenbereich auf jeden
>> Fall am Ende ein Unterschied ist.
> Der Reset ist schon wichtig - falls z.B. jemand zwecks schnellen
> Dateivergleichs direkt gegen MemoryMappedFile-Pointer arbeiten sollte,
> bestünde Gefahr, dass die Platte geschreddert wird.
Aus welchem Grund könnte das passieren?
Ich würde doch höchtens den Inhalt des Files verändern, oder nicht?
Viele Grüße,
Andreas
> Ich kann mir nicht vorstellen, daß StrPtr() ein Sperren des jeweiligen
> Speichers bewirkt, denn eine derartige Sperrung würde auch das
> Entsperren (GlobalUnlock() etc.) des jeweiligen Speicherbereiches
> erfordern - und wann sollte das passieren?
Im Compilat wird oft __vbaFreeVarList und __vbaFreeStr aufgerufen. Ich
habe das mißinterpretiert, tatsächlich findet keine Sperrung des
Speichers statt.
> Beim Zugriff auf Speicher über die entspr. VB-Variablen-Verwaltung
> dagegen wird der Speicherbereich sicherlich von VB intern ge- und
> entsperrt.
Das ist auch nicht der Fall. Zumindest nicht von VB. Wenn eine Funktion
auf die Stringdaten zugreift, kommen scheinbar direkt die
BSTR-Funktionen zum Einsatz. Diese können natürlich durchaus hier und da
etwas verschieben.
>> Wenn jedoch das Verbiegen und der Datenvergleich direkt aufeinander
>> folgen, sehe ich keine Möglichkeit, daß hier eine G.C. eingreifen
>> könnte. Man muß nur sofort wieder "zurückbiegen", und darf das nicht
>> so stehen lassen.
> Wie ich schon einmal sagte: Ich würde lieber keine Annahmen darüber
> machen, wann, wo und wie lange ein Zeiger, der über StrPtr() oder
> VarPtr() ermittelt wurde, gültig ist...
VB ist nicht Threadsafe. Und aus diesem Grund kann eine Verschiebung des
Speichers ganz sicher NICHT von einem anderen/zweiten Thread aus
erfolgen, sondern nur aus dem aktuellen Thread heraus (z.B. bei
Funktionsaufrufen, Variablenzugriffen, etc), oder wenn dieser IDLE ist.
Und wenn zwischen StrPtr und dem Ende meiner Funktion keine
BSTR-Funktion aufgerufen wird, hat die BSTR-Verwaltung (oder wie man das
nennt) keine Chance.
Viele Grüße,
Andreas
> > Der Reset ist schon wichtig - falls z.B. jemand zwecks schnellen
> > Dateivergleichs direkt gegen MemoryMappedFile-Pointer arbeiten sollte,
> > bestünde Gefahr, dass die Platte geschreddert wird.
>
> Aus welchem Grund könnte das passieren?
> Ich würde doch höchtens den Inhalt des Files verändern, oder nicht?
ja, der Inhalt der Dateien kann verändert werden. Stell Dir vor, Du wolltest
nach diesem Muster (Memory Mapping + Binärvergleich *mit* Veränderung der
Daten) zwei EXE-Dateien vergleichen. Oder zwei (sehr große) Dateien, die
nicht gleichzeitig und vollständig per Mapping eingeblendet werden können.
Du würdest die Dateien abschnittsweise einblenden und vergleichen. Jeder
Vergleich ändert etwas an den Daten, das System schreibt die Änderungen auf
die Platte zurück.
Um das zu verstehen, musst man wissen, wie File Mapping arbeitet. Wenn
Memory Mapped Files benutzt werden, wird in Wirklichkeit ein Teil des
System-Cache in den Adressraum einer Anwendung eingeblendet. Dies erlaubt es
dem Dateisystem, dass Anwendungen sowohl die normalen API-Funktionen
(ReadFile, WriteFile) als auch Memory Mapped Files benutzen können, und
trotzdem alle beteiligten immer eine konsistente Sicht der aktuellsten Daten
bekommen.
Wenn Du die Seiten mit Schreibzugriff einblendest - und das musst Du machen,
wenn Du sie in-place verändern willst - überwacht das Betriebssystem auch,
ob sie verändert werden. Beim ersten Schreibzugriff wird ein entsprechendes
Flag in der Speicherverwaltung gesetzt, und die Seite in die Änderungsliste
aufgenommen. Bis dahin ist noch nichts passiert; allerdings kommt irgendwann
der Zeitpunkt, an dem ein System-Thread namens Mapped Page Writer aktiv
wird. Dieser durchsucht die Änderungsliste und schreibt veränderte Seiten
auf den Datenträger zurück.
Aus diesem Grunde solltest Du einen solchen Vergleich *niemals* mit dem
obigen Algorithmus implementieren. Du läufst damit Gefahr, *jede* Datei zu
zerstören, die irgendwie einmal so "behandelt" wurde. Ein zurückschreiben
der ursprünglichen Daten hilft hier überhaupt nicht. Stell Dir vor, der
Mapped Page Writer schreibt Deine Daten vor dem zurücktauschen auf die
Platte, und dann stolpert jemand über die Stromversorgung. Ein
zurückschreiben ist nicht mehr möglich, und nach dem Neustart musst Du mit
demolierten Dateien leben, wenn das System überhaupt noch startet;
schließlich könnte ja gerade jemand auf die Idee gekommen sein, die
installierte Kernel-Datei mit einer älteren zu vergleichen...
Viele Grüße,
Jan-Arne
> VB ist nicht Threadsafe.
Das GUI soweit ich weiß nicht, Du kannst aber mit ActiveX-EXEs so etwas
erreichen.
> Und aus diesem Grund kann eine Verschiebung des
> Speichers ganz sicher NICHT von einem anderen/zweiten Thread aus
> erfolgen, sondern nur aus dem aktuellen Thread heraus (z.B. bei
> Funktionsaufrufen, Variablenzugriffen, etc), oder wenn dieser IDLE ist.
Hat mit Threadsafe absolut nichts zu tun. Strings sind, im Gegensatz zu
.NET, keine Objekte. Nur der aktuelle Thread kennt die Adresse der Daten,
nur er kann etwas verändern. Ein anderer Thread bekommt immer nur eine
Kopie.
Nach jeder Veränderung des Strings ist der alte Rückgabewert von StrPtr
unbrauchbar, das ist klar. Du kannst aber gefahrlos den String an andere
Funktionen übergeben (natürlich nur ByVal !!!), oder mit den üblichen
String-Funktionen *lesend* auf seine Daten zugreifen (Left, Right, a =
mid(s, ...) usw.). Ich glaube aber nicht, dass ein String verändert werden
kann, wenn ein Thread im Leerlauf ist. Die Runtime wird niemals einen String
im Speicher verschieben, ohne dass der Thread, dem er gehört, ihn verändert
hat.
> Und wenn zwischen StrPtr und dem Ende meiner Funktion keine
> BSTR-Funktion aufgerufen wird, hat die BSTR-Verwaltung (oder wie man das
> nennt) keine Chance.
Noch besser: keine BSTR-Funktion, die den String *verändert*. Left, Right,
Len usw. sind völlig ungefährlich.
Viele Grüße,
Jan-Arne
> Der durchaus auch Speicherblöcke "verschieben" könnte, um möglichst viel
> zusammenhängenden freien Speicher zu reservieren? hab schon mit meinem
> Disassembler nach sowas gesucht, aber hierfür sind meine Kenntnisse
> nicht ausreichend.
nein, eine Verschiebung kann nicht stattfinden, weil es keine Möglichkeit
gibt, den Anwendungen die neue Position des Strings mitzuteilen. Strings
sind einfach nur Zeiger; die Runtime weiß nicht, wie viele Zeiger auf einen
Speicherbereich es gibt, geschweige denn wo diese im Speicher liegen.
> > Eine GC ist etwas anderes: sie überwacht die Verwendung von Objekten,
> > um Memory Leaks zu vermeiden. Wenn ein Objekt nicht mehr in Benutzung
> > ist (etwa, weil das Programm "vergessen" hat, den letzten Verweis zu
> > dereferenzieren), wird es von der Runtime automatisch freigegeben,
> > *während* die Anwendung weiterläuft. Eine Anwendung kann also viele
> > Objekte erzeugen und ziemlich frei mit ihnen herumspielen, ohne
> > Gefahr zu laufen, dass vergessene Verweise die berüchtigten Memory
> > Leaks erzeugen.
>
> Also z.b. innerhalb einer Funktion ein Objekt instanzieren, aber nicht
> wieder freizugeben? Z.B. mit "As New"?
Würde ich immer noch nicht als GC bezeichnen, weil die aufrufende Routine
das Objekt ja explizit freigibt (ob mit Set x = Nothing oder bei End
Sub/Function/Property, ist ja egal). Unter einer GC stelle ich mir dann eher
so etwas vor, dass nachschaut, ob irgendwelche Objekte, die zu beliebiger
Zeit einmal erzeugt wurden, noch gebraucht werden. So eine GC müsste einmal
die Position der Verweis-Variablen im Speicher kennen, damit sie erkennt, ob
diese überhaupt noch gültig sind. Sie könnte zum Beispiel feststellen, dass
eine Routine verlassen wurde, ohne das Objekt (egal, ob Set x = Nothing oder
End Sub) freigegeben zu haben (Achtung: sehr konstruiertes Beispiel):
Sub SomeThreadProc
Dim x As Object
Set x = New ...
ExitThread
End Sub
Die GC könnte dann feststellen, dass das Objekt noch existiert, obwohl der
Verweis nicht mehr benötigt wird, und es dann zerstören.
> (*) Nach Deiner Erklärung heißt "Freigeben" zwar, daß das Objekt
> offiziell nicht mehr existiert, es kann aber durchaus noch für eine evtl
> spätere Verwendung im Speicher verbleiben?
Mit "freigeben" meine ich eigentlich den Aufruf von IUnknown::Release. Das
Objekt zerstört sich automatisch, sobald der Referenzzähler auf 0 fällt.
Viele Grüße,
Jan-Arne
> Trotzdem eine Frage:
>
> Ein Programm, welches häufig neue (größere) Strings erzeugt und in loser
> Folge wieder freigibt, läßt den Speicherbereich irgendwann wie einen
> Schwamm aussehen: Es gibt zwar reichlich Platz, aber durch viele kleine
> Strings derart unterbrochen, daß kein größerer Block als ganzes mehr
> alloziiert werden kann. Jetzt sollte aufgeräumt werden. D.h., all diese
> Strings aneinanderketten, um "weiter hinten" einen größeren freien Block
> zu schaffen.
die Runtime macht das nicht und kann das auch nicht.
> Dazu gibt es in Asm ja die Möglichkeit, direkt ganze
> Speicherblöcke zu verschieben, und bei GlobalAlloc und Co ein Flag, das
> dieses Verschieben entweder erlaubt oder verbietet.
Ich meine einmal gelesen zu haben, dass Win32 prinzipiell keine
Speicherblöcke verschiebt, auch wenn das entsprechende Flag benutzt wird.
Das scheint es nur zur Kompatibilität mit Win16 zu geben. Ich kann mich aber
auch täuschen...
> Für ein normales
> VB-programm wäre das ja auch ganz ungefährlich, da ja nur der Pointer
> auf die eigentlichen Stringdaten geändert werden müßte.
Und die Runtime weiß nicht, wo im Speicher dieser Pointer steht, oder (für
nicht-VB-Programme), wie viele Pointer auf denselben Speicherbereich die
Anwendung benutzt...
> Und dieser wird
> ja den Funktionen nicht übergeben, sondern nur der Pointer auf diesen
> Pointer.
Hängt von der Funktion ab. Wenn diese mit ByVal (in C: BSTR) aufgerufen
wird, erhält sie eine Kopie des Strings.
> Ist es nun auszuschließen, daß z.b. infolge einer
> Redim-Anweisung oder einer Stringzuweisung eine solche
> Speicher-Verschiebung stattfindet?
Nein, denn das entspräche dem erzeugen eines neuen Strings. Die alten Daten
werden kopiert, die neuen angehängt, und der alte String wird zerstört. Im
Vergleich mit GlobalAlloc entspricht jedes Redim und jede Zuweisung einem
GlobalReAlloc, das den alten Zeiger ungültig macht.
> Die eigentliche Frage ist, welche Anweisungen zwischen dem Aufruf von
> StrPtr und dem Zugriff auf den zurückgelieferten Speicherbereich
> stattfinden dürfen und welche nicht, falls es passieren kann, daß sich
> der speicherbereich ändert.
Habe ich in den anderen Antworten schon versucht zu beantworten, nur ein
Hinweis noch: was spricht dagegen, erst alles, was Du an Stringdaten
brauchst, auch hineinzukopieren, und dann StrPtr aufzurufen? Oder, anders
gefragt: warum willst Du den String verändern, nachdem Du die Adresse seiner
Daten mit StrPtr ermittelt hast? Was spricht dagegen, nach jeder Veränderung
erneut StrPtr aufzurufen?
Viele Grüße,
Jan-Arne
> Aus welchem Grund könnte das passieren?
> Ich würde doch höchtens den Inhalt des Files verändern, oder nicht?
Ja klar, wenn Deine Dateivergleiche z.B. gegen das WinSys-Directory laufen,
dann sind bei fehlendem Reset "höchstens" sämtliche Binaries zerstört. ;-)
Nein im Ernst, wie Jan schon geschrieben hat, ich würde versuchen, die
schnelle Schleife ohne diesen "Kunstgriff" zu implementieren. Ist halt ein
Increment mehr - aber dafür sicherer.
Ausserdem scheidet bei dem Ansatz derzeit ein Readonly-FileMapping (der
eigentliche Standard-Fall für schnelle File-Compares) aus.
Olaf
> Ja klar, wenn Deine Dateivergleiche z.B. gegen das WinSys-Directory
laufen,
> dann sind bei fehlendem Reset "höchstens" sämtliche Binaries zerstört. ;-)
Ohne Reset werden die Daten garantiert geschreddert (der MPW schreibt
veränderte Daten auch dann, wenn das Mapping längst zurückgenommen und die
Datei geschlossen wurde), mit Reset sind sie aber auch nicht sicher. Wie
gesagt: diese Variante *eignet sich nicht für File Mappings*, völlig egal,
welche Kunstgriffe noch eingebaut werden. Sie ist nur geeignet, wenn Du die
Daten vorher in einen zusätzlichen Puffer kopierst. Aber bitte nicht
zusätzlich zum File Mapping verwenden ;-).
> Nein im Ernst, wie Jan schon geschrieben hat, ich würde versuchen, die
> schnelle Schleife ohne diesen "Kunstgriff" zu implementieren. Ist halt ein
> Increment mehr - aber dafür sicherer.
Was spricht eigentlich gegen RtlCompareMemory (zumindest unter NT und
Nachfolgern)?
Public Declare Function RtlCompareMemory Lib "ntdll.dll" (pSrc1 As Any,
pSrc2 As Any, ByVal Length As Long) As Long
Viele Grüße,
Jan-Arne
Na, vermutlich genau das...
>> VB ist nicht Threadsafe.
> Das GUI soweit ich weiß nicht, Du kannst aber mit ActiveX-EXEs so
> etwas erreichen.
Nicht wirklich. Jeder VB-Code wird mittels CriticalSections (oder durch
einen ähnlichen Mechanismus) synchronisiert. Natürlich kann eine AX-Exe
innerhalb eines Prozesses mehrere unabhängige Threads haben, aber sobald
über COM-Referenzen ein Thread eine Funktion in einem anderen Thread
aufruft, ein API-Callback empfangen wird etc. wird synchronisiert, d.h.
der aufgerufene Thread muß vorher Idle sein, sodaß der aufrufende Thread
warten muß und nicht einfach auf die Speicherbereiche lesend oder
ausführend zugreifen kann.
>> Und aus diesem Grund kann eine Verschiebung des
>> Speichers ganz sicher NICHT von einem anderen/zweiten Thread aus
>> erfolgen, sondern nur aus dem aktuellen Thread heraus (z.B. bei
>> Funktionsaufrufen, Variablenzugriffen, etc), oder wenn dieser IDLE
>> ist.
> Hat mit Threadsafe absolut nichts zu tun. Strings sind, im Gegensatz
> zu .NET, keine Objekte. Nur der aktuelle Thread kennt die Adresse der
> Daten, nur er kann etwas verändern. Ein anderer Thread bekommt immer
> nur eine Kopie.
Man könnte ja einem solchen Thread einen Pointer auf die Daten übergeben
(und sich damit mächtig Ärger einfangen). Aber VB/COM tut das ja nicht,
und daher sind threadübergreifende COM-Zugriffe meiner Meinung nach
Threadsicher, weil eben synchronisiert.
Ansonsten liegt hier wohl ein Mißverständnis vor. Ich wollte damit
belegen, daß eine evtl vorhandene GC nicht einfach meine Stringpointer
oder Stringdaten verschieben kann, während ein Thread gerade auf diese
Daten zugreift. Der Zugriff ist nämlich nicht synchronisiert, wenn er
nicht via COM erfolgt, und daher würde das Programm aller
wahrscheinlichkeit nach abstürzen, weil die VB-Runtime nicht
threadsicher ist. (Wäre sie das, könne man ja auch mittels CreateThread
und ein paar Tricks problemlos arbeiten.)
> Nach jeder Veränderung des Strings ist der alte Rückgabewert von
> StrPtr unbrauchbar, das ist klar. Du kannst aber gefahrlos den String
> an andere Funktionen übergeben (natürlich nur ByVal !!!),
Warum nicht auch ByRef? Sofern die Funktion den String nicht ändert,
sehe ich kein problem..?
> oder mit den üblichen
> String-Funktionen *lesend* auf seine Daten zugreifen (Left, Right, a =
> mid(s, ...) usw.). Ich glaube aber nicht, dass ein String verändert
> werden kann, wenn ein Thread im Leerlauf ist. Die Runtime wird niemals
> einen String im Speicher verschieben, ohne dass der Thread, dem er
> gehört, ihn verändert hat.
Dann darf ich den String nun auch gefahrlos verbiegen...
# CopyMemory ByVal VarPtr(s), newPtr2BSTR, 4&
... sofern ich ihn wieder zurückbiege und nur lesend auf diese Daten
zugreife?
Viele Grüße,
Andreas
>> Jetzt sollte aufgeräumt werden. D.h., all diese
>> Strings aneinanderketten, um "weiter hinten" einen größeren freien
>> Block zu schaffen.
> die Runtime macht das nicht und kann das auch nicht.
Ahja, gut zu wissen.
>> Für ein normales
>> VB-programm wäre das ja auch ganz ungefährlich, da ja nur der Pointer
>> auf die eigentlichen Stringdaten geändert werden müßte.
> Und die Runtime weiß nicht, wo im Speicher dieser Pointer steht, oder
> (für nicht-VB-Programme), wie viele Pointer auf denselben
> Speicherbereich die Anwendung benutzt...
Klingt logisch.
Also kann man bei exzessivem "KleinString"-Gebrauch durchaus
Speicherprobleme bekommen.
>> Und dieser wird ja den Funktionen nicht übergeben, sondern nur der
>> Pointer auf diesen Pointer.
> Hängt von der Funktion ab. Wenn diese mit ByVal (in C: BSTR)
> aufgerufen wird, erhält sie eine Kopie des Strings.
Du meinst, es wird eine Kopie des Strings erstellt, und dann der zeiger
darauf übergeben? So interpretiere ich jedenfalls den asm code. Also
passiert nichts anderes als mit ByRef auch, nur daß die Funktion mit der
Kopie arbeitet...
> was spricht dagegen, erst alles, was Du an Stringdaten
> brauchst, auch hineinzukopieren, und dann StrPtr aufzurufen? Oder,
> anders gefragt: warum willst Du den String verändern, nachdem Du die
> Adresse seiner Daten mit StrPtr ermittelt hast? Was spricht dagegen,
> nach jeder Veränderung erneut StrPtr aufzurufen?
Gar nichts.
Die Diskussion entstand eigentlich aus der Idee heraus, den
Stringpointer auf den zu vergleichenden Speicherbereich umzubiegen:
hMem1 = GlobalAlloc(ByVal 0&, size+6)
hMem2 = GlobalAlloc(ByVal 0&, size+6)
'BSTR prescriptor
CopyMemory ByVal hMem1, size, 4&
CopyMemory ByVal hMem1, size, 4&
'BSTR terminating Null
CopyMemory ByVal hMem1+size+4, 0&, 2&
CopyMemory ByVal hMem1+size+4, 0&, 2&
'initialisieren, damit ein BSTR Existiert.
s1 = " "
s2 = " "
'aktuelle Datenbereiche merken
oldPtr1 = StrPtr(s1)
oldPtr2 = StrPtr(s2)
'Pointer verbiegen
CopyMemory ByVal varPtr(s1), hMem1, 4&
CopyMemory ByVal varPtr(s2), hMem2, 4&
' nun können die daten im Bereich
' [hMem1+4..hMem1+size+3] verglichen werden:
ret = (s1=s2)
'aufräumen:
CopyMemory ByVal varPtr(s1), oldPtr1, 4&
CopyMemory ByVal varPtr(s2), oldPtr2, 4&
GlobalFree hMem1
GlobalFree hMem2
Dieser Speichervergleich funktioniert ordentlich schnell, da man den
schnellen VB-Stringvergleich (bzw. die dabei genutzen routinen) direkt
ausführen kann. (darf natürlich nur lesend auf die Strings zugegriffen
werden!). Diesen Code hatte ich vor der Asm routine getestet und es
funktionierte einwandfei.
Mir ging es hauptsächlich darum, Speicherbereiche direkt zu vergleichen,
ohne die Daten nochmals kopieren zu müssen, und das ist hier der fall.
Mit obigem Beispiel kann ich z.B. die Speicherbereiche nutzen, um (size)
Bytes von einer Datei dort einzulesen, und zu vergleichen. Mein
Beispielcode (FileCompare.bas) hat ursprünglich diesen Stringvergleich
genutzt, bevor ich die Asm routine eingebaut hatte.
Die Frage im Zusammenhang mit StrPtr und BSTR war, ob zwischen dem
Verbiegen und Aufräumen der Stringpointer evtl eine GC zuschlagen und
dadurch ein Problem verursachen könnte. Ich kann mir das nicht
vorstellen.
Viele Grüße,
Andreas
> Was spricht eigentlich gegen RtlCompareMemory (zumindest unter NT und
> Nachfolgern)?
> Public Declare Function RtlCompareMemory Lib "ntdll.dll" (pSrc1 As
> Any, pSrc2 As Any, ByVal Length As Long) As Long
Zum einen das NT, zum anderen ist es wahrscheinlich langsamer (wird
vielleicht auch von memcmp() in C verwendet). Auch ist der
Assemblerbefehl "repe cmpsd" bei mir langsamer als eine vergleichbare
direkte Schleife. Obwohl ich nicht so ganz verstehe, warum.
Aber: Es gibt doch Intel- bzw. AMD-spezifische Befehlssätze? Hab in den
Intel-specs schonmal gestöbert, allerdings ist man ja Wochen
beschäftigt, wenn man nicht weiß, wonach man da suchen soll ;)
Viele Grüße,
andreas
>>> Der Reset ist schon wichtig - falls z.B. jemand zwecks schnellen
>>> Dateivergleichs direkt gegen MemoryMappedFile-Pointer arbeiten
>>> sollte, bestünde Gefahr, dass die Platte geschreddert wird.
>> Aus welchem Grund könnte das passieren?
>> Ich würde doch höchtens den Inhalt des Files verändern, oder nicht?
> ja, der Inhalt der Dateien kann verändert werden.
Das ist schon klar, aber dem Dateisystem selber kann wohl nichts
passieren (war also ein Mißverständnis).
> Stell Dir vor, Du wolltest
> nach diesem Muster (Memory Mapping + Binärvergleich *mit* Veränderung
> der Daten) zwei EXE-Dateien vergleichen. Oder zwei (sehr große)
> Dateien, die
[...]
> Aus diesem Grunde solltest Du einen solchen Vergleich *niemals* mit
> dem obigen Algorithmus implementieren. Du läufst damit Gefahr, *jede*
> Datei zu zerstören, die irgendwie einmal so "behandelt" wurde. Ein
> zurückschreiben der ursprünglichen Daten hilft hier überhaupt nicht.
Wäre auch unsinnig, da nach Deiner Beschreibung die Datei ja aufgrund
der ersten Änderung komplett zurückgeschrieben werden würde. Außerdem,
wenn es um den reinen Vergleich geht, macht es doch kaum Sinn, Memory
Mapping zu benutzen, zumindest nicht im Zugriffsmodus READWRITE?
> Stell Dir vor, der
> Mapped Page Writer schreibt Deine Daten vor dem zurücktauschen auf die
> Platte, und dann stolpert jemand über die Stromversorgung. Ein
> zurückschreiben ist nicht mehr möglich, und nach dem Neustart musst Du
> mit demolierten Dateien leben, wenn das System überhaupt noch startet;
> schließlich könnte ja gerade jemand auf die Idee gekommen sein, die
> installierte Kernel-Datei mit einer älteren zu vergleichen...
Verstehe, schon klar :)
Viele Grüße,
Andreas
>> Aus welchem Grund könnte das passieren?
>> Ich würde doch höchtens den Inhalt des Files verändern, oder nicht?
> Ja klar, wenn Deine Dateivergleiche z.B. gegen das WinSys-Directory
> laufen, dann sind bei fehlendem Reset "höchstens" sämtliche Binaries
> zerstört. ;-)
> Nein im Ernst, wie Jan schon geschrieben hat, ich würde versuchen, die
> schnelle Schleife ohne diesen "Kunstgriff" zu implementieren. Ist halt
> ein Increment mehr - aber dafür sicherer.
Genau der Kunstgriff macht es aber schneller als alles andere. Und wenn
man es nicht beim FileMapping anwendet, besteht ja auch keine Gefahr.
(Ok, man muß dann ziemlich sicher sein, daß z.B. DIBs etc nicht gemapped
sind...)
Aber ich kann Euren Einwand natürlich nachvollziehen.
> Ausserdem scheidet bei dem Ansatz derzeit ein Readonly-FileMapping
> (der eigentliche Standard-Fall für schnelle File-Compares) aus.
Unbuffered File I/O dürfte schneller sein als R/O-FileMapping (und
darauf baut auch die Idee des ganzen auf). Gegenbeweise sind natürlich
willkommen ;-)
Aber ich werde den Code noch ändern, und anders optimieren. Ihr habt ja
Recht, sicher ist sicher ;-)
Viele Grüße,
Andreas
> > Das GUI soweit ich weiß nicht, Du kannst aber mit ActiveX-EXEs so
> > etwas erreichen.
>
> Nicht wirklich. Jeder VB-Code wird mittels CriticalSections (oder durch
> einen ähnlichen Mechanismus) synchronisiert. Natürlich kann eine AX-Exe
> innerhalb eines Prozesses mehrere unabhängige Threads haben, aber sobald
> über COM-Referenzen ein Thread eine Funktion in einem anderen Thread
> aufruft, ein API-Callback empfangen wird etc. wird synchronisiert, d.h.
> der aufgerufene Thread muß vorher Idle sein, sodaß der aufrufende Thread
> warten muß und nicht einfach auf die Speicherbereiche lesend oder
> ausführend zugreifen kann.
dazu weiß ich nichts. Ich habe nur einmal gelesen, dass per CreateObject
erzeugte Objekte in einem anderen (eigenen) Thread erzeugt werden, wenn das
Objekt in einer ActiveX-EXE liegt. Der Trick war dann, die Haupt-EXE einer
Anwendung selber als ActiveX-EXE zu kompilieren, und das Objekt, das in
einem eigenen Thread ausgehürt werden sollte, statt per New mit CreateObject
zu erzeugen. Anschließend wurde ein Timer gesetzt, der dann Code im neuen
Thread ausführte. Hat prima geklappt.
> >> Und aus diesem Grund kann eine Verschiebung des
> >> Speichers ganz sicher NICHT von einem anderen/zweiten Thread aus
> >> erfolgen, sondern nur aus dem aktuellen Thread heraus (z.B. bei
> >> Funktionsaufrufen, Variablenzugriffen, etc), oder wenn dieser IDLE
> >> ist.
>
> > Hat mit Threadsafe absolut nichts zu tun. Strings sind, im Gegensatz
> > zu .NET, keine Objekte. Nur der aktuelle Thread kennt die Adresse der
> > Daten, nur er kann etwas verändern. Ein anderer Thread bekommt immer
> > nur eine Kopie.
>
> Man könnte ja einem solchen Thread einen Pointer auf die Daten übergeben
> (und sich damit mächtig Ärger einfangen). Aber VB/COM tut das ja nicht,
> und daher sind threadübergreifende COM-Zugriffe meiner Meinung nach
> Threadsicher, weil eben synchronisiert.
Threadsicher per Holzhammer. Jedes COM-Objekt hat einen Thread, zu dem es
gehört und der jeden Code dieses Objektes ausführt. Wenn ein Objekt aus
einem anderen Thread heraus aufgerufen wird, wartet dieser auf die
Bearbeitung des Aufrufes durch den Thread, zu dem das Objekt gehört.
> Ansonsten liegt hier wohl ein Mißverständnis vor. Ich wollte damit
> belegen, daß eine evtl vorhandene GC nicht einfach meine Stringpointer
> oder Stringdaten verschieben kann, während ein Thread gerade auf diese
> Daten zugreift. Der Zugriff ist nämlich nicht synchronisiert, wenn er
> nicht via COM erfolgt, und daher würde das Programm aller
> wahrscheinlichkeit nach abstürzen, [...]
Zugriffe auf Strings sind niemals per COM synchronisiert, weil Strings keine
Objekte sind.
> [...] weil die VB-Runtime nicht
> threadsicher ist. (Wäre sie das, könne man ja auch mittels CreateThread
> und ein paar Tricks problemlos arbeiten.)
Hier vergleichst Du meiner Meinung nach Äpfel mit Birnen.
Die VB-Runtime ist threadsicher, solange Du innerhalb der Sprache bleibst.
Wenn Du versuchst, mit CreateThread einen "normalen" neuen Thread zu
erzeugen, weiß die Runtime davon nichts. Sie kann also auch ihren
thread-spezifischen Kontext nicht einrichten, verlässt sich aber darauf,
dass dieser korrekt erzeugt wurde, sobald Dein Code ausgeführt wird.
> > Nach jeder Veränderung des Strings ist der alte Rückgabewert von
> > StrPtr unbrauchbar, das ist klar. Du kannst aber gefahrlos den String
> > an andere Funktionen übergeben (natürlich nur ByVal !!!),
>
> Warum nicht auch ByRef? Sofern die Funktion den String nicht ändert,
> sehe ich kein problem..?
Hast natürlich recht, nur bin ich eher dafür, dieses Risiko gar nicht erst
einzugehen. Ich meine, wer ByRef sagt, will auch ByRef ;-).
> > oder mit den üblichen
> > String-Funktionen *lesend* auf seine Daten zugreifen (Left, Right, a =
> > mid(s, ...) usw.). Ich glaube aber nicht, dass ein String verändert
> > werden kann, wenn ein Thread im Leerlauf ist. Die Runtime wird niemals
> > einen String im Speicher verschieben, ohne dass der Thread, dem er
> > gehört, ihn verändert hat.
>
> Dann darf ich den String nun auch gefahrlos verbiegen...
>
> # CopyMemory ByVal VarPtr(s), newPtr2BSTR, 4&
>
> ... sofern ich ihn wieder zurückbiege und nur lesend auf diese Daten
> zugreife?
Das habe ich nie getestet, es sollte aber funktionieren, *solange*
newPtr2BSTR ein richtiger Zeiger auf einen BSTR ist und nicht nur ein
null-terminiertes Array von Unicode-Zeichen. Wenn bei (newPtr2BSTR - 4) die
korrekte Länge der Daten steht, sollte das funktionieren.
Viele Grüße,
Jan-Arne
> > Public Declare Function RtlCompareMemory Lib "ntdll.dll" (pSrc1 As
> > Any, pSrc2 As Any, ByVal Length As Long) As Long
>
> Zum einen das NT, zum anderen ist es wahrscheinlich langsamer (wird
> vielleicht auch von memcmp() in C verwendet). Auch ist der
> Assemblerbefehl "repe cmpsd" bei mir langsamer als eine vergleichbare
> direkte Schleife. Obwohl ich nicht so ganz verstehe, warum.
ist der zu vergleichende Speicherbereich korrekt ausgerichtet (beide
Adressen ohne Rest durch 4 teilbar)? Ansonsten fällt mir absolut nichts ein,
warum das langsamer sein sollte. Das sollte der schnellste Vergleich sein,
der überhaupt nur möglich ist. Allerdings funktioniert er nur bei voller
Geschwindigkeit, wenn die Adressen auch stimmen. Sind diese nämlich nicht
ohne Rest durch 4 teilbar, muss der Prozessor ein ganzes QWORD einlesen
(zusätzlicher Speicherzyklus) und die "richtigen" Bytes erst einmal
herausfischen.
> Aber: Es gibt doch Intel- bzw. AMD-spezifische Befehlssätze? Hab in den
> Intel-specs schonmal gestöbert, allerdings ist man ja Wochen
> beschäftigt, wenn man nicht weiß, wonach man da suchen soll ;)
<ironie>
Und was spricht gegen ein NT-basiertes System, das mit RtlCompareMemory
einen schönen schnellen Speichervergleich, wenn Du die neuesten Prozessoren
einsetzen willst?
</ironie>
Im Ernst: diese ganzen SIMD-Befehle sind zwar schön, aber für
Speichervergleiche nicht nötig. Ich behaupte, dass es keinen schnelleren
Vergleich gibt als ein "repe cmpsd", wenn die Speicherbereiche an den
richtigen Adressen liegen.
Viele Grüße,
Jan-Arne
> Unbuffered File I/O dürfte schneller sein als R/O-FileMapping (und
> darauf baut auch die Idee des ganzen auf).
das bezweifle ich. Ich gehe einfach mal davon aus, dass Du mit "unbuffered
file I/O" ein CreateFile mit FILE_FLAG_NO_BUFFERING meinst. Das hat den
Nachteil, dass Dich der Cache auch nicht beim Vorauslesen unterstützt. Du
musst also immer einen Block Daten lesen und vergleichen, den nächsten lesen
und vergleichen usw. Und zwischendurch natürlich warten, bis die Platte die
Daten liefert.
Wenn Du das Flag weglässt (oder ein FileMapping einsetzt), und von mir aus
immer nur eine Seite (4 KB, oder ein vielfaches davon) pro Aufruf
vergleichst, erkennt der Dateisystemcache ein Lesemuster. Er kann also
weitere Daten von beiden Dateien anfordern und einlesen, während Du weiter
"vorne" noch mit dem Vergleich beschäftigt bist.
> Gegenbeweise sind natürlich willkommen ;-)
Dito.
Viele Grüße,
Jan-Arne
> Klingt logisch.
> Also kann man bei exzessivem "KleinString"-Gebrauch durchaus
> Speicherprobleme bekommen.
Ja. Außerdem kann der Speicher ziemlich schnell fragmentiert werden.
>
> >> Und dieser wird ja den Funktionen nicht übergeben, sondern nur der
> >> Pointer auf diesen Pointer.
>
> > Hängt von der Funktion ab. Wenn diese mit ByVal (in C: BSTR)
> > aufgerufen wird, erhält sie eine Kopie des Strings.
>
> Du meinst, es wird eine Kopie des Strings erstellt, und dann der zeiger
> darauf übergeben? So interpretiere ich jedenfalls den asm code. Also
> passiert nichts anderes als mit ByRef auch, nur daß die Funktion mit der
> Kopie arbeitet...
Ein BSTR ist ein Zeiger auf seine Daten. Bei ByVal wird ein Zeiger auf eine
Kopie dieser Daten übergeben, bei ByRef wird ein Zeiger auf die
BSTR-Variable übergeben, effektiv also ein Zeiger auf einen Zeiger auf die
Daten.
Sange Du hMem1 und hMem2 nicht durcheinanderbringst, siehe die ersten 4
CopyMemory-Aufrufe ;-).
Das Null-Zeichen am Ende kannst Du dir übrigens sparen. Die Länge steht
schon am Anfang, ein Null-Zeichen am Ende ist deshalb nicht nötig. Achtung:
das gilt nur, wenn Du VB-String-Funktionen einsetzt. Aber das sind sowieso
die einzigen, denen Du einen solchen String übergeben solltest. Schließlich
würde jede Funktion, die einen null-terminierten String erwartet, über das
erste Null-zeichen in den "Daten" stolpern.;-)
> Dieser Speichervergleich funktioniert ordentlich schnell, da man den
> schnellen VB-Stringvergleich (bzw. die dabei genutzen routinen) direkt
> ausführen kann. (darf natürlich nur lesend auf die Strings zugegriffen
> werden!). Diesen Code hatte ich vor der Asm routine getestet und es
> funktionierte einwandfei.
>
> Mir ging es hauptsächlich darum, Speicherbereiche direkt zu vergleichen,
> ohne die Daten nochmals kopieren zu müssen, und das ist hier der fall.
> Mit obigem Beispiel kann ich z.B. die Speicherbereiche nutzen, um (size)
> Bytes von einer Datei dort einzulesen, und zu vergleichen. Mein
> Beispielcode (FileCompare.bas) hat ursprünglich diesen Stringvergleich
> genutzt, bevor ich die Asm routine eingebaut hatte.
>
> Die Frage im Zusammenhang mit StrPtr und BSTR war, ob zwischen dem
> Verbiegen und Aufräumen der Stringpointer evtl eine GC zuschlagen und
> dadurch ein Problem verursachen könnte. Ich kann mir das nicht
> vorstellen.
Definitiv: nein. Eine derartige GC gibt es weder in der OLE- noch in der
VB-Runtime.
Viele Grüße,
Jan-Arne
>>...ich würde versuchen, die
>> schnelle Schleife ohne diesen "Kunstgriff" zu implementieren. Ist halt
>> ein Increment mehr - aber dafür sicherer.
> Genau der Kunstgriff macht es aber schneller als alles andere.
Hatten wir doch schon - die VB-Routine kommt z.B. ohne aus und ist nur 6%
langsamer.
Auf einem PIV mit >2GHz und schnellem Speicher dürfte sich das Ganze so im
Bereich von 300MB/sec abspielen.
Eine aktuelle IDE-Platte liefert im Schnitt um die 30MB/sec; da ist es
völlig unerheblich, ob der Algo nun 320MB/sec oder "nur" 300MB/sec Durchsatz
bringt.
Olaf
Angesichts der Tatsache, daß RtlCopyMemory() etc. in WINNT.H lediglich auf
memcpy() etc. abgebildet wird, dürfte klar sein, daß auch
RtlCompareMemory() nur ein alias für memcmp() ist. Und damit waren, wenn
ich mich nicht irre, Deine Test ja nicht ergiebig, also kann man das
getrost streichen.
Ich muß mich 'mal wieder 'einschalten', denn ich habe Dich ja überhaupt,
als alter C64- und ATARI ST-Nostalgiker - erst auf die Idee mit der Garbage
Collection gebracht - die ich hier auch gleich zurücknehme (u.a. das macht
ja eine der Änderungen zu VB.NET aus, wo die GC wieder in Mode gekommen
ist).
Eine GC von VB aus ist natürlich völlig unsinnig angesichts der
Win32-Speicherverwaltung: Nicht-dynmische Daten in VB erfordern keine GC.
Die dynamische Daten liegen im globalen Speicher, VB (& Co.) erhält nur
einen Zeiger auf die dynamischen Daten, der in einer nicht-dynamischen
'Zeiger-Variable' abgelegt wird. Der Zeiger auf die dynamischen Daten ist
kein Zeiger auf den tatsächlichen physikalischen Speicher, sondern ein
Zeiger auf eine virtuelle Speicheradresse in dem virtuellen
Speicheradressraum dieser einen Anwendung. Das Betriebssystem übernimmt das
Mapping zwischen physikalischem und virtuellem Speicher. Infolge dessen
wäre auch das Betriebssystem der Verantwortliche für eine GC, nicht VB &
Co.
Ich würde nicht sagen, daß man durch exzessiven "KleinString"-Gebrauch per
se Speicherprobleme bekommen kann, denn der belegte, virtuelle
Speicherplatz belegt nicht unbedingt den entsprechenden physikalischen
Speicher, da das Win32-Speichermanagment diesen in die Auslagerungsdatei
schieben kann und nur die Seiten lädt (nach einer gewissen Logik), auf die
jeweils zugegriffen wird.
Aber zu einer starken Fragmentierung des Speichers wird es dadurch
natürlich wahrscheinlich kommen, und damit zu einer Verlangsamung des
Speichermanagements.
> Die Diskussion entstand eigentlich aus der Idee heraus, den
> Stringpointer auf den zu vergleichenden Speicherbereich umzubiegen:
...wogegen aus den genannten Gründen wohl doch nichts zu sprechen scheint.
Dennoch solltest Du grundsätzlich darauf achten, daß Du den temporären
String-Zeiger nach Ende der Umbiege-Aktion wieder restaurierst (wie in
Deinem Code geschehen).
>> vielleicht auch von memcmp() in C verwendet). Auch ist der
>> Assemblerbefehl "repe cmpsd" bei mir langsamer als eine vergleichbare
>> direkte Schleife. Obwohl ich nicht so ganz verstehe, warum.
> ist der zu vergleichende Speicherbereich korrekt ausgerichtet (beide
> Adressen ohne Rest durch 4 teilbar)? Ansonsten fällt mir absolut
> nichts ein, warum das langsamer sein sollte. Das sollte der schnellste
> Vergleich sein, der überhaupt nur möglich ist. Allerdings funktioniert
> er nur bei voller Geschwindigkeit, wenn die Adressen auch stimmen.
> Sind diese nämlich nicht ohne Rest durch 4 teilbar, muss der Prozessor
> ein ganzes QWORD einlesen (zusätzlicher Speicherzyklus) und die
> "richtigen" Bytes erst einmal herausfischen.
Ich denke schon, werde das aber nochmal nachprüfen.
Hab allerdings über google auch ein paar Meinungen gelesen, die das
gleiche behaupten.
>> Aber: Es gibt doch Intel- bzw. AMD-spezifische Befehlssätze? Hab in
>> den Intel-specs schonmal gestöbert, allerdings ist man ja Wochen
>> beschäftigt, wenn man nicht weiß, wonach man da suchen soll ;)
> <ironie>
> Und was spricht gegen ein NT-basiertes System, das mit
> RtlCompareMemory einen schönen schnellen Speichervergleich, wenn Du
> die neuesten Prozessoren einsetzen willst?
> </ironie>
Weil RtlCompareMemory eben bereits durch eine einfache simple Schleife
geschlagen werden kann. Es sei denn, es verwendet bei schnellen
prozessoren auch andere routinen, aber das glaube ich kaum.
> Im Ernst: diese ganzen SIMD-Befehle sind zwar schön, aber für
> Speichervergleiche nicht nötig. Ich behaupte, dass es keinen
> schnelleren Vergleich gibt als ein "repe cmpsd", wenn die
> Speicherbereiche an den richtigen Adressen liegen.
Wie gesagt, werde das demnächst nochmal besser austesten.
Viele Grüße,
Andreas
>> Dann darf ich den String nun auch gefahrlos verbiegen...
>>
>> # CopyMemory ByVal VarPtr(s), newPtr2BSTR, 4&
>>
>> ... sofern ich ihn wieder zurückbiege und nur lesend auf diese Daten
>> zugreife?
> Das habe ich nie getestet, es sollte aber funktionieren, *solange*
> newPtr2BSTR ein richtiger Zeiger auf einen BSTR ist und nicht nur ein
> null-terminiertes Array von Unicode-Zeichen. Wenn bei (newPtr2BSTR -
> 4) die korrekte Länge der Daten steht, sollte das funktionieren.
Tut es auch wunderbar :)
Viele Grüße,
Andreas
> Eine GC von VB aus ist natürlich völlig unsinnig angesichts der
> Win32-Speicherverwaltung:
den Zusammenhang verstehe ich nicht ganz. Anders gefragt: wie kommt .NET um
eben jene Speicherverwaltung herum? Die Frage ist durchaus ernst gemeint,
vielleicht kann ich das für zukünftige Projekte gebrauchen ;-). Was, kommt
es gar nicht? Wofür hat es dann eine GC *verwirrt* ??? ;-)))
> Nicht-dynmische Daten in VB erfordern keine GC.
> Die dynamische Daten liegen im globalen Speicher, VB (& Co.) erhält nur
> einen Zeiger auf die dynamischen Daten, der in einer nicht-dynamischen
> 'Zeiger-Variable' abgelegt wird. Der Zeiger auf die dynamischen Daten ist
> kein Zeiger auf den tatsächlichen physikalischen Speicher, sondern ein
> Zeiger auf eine virtuelle Speicheradresse in dem virtuellen
> Speicheradressraum dieser einen Anwendung.
Das ist richtig, hat nur leider mit der Entscheidung für/gegen eine GC
absolut nichts zu tun. Auch in .NET gibt es keine "Zeiger auf den
tatsächlichen physikalischen Speicher". Im Gegenteil, es soll ja gerade
versucht werden, den Programmierer weiter von diesen Low-Level-G'schichten
fernzuhalten.
Jede Anwendung unter Win32, ob .NET, ob VB(classic), C(mit oder ohne ++ oder
#), hat nur Zugriff auf einen *virtuellen* Speicher. Innerhalb dessen kann
man natürlich eine GC ausführen (und damit dem Programmierer die
Verantwortung für einen verantwortungsvollen Gebrauch der manchmal knappen
Resource "Speicher-Adressen" abnehmen). Man muss es aber nicht. Eine GC hat
nichts damit zu tun, ob wir es mit virtuellen, physischen oder logischen
Adressen zu tun haben. Sie erlaubt es nur dem Programmierer, sich nicht
(unnötig?) mit dem Speichermanagement aus längst vergangenen Epochen
auseinanderzusetzen. Freigeben von Speicher? Lächerlich, dafür habe ich
meine GC ;-). Zurückgeben von Speicher an das System??? Warum "System", ich
laufe doch im User Mode ;-))).
> Das Betriebssystem übernimmt das
> Mapping zwischen physikalischem und virtuellem Speicher. Infolge dessen
> wäre auch das Betriebssystem der Verantwortliche für eine GC, nicht VB &
> Co.
Der erste Satz stimmt (nur, dass Du die logischen Adressen vergessen hast
;-), den zweiten verstehe ich nicht. Eine GC übernimmt das Sammeln von
Speicherleichen. Was hat das jetzt mit dem Mapping von
virtuellen/physischen/logischen Adressen zu tun? Dem Betriebssystem kann es
letztlich völlig egal sein, wer da mit dem Speicher herumspielt, und es
*ist* ihm egal. Nicht umsonst betrachtet jeder Programmierer, der Code für
den Kernel Mode (Ring 0) schreibt, den User Mode (Ring 3 in
Intel-Nomenklatur) als "Reich des Bösen", gegen das man sich mit allen zur
Verfügung stehen Mitteln verteidigen muss (in IA32 sind das u.a. einige
Flags in den Seitentabellen und eine ganze Reihe von Interrupt- und
Gate-Deskriptoren in den zugehörigen Tabellen) ;-))).
> Ich würde nicht sagen, daß man durch exzessiven "KleinString"-Gebrauch per
> se Speicherprobleme bekommen kann, denn der belegte, virtuelle
> Speicherplatz belegt nicht unbedingt den entsprechenden physikalischen
> Speicher, da das Win32-Speichermanagment diesen in die Auslagerungsdatei
> schieben kann und nur die Seiten lädt (nach einer gewissen Logik), auf die
> jeweils zugegriffen wird.
Geladen wird, was im Moment benötigt wird (harter Seitenfehler), oder was
das System in naher Zukunft brauchen wird (Read Ahead). Allerdings belegt
alles (virtuellen) Speicher, dazu Platz in den Verwaltungsstrukturen von
diversen Heap-Managern, Seitentabellen und VADs. Einer Speicherverwaltung,
die nicht ausdrücklich für "Winzlinge" ausgelegt ist, kann man mit solchem
"Kleinvieh" ziemlich zusetzen. Jede Anwendung hat halt nur 2 GB (die
restlichen 2 GB belegen Kernel und co.), auf dem teuren Enterprise Server
maximal 3 GB (bei geeignetem Schalter in boot.ini UND dem EXE-Header, was
bei VB-Programmen ohne Hex-Editor nur schwer möglich sein dürfte). Wenn
dieser Speicher erst einmal zugelaufen ist, nutzt keine Auslagerungsdatei
mehr etwas. Dann schlägt jede Speicheranforderung einfach nur noch fehl.
Bitte sagt jetzt nicht, so etwas gebe es nicht ("wer braucht schon so viel
Speicher?", oder, treffender - aber aus dem Gedächtnis zitiert -: "Niemand
wird je mehr als 640 KB Arbeitsspeicher brauchen." ;-). 2 GB erscheinen als
seeehr viel, sind aber leider irgendwann einmal voll...
> Aber zu einer starken Fragmentierung des Speichers wird es dadurch
> natürlich wahrscheinlich kommen, und damit zu einer Verlangsamung des
> Speichermanagements.
... bis zur völligen Unbenutzbarkeit der Maschine. Schon 'mal versucht, 400
MB an Mesh-Dateien in POV-Ray zu zeichen (alternativ 4 Objekte zu je 100
MB)? Ich weiß nicht, was auf deiner Maschine dann passiert, aber bei mir
(512 MB RAM, 1 GB Swapfile, W2K Adv Srv) hilft dann nur noch der Griff zum
Netzteilschalter. Ja, ja, Reset-Knopf habe ich natürlich auch, aber meist
keinen passenden Stift zur Hand :-(((.
> > Die Diskussion entstand eigentlich aus der Idee heraus, den
> > Stringpointer auf den zu vergleichenden Speicherbereich umzubiegen:
>
> ...wogegen aus den genannten Gründen wohl doch nichts zu sprechen scheint.
> Dennoch solltest Du grundsätzlich darauf achten, daß Du den temporären
> String-Zeiger nach Ende der Umbiege-Aktion wieder restaurierst (wie in
> Deinem Code geschehen).
Dem stimme ich ausnahmsweise kommentarlos zu ;-).
Viele Grüße,
Jan-Arne
> > ist der zu vergleichende Speicherbereich korrekt ausgerichtet (beide
> > Adressen ohne Rest durch 4 teilbar)? Ansonsten fällt mir absolut
> > nichts ein, warum das langsamer sein sollte. Das sollte der schnellste
> > Vergleich sein, der überhaupt nur möglich ist. Allerdings funktioniert
> > er nur bei voller Geschwindigkeit, wenn die Adressen auch stimmen.
> > Sind diese nämlich nicht ohne Rest durch 4 teilbar, muss der Prozessor
> > ein ganzes QWORD einlesen (zusätzlicher Speicherzyklus) und die
> > "richtigen" Bytes erst einmal herausfischen.
>
> Ich denke schon, werde das aber nochmal nachprüfen.
> Hab allerdings über google auch ein paar Meinungen gelesen, die das
> gleiche behaupten.
ich halte es zumindest für logisch. Bei der berüchtigten Folge "repe cmpsd"
hat der Prozessor nur einen einzigen Befehl (cmpsd) auszuführen, das "repe"
ist nur ein Befehlspräfix. Während der Ausführung sind der gesamte
Instruction Decoder und der Instruction Cache arbeitslos, weil keine
weiteren Befehle mehr hereinkommen. Auch sind keine Sprungbefehle mehr
auszuführen (egal, ob mit oder ohne Bedingung), die sich wegen des dann
nötigen vollständigen Leerens der Pipeline immer negativ auf die
Geschwindigkeit auswirken.
Könnte es sein, dass Deine Speicherbandbreite für Deinen Prozessor nicht
ausreicht (P4 mit Einkanal-DDR-RAM oder so etwas)? Dann könnte es eventuell
vorkommen, dass einige Optimierungen (spekulative Ausführung, günstige
Sprungvorhersagen usw.) die Schleife schneller erscheinen lassen, weil der
Prozessor bei einem einfachen "repe cmpsd" zu lange auf den Hauptspeicher
warten muss.
> Weil RtlCompareMemory eben bereits durch eine einfache simple Schleife
> geschlagen werden kann. Es sei denn, es verwendet bei schnellen
> prozessoren auch andere routinen, aber das glaube ich kaum.
Macht es nicht. RtlCompareMemory ist der (theoretisch) schnellste
Speichervergleich (aus dem Handgelenk, und ohne die Befehle genau
nachzuschlagen):
mov ecx, length
mov esi, p1
shr ecx, 2
mov edi, p2
repe cmpsd
jne done
mov ecx, length
and ecx, 3
repe cmpsd
done: ...
Ich glaube sogar, dass diese Routine in Assembler-Code geschrieben ist, eben
weil man eine Schleife wie
for (i = 0; i < (length / sizeof(ULONG)); i++)
if (p1[i] != p2[i]) break;
verhindern wollte. Diese trieft (zumindest bei schlecht-optimierenden
Compilern) vor Sprungbefehlen. Ich denke daher, dass die meisten modernen
Compiler sie durch ein entsprechendes cmpsd ersetzen, wenn sie auf
Geschwindigkeit optimieren.
> > Im Ernst: diese ganzen SIMD-Befehle sind zwar schön, aber für
> > Speichervergleiche nicht nötig. Ich behaupte, dass es keinen
> > schnelleren Vergleich gibt als ein "repe cmpsd", wenn die
> > Speicherbereiche an den richtigen Adressen liegen.
>
> Wie gesagt, werde das demnächst nochmal besser austesten.
Eventuell kannst Du auch auf verschiedenen Maschinen mit unterschiedlichen
RAM-Konfigurationen testen? Ich meine damit so Dinge wie: Vergleich auf
einem Ein-Kanal-DDR-System, auf einem Zwei-Kanal-DDR-System mit und ohne
bestücktem zweiten Kanal usw. Auf das Ergebnis bin ich schon jetzt gespannt.
Viele Grüße,
Jan-Arne
Du verstehst den Zusammenhang nicht, weil Du dort einen suchst, wo ich
keinen hergestellt habe. Der einzige Zusammenhang zwischen VB und VB.NET in
meinem Posting ist, daß in VB.NET eine GC am Werkeln ist, in VB nicht; über
die Speicherverwaltung, der VB.NET unterworfen ist, wurde nichts gesagt.
> Das ist richtig, hat nur leider mit der Entscheidung für/gegen eine GC
> absolut nichts zu tun. Auch in .NET gibt es keine "Zeiger auf den
> tatsächlichen physikalischen Speicher". Im Gegenteil, es soll ja gerade
> versucht werden, den Programmierer weiter von diesen
Low-Level-G'schichten
> fernzuhalten.
a) Noch einmal: Ich beabsichtige und beabsichtigte in keiner Weise, mich
mit der Speicherverwaltung, GC oder sonstigem von (VB).NET
auseinanderzusetzen.
b) Das spielt aus Sicht von >VB< sehr wohl eine Rolle für eine GC: Nur der
Bereich, welcher die Kontrolle über den physikalischen Speicher hat, kann
überhaupt bei einer GC involviert sein. Da VB keine Kontrolle darüber hat,
fällt die Andreas von mir 'angedrohte' GC flach.
> Jede Anwendung unter Win32, ob .NET, ob VB(classic), C(mit oder ohne ++
oder
> #), hat nur Zugriff auf einen *virtuellen* Speicher. Innerhalb dessen
kann
> man natürlich eine GC ausführen (und damit dem Programmierer die
> Verantwortung für einen verantwortungsvollen Gebrauch der manchmal
knappen
> Resource "Speicher-Adressen" abnehmen). Man muss es aber nicht.
Solange man, wie in VB, nicht zur Laufzeit neue Speicherzeiger für die
Aufnahme virtueller Speicheradressen erstellen kann, wäre eine GC gänzlich
sinnfrei (machbar natürlich...).
> Eine GC hat
> nichts damit zu tun, ob wir es mit virtuellen, physischen oder logischen
> Adressen zu tun haben. Sie erlaubt es nur dem Programmierer, sich nicht
> (unnötig?) mit dem Speichermanagement aus längst vergangenen Epochen
> auseinanderzusetzen. Freigeben von Speicher? Lächerlich, dafür habe ich
> meine GC ;-). Zurückgeben von Speicher an das System??? Warum "System",
ich
> laufe doch im User Mode ;-))).
Du verstehst Garbage Collection m.E. einseitig: Offensichtlich beschränkst
Du sie auf die >Freigabe< nicht mehr benötigten Speichers. Tatsächlich
gehört dazu aber auch die 'Neustrukturierung' des Speichers, um
Speicherlöcher zu vermeiden und den vorhandenen Speicher effizient zu
nutzen. Und dabei ist sehr wohl von Bedeutung, ob eine Anwendung etc. die
Kontrolle über den physikalischen Speicher hat, denn nur dann kann die
Notwendigkeit einer Neustrukturierung überhaupt wahrgenommen geschweige
denn durchgeführt werden.
> Der erste Satz stimmt (nur, dass Du die logischen Adressen vergessen hast
> ;-)
???
> den zweiten verstehe ich nicht. Eine GC übernimmt das Sammeln von
> Speicherleichen. Was hat das jetzt mit dem Mapping von
> virtuellen/physischen/logischen Adressen zu tun? Dem Betriebssystem kann
es
> letztlich völlig egal sein, wer da mit dem Speicher herumspielt, und es
> *ist* ihm egal.
S.o.! Wie gesagt, bei der GC geht es nicht nur um die Freigabe nicht mehr
verwendeten Speichers, sondern auch um die 'Neustrukturierung'. Dafür
ist/wäre derjenige verantwortlich, der den physikalischen Speicher
verwaltet.
> Geladen wird, was im Moment benötigt wird (harter Seitenfehler), oder was
> das System in naher Zukunft brauchen wird (Read Ahead). Allerdings
belegt
> alles (virtuellen) Speicher, dazu Platz in den Verwaltungsstrukturen von
> diversen Heap-Managern, Seitentabellen und VADs. Einer
Speicherverwaltung,
> die nicht ausdrücklich für "Winzlinge" ausgelegt ist, kann man mit
solchem
> "Kleinvieh" ziemlich zusetzen. Jede Anwendung hat halt nur 2 GB (die
> restlichen 2 GB belegen Kernel und co.), auf dem teuren Enterprise Server
> maximal 3 GB (bei geeignetem Schalter in boot.ini UND dem EXE-Header, was
> bei VB-Programmen ohne Hex-Editor nur schwer möglich sein dürfte). Wenn
> dieser Speicher erst einmal zugelaufen ist, nutzt keine Auslagerungsdatei
> mehr etwas. Dann schlägt jede Speicheranforderung einfach nur noch fehl.
> Bitte sagt jetzt nicht, so etwas gebe es nicht ("wer braucht schon so
viel
> Speicher?", oder, treffender - aber aus dem Gedächtnis zitiert -:
"Niemand
> wird je mehr als 640 KB Arbeitsspeicher brauchen." ;-). 2 GB erscheinen
als
> seeehr viel, sind aber leider irgendwann einmal voll...
Ich ging davon aus, daß Andreas >physikalischen< Speicher meinte, und
wollte damit nur klarmachen, daß das 'Zumüllen' des physikalischen
Speichers durch eine Anwendung keine größere Beachtung verdient, weil die
physikalischen Speichergrenzen in der Regle nicht die Speichergrenzen
angibt, die der Anwendung gesetzt sind.
Darin, daß eine Anwendung auch den ihr zur Verfügung stehenden virtuellen
Speicher 'zumüllen' kann, diesen sinnvoll verwenden sollte und 2 GB
möglicherweise 'schnell' verbraucht sind, darin sind wir uns, denke ich,
völlig einig.
> Du verstehst Garbage Collection m.E. einseitig: Offensichtlich
beschränkst
> Du sie auf die >Freigabe< nicht mehr benötigten Speichers. Tatsächlich
> gehört dazu aber auch die 'Neustrukturierung' des Speichers, um
> Speicherlöcher zu vermeiden und den vorhandenen Speicher effizient zu
> nutzen. Und dabei ist sehr wohl von Bedeutung, ob eine Anwendung etc. die
> Kontrolle über den physikalischen Speicher hat, denn nur dann kann die
> Notwendigkeit einer Neustrukturierung überhaupt wahrgenommen geschweige
> denn durchgeführt werden.
Eine Win32-Anwendung kennt doch gar keinen "physikalischen" Speicher,
sondern nur einen ihr vorgegaukelten Adressraum...?
> S.o.! Wie gesagt, bei der GC geht es nicht nur um die Freigabe nicht mehr
> verwendeten Speichers, sondern auch um die 'Neustrukturierung'. Dafür
> ist/wäre derjenige verantwortlich, der den physikalischen Speicher
> verwaltet.
Also das OS?
Viele Grüße
Harald M. Genauck
ABOUT Visual Basic - das Webmagazin
http://www.aboutvb.de
"Thorsten Albers" <albe...@SPAMuni-freiburg.de> schrieb:
> Du verstehst den Zusammenhang nicht, weil Du dort einen suchst,
> wo ich keinen hergestellt habe. Der einzige Zusammenhang zwischen
> VB und VB.NET in meinem Posting ist, daß in VB.NET eine GC
> am Werkeln ist, in VB nicht; über die Speicherverwaltung, der
> VB.NET unterworfen ist, wurde nichts gesagt.
Auch VB hat einen GC.
VB .NET: Ablaufverfolgungs-GC
VB-Classic: Referenzzählungs-GC
Das ist auch ein GC.
Grüsse,
Herfried K. Wagner
--
http://www.mvps.org/dotnet
Ja, eben! Bei der Speicherverwaltung von Win32 kann nur das Betriebssystem
eine GC durchführen, da nur ihm die physikalischen Speicheradressen bekannt
sind.
> Auch VB hat einen GC.
> VB-Classic: Referenzzählungs-GC
Das hatten wir doch auch schon mal in dem Thread - das ist kein GC im Sinne
einer zeitlich unabhängig agierenden Instanz.
Bei "Set ObjRef = Nothing" wird auf dem IUnknown-Interface des COM-Objekts
die Release-Methode aufgerufen.
Diese decrementiert den RefCount und ist ausserdem dafür verantwortlich, die
"eigene" Instanz aus dem Speicher zu räumen, sobald der RefCount auf Null
geht.
Natürlich kann man in VB-Classic mit ganz normalen Mitteln (ohne
API-Trickserei) eine eigene ClassFactory mit integriertem GC aufbauen - was
gar nicht so kompliziert ist - Ralf Westphal hat das in einer älteren
BasicPro mal anhand eines Beispielprojektes umgesetzt (ging damals um die
Vermeidung von zykl. Referenzen in komplexen Objektmodellen).
Olaf
Na ja, die Referenzzählung als GC zu bezeichnen ist dann doch etwas weit
hergeholt, nicht? Zumindest wird damit nur ein Teilbereich einer GC
abgedeckt.
Außerdem wäre dabei auch noch die Frage, ob VB selbst eine eigene
Referenzzählung hat (was ich für unwahrscheinlich halte) oder sie dem
Betriebssystem überläßt (vgl. GlobalAlloc(), GlobalFree()).
"Thorsten Albers" <albe...@SPAMuni-freiburg.de> schrieb:
> Na ja, die Referenzzählung als GC zu bezeichnen ist dann doch etwas
weit
> hergeholt, nicht? Zumindest wird damit nur ein Teilbereich einer GC
> abgedeckt.
Lediglich die Wortwahl von MS.
> Du verstehst den Zusammenhang nicht, weil Du dort einen suchst, wo ich
> keinen hergestellt habe. Der einzige Zusammenhang zwischen VB und VB.NET
in
> meinem Posting ist, daß in VB.NET eine GC am Werkeln ist, in VB nicht;
über
> die Speicherverwaltung, der VB.NET unterworfen ist, wurde nichts gesagt.
Ich habe mich auf deine Aussage "Eine GC von VB aus ist natürlich völlig
unsinnig angesichts der Win32-Speicherverwaltung" bezogen, die dies meiner
Meinung nach nahelegt. Ich interpretiere dies so, dass du meinst, die
Speicherverwaltung mache eine GC überflüssig. Und ich bin der Meinung, dass
dies nichts miteinander zu tun hat. Ich kann eine GC implementieren, ich
kann es aber auch lassen. Genauso kann ich die normale Speicherverwaltung
einsetzen, ich kann aber auch eine völlig neue in den Kernel integrieren
(theoretisch zumindest), ohne dass sich eine GC davon beeinflussen ließe,
und auch ohne dass sie es merken würde. Die Speicherverwaltung des
Betriebssystems (von mir aus auch "Win32-Speicherverwaltung") weiß nichts
von einer GC.
> a) Noch einmal: Ich beabsichtige und beabsichtigte in keiner Weise, mich
> mit der Speicherverwaltung, GC oder sonstigem von (VB).NET
> auseinanderzusetzen.
> b) Das spielt aus Sicht von >VB< sehr wohl eine Rolle für eine GC: Nur der
> Bereich, welcher die Kontrolle über den physikalischen Speicher hat, kann
> überhaupt bei einer GC involviert sein. Da VB keine Kontrolle darüber hat,
> fällt die Andreas von mir 'angedrohte' GC flach.
>
> > Jede Anwendung unter Win32, ob .NET, ob VB(classic), C(mit oder ohne ++
> oder
> > #), hat nur Zugriff auf einen *virtuellen* Speicher. Innerhalb dessen
> kann
> > man natürlich eine GC ausführen (und damit dem Programmierer die
> > Verantwortung für einen verantwortungsvollen Gebrauch der manchmal
> knappen
> > Resource "Speicher-Adressen" abnehmen). Man muss es aber nicht.
>
> Solange man, wie in VB, nicht zur Laufzeit neue Speicherzeiger für die
> Aufnahme virtueller Speicheradressen erstellen kann, wäre eine GC gänzlich
> sinnfrei (machbar natürlich...).
Was meinst Du jetzt mit "neue Speicherzeiger für die Aufnahme virtueller
Speicheradressen"? Neue Objekte anlegen und einen Zeiger auf diese erzeugen?
Oder Zeiger auf vorhandene Objekte duplizieren?
> > Eine GC hat
> > nichts damit zu tun, ob wir es mit virtuellen, physischen oder logischen
> > Adressen zu tun haben. Sie erlaubt es nur dem Programmierer, sich nicht
> > (unnötig?) mit dem Speichermanagement aus längst vergangenen Epochen
> > auseinanderzusetzen. Freigeben von Speicher? Lächerlich, dafür habe ich
> > meine GC ;-). Zurückgeben von Speicher an das System??? Warum "System",
> ich
> > laufe doch im User Mode ;-))).
>
> Du verstehst Garbage Collection m.E. einseitig: Offensichtlich beschränkst
> Du sie auf die >Freigabe< nicht mehr benötigten Speichers. Tatsächlich
> gehört dazu aber auch die 'Neustrukturierung' des Speichers, um
> Speicherlöcher zu vermeiden und den vorhandenen Speicher effizient zu
> nutzen. Und dabei ist sehr wohl von Bedeutung, ob eine Anwendung etc. die
> Kontrolle über den physikalischen Speicher hat, denn nur dann kann die
> Notwendigkeit einer Neustrukturierung überhaupt wahrgenommen geschweige
> denn durchgeführt werden.
Eine Neustrukturierung, also das Verschieben von Objekten im Speicher, lässt
sich auch hervorragend mit virtuellen Adressen erledigen. Alle Objekte
werden möglichst weit an den Anfang eines Speicherbereiches verschoben.
Dadurch muss das System nicht mehr auf die dahinter liegenden Adressen
zugreifen, also können die entsprechenden Seiten freigegeben werden.
Mit physischen Adressen hat das aber trotzdem nichts zu tun: Keine Anwendung
unter modernen Windows-Systemen hat sich dafür zu interessieren, wo (und ob)
ihre Daten im physischen Speicher liegen. Eine Anwendung interessiert sich
nur für den virtuellen Speicher. Es gibt nur eine einzige Instanz im System,
die mit physischen Adressen in Berührung kommt, und dass ist der Memory
Manager, der in ntoskrnl.exe implementiert ist. Dieser kennt als einziges
Modul die Zuordnung von virtuellen zu physischen Adressen, und es gibt keine
Möglichkeit für Anwendungen und auch nicht für anderen Code des Kernel Mode
(Win32k, Treiber, etc.), an diese Zuordnung zu kommen. Sie ist vollkommen
transparent.
>
> > Der erste Satz stimmt (nur, dass Du die logischen Adressen vergessen
hast
> > ;-)
>
> ???
Sorry, mein Fehler, ist Treiber-Entwickler-Slang und damit OT. Kurze
Erklärung (MSDN: Driver Development Kit\Kernel-Mode Driver
Architecture\Memory Management\Map Registers): "Devices vary in their
ability to access the system's full virtual address space. A device uses
addresses in logical (device) address space. Each HAL uses map registers to
translate a device or logical address to a physical address (a location in
physical RAM). For the device hardware, map registers perform the same
function that the MDL (and page table) performs for the software (drivers):
they translate addresses to physical memory. "
> > den zweiten verstehe ich nicht. Eine GC übernimmt das Sammeln von
> > Speicherleichen. Was hat das jetzt mit dem Mapping von
> > virtuellen/physischen/logischen Adressen zu tun? Dem Betriebssystem kann
> es
> > letztlich völlig egal sein, wer da mit dem Speicher herumspielt, und es
> > *ist* ihm egal.
>
> S.o.! Wie gesagt, bei der GC geht es nicht nur um die Freigabe nicht mehr
> verwendeten Speichers, sondern auch um die 'Neustrukturierung'. Dafür
> ist/wäre derjenige verantwortlich, der den physikalischen Speicher
> verwaltet.
s.o.
Viele Grüße,
Jan-Arne
"Thorsten Dörfler" <t.doerf...@bdsw.de> schrieb:
> > Auch VB hat einen GC.
>
> Nicht direkt. Das Dingen, was Du da meinst, wird von COM
> bereitgestellt und verwaltet. Kann also, als ein von Windows
> verwalteter GC betrachtet werden. VB hat damit weniger am Hut.
Ja, klar. Bei VB .NET ist es auch der GC von .NET und nicht von der
Programmiersprache.
;-)
> Außerdem wäre dabei auch noch die Frage, ob VB selbst eine eigene
> Referenzzählung hat (was ich für unwahrscheinlich halte) oder sie dem
> Betriebssystem überläßt (vgl. GlobalAlloc(), GlobalFree()).
jedes Objekt, dass man in VB-Code benutzen und einer Objekt-Variablen
zuweisen kann, hat eine IUnknown-Schnittstelle, implementiert also die
übliche COM-Referenzzählung. Das kannst Du z.B. gut sehen, wenn Du Dir
einmal meine Deassemblierung von Form_Load ansiehst:
00401927 mov dword ptr [Me],eax
EAX enthält die Adresse des Objektes, das Du mit Me ansprichst (hier die
Form).
0040192A mov edx,dword ptr [eax]
EDX erhält die Adresse der Verteilertabelle des COM-Objektes.
0040192C call dword ptr [edx+4]
Aufruf von IUnknown::AddRef.
Viele Grüße,
Jan-Arne
Einfach nur, daß man in VB zur Laufzeit keine Variablen erzeugen kann. Man
kann nur auf Variablen zurückgreifen, die man zur Entwurfzeit deklariert
hat.
> Eine Neustrukturierung, also das Verschieben von Objekten im Speicher,
lässt
> sich auch hervorragend mit virtuellen Adressen erledigen. Alle Objekte
> werden möglichst weit an den Anfang eines Speicherbereiches verschoben.
> Dadurch muss das System nicht mehr auf die dahinter liegenden Adressen
> zugreifen, also können die entsprechenden Seiten freigegeben werden.
Damit optimierst Du das Laden der Seiten, aber änderst nichts an der
Fragmentierung des Speichers. Hat also m.E. nichts mit GC zu tun.
> Mit physischen Adressen hat das aber trotzdem nichts zu tun: Keine
Anwendung
> unter modernen Windows-Systemen hat sich dafür zu interessieren, wo (und
ob)
> ihre Daten im physischen Speicher liegen. Eine Anwendung interessiert
sich
> nur für den virtuellen Speicher. Es gibt nur eine einzige Instanz im
System,
> die mit physischen Adressen in Berührung kommt, und dass ist der Memory
> Manager, der in ntoskrnl.exe implementiert ist. Dieser kennt als einziges
> Modul die Zuordnung von virtuellen zu physischen Adressen, und es gibt
keine
> Möglichkeit für Anwendungen und auch nicht für anderen Code des Kernel
Mode
> (Win32k, Treiber, etc.), an diese Zuordnung zu kommen. Sie ist vollkommen
> transparent.
Ich glaube, wir reden aneinander vorbei: Genau das sage ich doch auch! Nur
eine Instanz (von mir als das "Betriebssystem" oder die "Win32
Speicherverwaltung" bezeichnet), die über die physikalischen Adressen
'Aussagen' machen kann, infolge dessen auch nur eine Instanz, die eine
echte GC (jedenfalls so, wie ich sie verstehe) durchführen kann. Den
einzigen Part, denn eine Anwendung bei dieser GC übernehmen kann, ist es,
der Speicherverwaltung mitzuteilen, wann welcher Speicher nicht mehr
gebraucht wird.
Und alles, worum es ging, war, Andreas zu zeigen, warum meine Drohung mit
der angeblichen VB-GC Unsinn war.
> Einfach nur, daß man in VB zur Laufzeit keine Variablen erzeugen kann. Man
> kann nur auf Variablen zurückgreifen, die man zur Entwurfzeit deklariert
> hat.
ok, das ist klar. Wäre eine GC (gemeint ist: Finden von Speicherleichen und
Defragmentierung des Speichers) nicht auch in diesem Fall sinnvoll? Was
passiert, wenn eine Variable den Gültigkeitsbereich verlässt (von mir aus
auch so, dass die Runtime dies nicht just-in-time mitbekommt), das
zugehörige Objekt aber noch nicht freigegeben wurde? Eine GC könnte dies
erledigen. Oder was ist, wenn eine Variable zuerst auf ein Objekt zeigt,
dieses dann freigegeben wird, und sie auf ein neues, größeres, gesetzt wird?
Dieses größere passt nicht an die Stelle des alten kleineren Objektes im
virtuellen Speicher, also wird dieser fragmentiert. Auch hier könnte eine GC
die Objekte verschieben.
> Damit optimierst Du das Laden der Seiten, aber änderst nichts an der
> Fragmentierung des Speichers. Hat also m.E. nichts mit GC zu tun.
Warum ändere ich damit nicht die Fragmentierung? Wenn man einmal davon
ausgeht, dass auf alle Variablen gleich oft zugegriffen wird, dann macht es
einen riesigen Unterschied, ob diese über z.B. 1 MB oder nur über 8 KB
virtuellen Adressraum verteilt sind. Wenn sie über 1 MB verteilt sind, muss
das Betriebssystem diese 1 MB virtuellen Speicher immer im RAM halten (oder
bei einem Seitenfehler ins RAM holen), sie belegen also effektiv 1 MB
physischen Speicher. Wenn dieselben Daten aber von einem Modul in nur 8 KB
virtuellem Adressraum konzentriert werden (effektiv also der virtuelle
Speicher "defragmentiert" wird), dann müssen auch nur diese 8 KB im RAM
gehalten werden. Du sparst in diesem Beispiel also 1016 KB an physischem
Speicher ein, wenn Du die Fragmentierung des virtuellen Speichers
zurückschrauben kannst.
> Ich glaube, wir reden aneinander vorbei: Genau das sage ich doch auch! Nur
> eine Instanz (von mir als das "Betriebssystem" oder die "Win32
> Speicherverwaltung" bezeichnet), die über die physikalischen Adressen
> 'Aussagen' machen kann, infolge dessen auch nur eine Instanz, die eine
> echte GC (jedenfalls so, wie ich sie verstehe) durchführen kann. Den
> einzigen Part, denn eine Anwendung bei dieser GC übernehmen kann, ist es,
> der Speicherverwaltung mitzuteilen, wann welcher Speicher nicht mehr
> gebraucht wird.
Ich verstehe es immer noch nicht, sorry :-(((. Was haben physische Adressen
mit einer GC zu tun? Ich vermute, unsere Interpretation von
"Speicherfragmentierung" stimmt nicht überein. Meinst Du damit, dass
Objekte, die zwar im virtuellen Speicher linear angeordnet sind, dies im
physischen nicht sein müssen? Etwa, wenn sie über Seitengrenzen hinweg gehen
und eine der beiden Seiten ausgelagert und an anderer Stelle wieder
eingelagert wird?
Viele Grüße,
Jan-Arne
>> Unbuffered File I/O dürfte schneller sein als R/O-FileMapping (und
>> darauf baut auch die Idee des ganzen auf).
> das bezweifle ich. Ich gehe einfach mal davon aus, dass Du mit
> "unbuffered file I/O" ein CreateFile mit FILE_FLAG_NO_BUFFERING
> meinst. Das hat den Nachteil, dass Dich der Cache auch nicht beim
> Vorauslesen unterstützt. Du musst also immer einen Block Daten lesen
> und vergleichen, den nächsten lesen und vergleichen usw. Und
> zwischendurch natürlich warten, bis die Platte die Daten liefert.
Bei (E)IDE-Systeme wirkt sich eben just das Vorauslesen negativ auf die
Performance aus. Bei SCSI sieht es sicher anders aus.
> Wenn Du das Flag weglässt (oder ein FileMapping einsetzt), und von mir
> aus immer nur eine Seite (4 KB, oder ein vielfaches davon) pro Aufruf
> vergleichst, erkennt der Dateisystemcache ein Lesemuster. Er kann also
> weitere Daten von beiden Dateien anfordern und einlesen, während Du
> weiter "vorne" noch mit dem Vergleich beschäftigt bist.
Du meinst die "Read-Ahead"-Einstellungen im OS?
mir ist nicht ganz klar, wie das physikalisch funktionieren soll. Wenn
der Prozessor kontinuierlich auf eine RAM-Bank zugreift, wie soll die HD
"gleichzeitig" Daten dort hinein schreiben können, ohne den anderen
Prozess zu unterbrechen oder zu stören?
Ich kann mir dies mit dem SCSI-BUS und 2-kanal DDR-Ram vorstellen, aber
nicht mit den 08/15-Standardsystemen.
Viele Grüße,
Andreas
> [performance von repe cmpsd vs. simple loop]
> Könnte es sein, dass Deine Speicherbandbreite für Deinen Prozessor
> nicht ausreicht (P4 mit Einkanal-DDR-RAM oder so etwas)? Dann könnte
> es eventuell vorkommen, dass einige Optimierungen (spekulative
> Ausführung, günstige Sprungvorhersagen usw.) die Schleife schneller
> erscheinen lassen, weil der Prozessor bei einem einfachen
> "repe cmpsd" zu lange auf den Hauptspeicher warten muss.
Kann ich leider nicht beantworten. Meine Testmaschine ist ein P3 (866
MHz) mit 133MHz SDRAM. Also insofern schon denkbar, daß der Ram nicht
nachkommt. Effektive Zugriffszeiten hab ich leider nicht parat.
>> Wie gesagt, werde das demnächst nochmal besser austesten.
> Eventuell kannst Du auch auf verschiedenen Maschinen mit
> unterschiedlichen RAM-Konfigurationen testen? Ich meine damit so
> Dinge wie: Vergleich auf einem Ein-Kanal-DDR-System, auf einem
> Zwei-Kanal-DDR-System mit und ohne bestücktem zweiten Kanal usw.
> Auf das Ergebnis bin ich schon jetzt gespannt.
Geht momentan leider nicht. Hab hier nur Maschinen mit SDRAM bzw eine
mit 2K DDR-RAM stehen, diese aber als Server. Vielleicht kann ich
während der nächsten Wartungsarbeiten mal nen Test laufen lassen.
Viele Grüße,
Andreas