Autor Beitrag
rushifell
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 306
Erhaltene Danke: 14



BeitragVerfasst: Mi 16.02.11 07:02 
Hallo,

ich möchte in einem MemoryStream an einer beliebigen Position Daten entfernen bzw. einfügen. Nun habe ich dafür folgende Funktion geschrieben:

Vorsicht: Beim Einfügen, müssen die entsprechenden Daten anschließend in den Stream geschrieben werden.
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
{
Zum Einfügen bzw. Entfernen von Daten aus einem MemoryStream
Offset = Position, ab der die Daten eingefügt bzw. entfernt werden sollen
Resize = Länge der eingefügten bzw. entfernten Daten
Resize<0 => Entfernen
Resize>0 => Einfügen
}

Function MStream_InsertData(MStream:TMemoryStream; Offset:Integer; Resize:Integer):Boolean;
Var
    NewPos, ActPos,
    Numread,
    BufferSize:Integer;
    Buffer:Array[0..4096of Byte; //Größe kann beliebig verändert werden
Begin
Result:=False;
IF Assigned(MStream) then
   BEGIN
    Result:=True;
    MStream.Seek(Offset,soFromBeginning);

    IF Resize<0 then //Stream wird verkleinert (Entfernen)
       BEGIN
        ActPos:=MStream.Position;
        NewPos:=ActPos-Resize;

        Repeat
         MStream.Seek(NewPos,soFromBeginning);
         Numread:=MStream.Read(Buffer,Sizeof(Buffer));
         MStream.Seek(ActPos,soFromBeginning);
         MStream.Write(Buffer,Numread);

         ActPos:=ActPos+Numread;
         NewPos:=NewPos+Numread;
        Until Numread<Sizeof(Buffer);

        MStream.SetSize(MStream.Size+Resize); //Neue Größe setzen

       END ELSE //If Resize<0

    IF Resize>0 then //Stream wird vergrößert (Einfügen)
       BEGIN
        ActPos:=MStream.Size;
        NewPos:=ActPos+Resize;
        MStream.SetSize(NewPos); //Neue Größe setzen
        Resize:=MStream.Size-Resize-Offset;

        Repeat
         IF SizeOf(Buffer)<Resize then
            BufferSize:=SizeOf(Buffer) else
            BufferSize:=Resize;

         Resize:=Resize-BufferSize;
         ActPos:=ActPos-BufferSize;
         NewPos:=NewPos-BufferSize;
         MStream.Seek(Actpos,soFromBeginning);
         MStream.Read(Buffer,BufferSize);
         MStream.Seek(NewPos,soFromBeginning);
         MStream.Write(Buffer,BufferSize);
        Until Resize=0;

       END//If Resize>0

   END//If Assigned(MStream)
End;


Da ich stets kleine Datenmengen bis zu maximal 50 kb verwende, funktioniert das recht gut, bei Daten im Megabyte-Bereich, wird das ganze doch recht langsam.

Ist es vielleicht doch sinnvoller, einen temporären Memorystream zu verwenden, die Daten bis zum Offset in diesen temporären Stream zu kopieren, die neue Daten einzufügen, dann am Offset weiterzukopieren und anschließend dem ursprünglichen Memorystream den temporären Stream zuzuweisen?

Was denkt ihr darüber? Oder gibt es noch andere Möglichkeiten?

Gruß
rushifell


Moderiert von user profile iconNarses: Topic aus Sonstiges (Delphi) verschoben am Mi 16.02.2011 um 09:37
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 16.02.11 08:12 
Dass das langsam wird, liegt unter anderem an deiner Arrayvariablen (und an deiner Buffergröße vermutlich auch). Denn die liegt komplett auf dem Stack und da sind 4k schon extrem...

Nimm lieber einen Pointer und reserviere dir mit GetMem (+ FreeMem) direkt Speicher.

Wie es am performantesten geht, hängt auch davon ab was du mit den Daten eigentlich machst. (In Datei speichern, darauf zugreifen, ...)
Vielleicht gibt es insgesamt noch einen besseren Weg. ;-)
Horst_H
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1654
Erhaltene Danke: 244

WIN10,PuppyLinux
FreePascal,Lazarus
BeitragVerfasst: Mi 16.02.11 14:45 
Hallo,

ich habe mal 100000 Strings der Länge 12 eingefügt:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
 Einfügen davor    neue Version 00.00.09.223
 Einfügen davor    orig Version 00.00.16.637
 Einfügen dahinter neue Version 00.00.00.044
 Einfügen dahinter orig Version 00.00.00.022
 Anzahl Einfügungen : 100000
 Verschobene Mb     : 59999


Es liegt also an dem ständigen davor einfügen. Es werden 60 GB in 9.22 sec verschoben.
Das ist nicht wenig.
Wie so oft, wenn man vorher weiß, wieviel Platz man baucht und an welchen Positoinen, nachher was hin muss , dann sollte man tunlichst vorher dafür den Platz schaffen.
Das 100000-fache verlängern des Streams machte überhaupt keine Probleme, da wird selten kopiert.

Resize mit negativen Zahlen habe ich jetzt nicht getestet.

Meine Variantemit Move innerhalb des memorystreams
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
type
  tbArr = array[0..0of char;
  tpbArr = ^tbArr;

Function MStream_InsertData2(var MStream:TMemoryStream; Offset:Int64; Resize:Int64):Boolean;
Var
  movelength : Int64;
  pm : tpbarr;
Begin
IF resize = 0 then begin
  result := true;
  EXIT;
  end;

Result:=False;
IF Offset < 0 then
  EXIT;

IF (Offset + Resize) < 0 then
  begin
  //EXIT;
  //oder
  MStream.SetSize(0);
  result := true;
  EXIT;
  end;

IF Assigned(MStream) then
  BEGIN
  Result:=True;
  movelength := (ms.Size-1)-Offset;
  //Stream wird verkleinert (Entfernen)
  IF Resize<0 then BEGIN
    pm := MStream.Memory;// hier vor setsize
    IF Movelength >0 then
      move(pm^[Offset],pm^[Offset+resize],movelength);
    MStream.SetSize(MStream.Size+Resize);
    END
  ELSE BEGIN
    MStream.SetSize(MStream.Size+Resize);
    pm := MStream.Memory;// hier nach setsize
    IF Movelength >0 then ;
      move(pm^[Offset],pm^[Offset+resize],movelength);
//    fillchar(pm^[Offset],resize,#0);
    END;

   END//If Assigned(MStream)
End;

procedure IncString(var s: string);
// Spielerei , ein Zählerstring im eins erhöhen
// man kann den Memorystream -> string -> memo.lines.text zuweisen. 
var
  i : integer;
begin
  i := length(s)-2// wegen der #13#10 am Ende
  while (i > 0do begin
    inc(s[i]);
    IF s[i] <= '9' then
      exit
    else
      s[i] := '0';
    dec(i);
    end;
end;

procedure TForm2.Button1Click(Sender: TObject);
const
  Warte = 100;
  count = 100000;
  BoolAusgabePos : array[boolean] of string[9] = ('davor    ','dahinter ');
  BoolAusgabeVer : array[boolean] of string[12] = ('neue Version','orig Version');
var
  T0 :TDateTime;
  l,ofs : Int64;
  memotext : string;
  i : integer;
  dahinter,testvar : boolean;
begin
  memo1.Font.Name := 'lucida Console';
  memo1.Font.Color :=   clWindowText;
  Memo1.Lines.clear;


  ms:= TMemorystream.create;
  try
    For dahinter := false to true do
      For Testvar :=false to true do
        begin
        memotext := '0000000000'+#13#10;
        l := length(memotext);
        ms.SetSize(0);
        ofs := 0;
        t0 := time;
        For i := 1 to count do begin
          IF dahinter then
            begin
            IF testvar then
               MStream_InsertData(ms,Ofs,l)
             else
               MStream_InsertData2(ms,Ofs,l);
            ms.position:= Ofs;
            end
          else
            begin
            IF testvar then
               MStream_InsertData(ms,0,l)
             else
               MStream_InsertData2(ms,0,l);
            ms.position:= Ofs;
            end;
          incString(memotext);  // statt memotext := Format('%.10d'+#13#10,[i]);
          ms.Write(memotext[1],l);
          inc(Ofs,l);
          end;
      t0 := time-t0;
     memo1.Lines.Add(' Einfügen '+BoolAusgabePos[dahinter]+ BoolAusgabeVer[TestVar]
                      +FormatDateTime(' hh.nn.ss.zzz',t0));
     memo1.update;
     end;
    memo1.Lines.Add(' Anzahl Einfügungen : '+IntToStr(count));
    memo1.Lines.Add(' Verschobene Mb     : '+IntToStr( (l *( (Int64(count)*(count-1)) shr 1div 1000000 )));

  finally
    FreeAndNil(ms);
  end;
end;


Gruß Horst

Für diesen Beitrag haben gedankt: rushifell
rushifell Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 306
Erhaltene Danke: 14



BeitragVerfasst: Mi 16.02.11 20:08 
Danke für Eure Antworten!

Ich möchte für ein Spiel "aufgezeichnete" Demos in einer Datei speichern. Für jeden Level kann man eine Demo aufzeichnen, speichern und natürlich anschauen. Also oft gespeichert wird in der Regel nicht. Die Demo-Daten werden von mir vor dem Speichern gepackt und sind in diesem Zustand im Durchschnitt etwa 300 Bytes groß (plus, minus natürlich). Ich erwarte eine Dateigröße von 10-20 kb. Da die Daten in der Level-Reihenfolge gespeichert werden, muss ich eben "einfügen" oder "entfernen.

Da stets auf die Demos und deren Status zugriffen wird, habe ich statt einem FileStream einen MemoryStream gewählt.

Ich habe mir schon überlegt, ob ich eine fixe Größe für die Daten (z.B. 500 Bytes) festlege, eine variable Größe wäre mir jedoch lieber.

Ich habe übrigens einen großen Fehler beim Testen gemacht. Das Erstellen des Teststreams hat so lange gedauert. Das Einfügen an sich dauert bei Dateien um die 10 Megabyte zwar etwas, ist aber immer noch weniger als 1 Sekunde.

@jaenicke: wie groß würdest Du den Buffer wählen? In der Regel nehme ich 1 bis 2 kb, ich dachte nur, dass es bei einem größeren Buffer etwas schneller ist.

@Horst:
Danke für Deine Variante. Die ist im Durchschnitt schneller und natürlich eleganter. In Zeile 31 ist ein kleiner Fehler. Hier die Korrektur:

ausblenden Delphi-Quelltext
1:
movelength := (ms.Size)-Offset;					

Außerdem gehört der Strichpunkt in Zeile 42 nach dem then nicht dort hin.

Vielen Dank!
Gruß