Variable als Integer und als String(oder Binär <> Hex)

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Variable als Integer und als String(oder Binär <> Hex)

Beitrag von hubblec4 »

Hallo Lazarusgemeinde

Ich möchte für Matroska die Specs nochmal nachprogrammieren und dort gibt es Felder die als Interger deklariert sind.
Liest man solche Felder direkt aus einer Matroska Datei aus erhält man logischerweise einen Integer.
Den Wert kann man einfach in einer Variablen speichern.

Allerdings wird dann sehr oft dieser Interger als String benötigt. Zum anzeigen in der GUI und sogar als ausgabe in einer XML Datei.

Klar könnte ich wann immer der Integer als String benötigt wird, ein "IntToStr()" ausführen. Aber dies wäre sehr sehr oft (in der GUI beim VTV zum beispiel).

Würde es sinn machen ein Record oder Klasse zu verwenden wo man eine Int-var und eine String-var hat?
Beim setzen des Integer Wertes wird dann automatisch der String-Wert erzeugt und umgekehrt.

Oder gibt es da noch bessere Möglichkeiten?
Zuletzt geändert von hubblec4 am Mo 30. Sep 2019, 15:22, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Variable als Integer und als String

Beitrag von wp_xyz »

Du müsstest schon SEHR VIELE Integer haben, um das in der Geschwindigkeit zu merken.

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String

Beitrag von hubblec4 »

OK, also meinst du ich sollte da immer nach String umwandeln für Anzeigen in der GUI. Und beim parsen einer XML die String-Zahl in Integer umwandeln.

Was ist dann aber mit Elementen die als Binary deklariert sind.
Da habe ich bis jetzt eine variable als String definiert und wandel alle Bytes in einen Hex String um. Das ist dann schon aufwendiger als nur von Int-nach-String.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Variable als Integer und als String

Beitrag von Warf »

Wahrscheinlich geht das konvertieren von Int nach String schneller als das zeichnen des GUI's nachdem du den wert irgendwo reinschreibst. Mach dir darum keine gedanken. Solltest du massive performanceprobleme bekommen kannst du nochmal drüber nachdenken, aber vor erst machs einfach mal.

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Variable als Integer und als String

Beitrag von wp_xyz »

hubblec4 hat geschrieben:Was ist dann aber mit Elementen die als Binary deklariert sind.
Da habe ich bis jetzt eine variable als String definiert und wandel alle Bytes in einen Hex String um.

Speichere diese lieber in einem Array of Byte (TBytes) ab. An Daten, die als "string" gespeichert sind, kann FPC leicht Konvertierungen vornehmen, so dass die Original-Bytefolge zerstört werden kann.

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String

Beitrag von hubblec4 »

wp_xyz hat geschrieben:
hubblec4 hat geschrieben:Was ist dann aber mit Elementen die als Binary deklariert sind.
Da habe ich bis jetzt eine variable als String definiert und wandel alle Bytes in einen Hex String um.

Speichere diese lieber in einem Array of Byte (TBytes) ab. An Daten, die als "string" gespeichert sind, kann FPC leicht Konvertierungen vornehmen, so dass die Original-Bytefolge zerstört werden kann.


Ja, TBytes wäre auch meine erste wahl für diese art von Elementen. Aber dort wäre dann eine Umwandlung von TBytesToHexString notwenig und das denke ich mal ist schon etwas mehr Aufwand als IntToStr();

Desweiteren gibt es noch Zeit-Elemente die als Integer deklariert sind und Nanosekunden bedeuten und müssen dann in einen Timestamp umgewandelt werden (für die Anzeige in der GUI und im XML) als: Beispiel 1000000000 = 1 sekunde = 00:00:01.000000000.
Hierzu habe ich bis jetzt zwei variablen definiert einmal als Int64 und eine als String;

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von hubblec4 »

Ich habe mal doch eine Klasse für mein Binary-HexString gebastelt und wollte fragen was ihr davon haltet.

Code: Alles auswählen

 
type
 
 TBinary                = class
  strict private        // procedures
   (* setter *)
   procedure SetHex(const hex: String);
   procedure SetBytes(const bytes_: TBytes);
 
  private               // vars
   FBytes               : TBytes;
   FHex                 : String;
   //FText               : String;
  public                // procedures
   constructor Create;
   destructor Destroy; override;
 
   function Copy_Binary(const CopyTo: TBinary = nil): TBinary;
 
  public                // properties
   property Hex: String read FHex write SetHex;
   property Bytes: TBytes read FBytes write SetBytes;
 end;
 
 
 
implementation
 
(* TBinary *)
 
(*============ strict private ================================================*)
 
(* setter *)
 
// Set Hex
procedure TBinary.SetHex(const hex: String);
var
  b,l: Integer;
begin
  FHex:=hex;
  if FHex = '' then
  begin
   SetLength(FBytes,0);
   Exit;
  end;
  l:=Length(FHex) div 2;
  SetLength(FBytes,l);
  for b:=0 to l -1 do
  FBytes[b]:=StrToInt('$'+FHex[b*2]+FHex[b*2+1]);
end;
 
// Set Bytes
procedure TBinary.SetBytes(const bytes_: TBytes);
var
  b: Integer;
begin
  FBytes:=Copy(bytes_,0,MaxInt);
  FHex:='';
  for b:=0 to High(FBytes) do
  FHex:=FHex + IntToHex(FBytes[b],2);
end;
 
 
(*============ public ========================================================*)
 
// Constructor
constructor TBinary.Create;
begin
  inherited Create;
end;
 
// destructor
destructor TBinary.Destroy;
begin
  SetLength(FBytes,0);
  FHex:='';
  inherited Destroy;
end;
 
// Copy TBinary
function TBinary.Copy_Binary(const CopyTo: TBinary = nil): TBinary;
begin
  if CopyTo = nil then
  Result:=TBinary.Create
  else
  Result:=CopyTo;
  Result.FBytes:=Copy(FBytes,0,MaxInt);
  Result.FHex:=FHex;
end;
 

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von wp_xyz »

Also ehrlich gesagt, ich halte das Doppel-Gemoppel für übertrieben. Wie oft wird denn die String-Konvertierung für ein und dasselbe Element aufgerufen? Wenn das nur wenige Male ist, schaffst du dir damit einen gewaltigen Overhead an Verwaltung und Speicherbedarf, ohne dass man irgendwas an Geschwindigkeitsgewinn merkt.

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von hubblec4 »

Also so ein Binary Werte wird in ein Edit Feld geladen wo dann der HexString drin steht.
Der User kann diesen String verändern, dabei wird auf gültigkeit geprüft.
In einem VTV wird dieser Wert in einer Spalte dargestellt und dort wird GetText sehr sehr oft aufgerufen. Schon wenn man mit der Mouse über den Node geht wird GetText 10 mal oder so aufgerufen.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von Warf »

hubblec4 hat geschrieben:In einem VTV wird dieser Wert in einer Spalte dargestellt und dort wird GetText sehr sehr oft aufgerufen. Schon wenn man mit der Mouse über den Node geht wird GetText 10 mal oder so aufgerufen.

Code: Alles auswählen

program test;
{$mode objfpc}{$H+}
uses
sysutils;
 
var
  s: LongWord;
  i: Integer;
  j: LongInt;
  str: array of String;
begin
  SetLength(str, 10000001);
  for i:=0 to 10000000 do
    str[i]:= IntToStr(Random(2000000000));
  s := GetTickCount;
  for i:=0 to 10000000 do
    j:= StrToInt(str[i]);
  WriteLn(GetTickCount-s);
  ReadLn;
end.   

10 millionen strToInt benötigen bei mir knapp 500 millisekunden. Das sind im durchschnitt 50 nanosekunden pro IntToStr aufruf. Mach dir darüber mal keine gedanken. Mach dir um performance optimierung erst gedanken wenn du die performance brauchst (also wenn du merkst das es zu langsam läuft). Sonst optimierst du sachen die eigentlich gar kein problem sind. 10 mal GetText ist für einen computer nix. Mit 3GHz kann deine CPU 3000000000 atomare operationen pro sekunde ausführen. 10 aufrufe sind nichts.

Und wenn du dir über performance gedanken machst, denk mal drüber nach wann und wo Speicher alloziiert werden muss, denn das ist tatsächlich teuer. Bei deinem Code z.B.

Code: Alles auswählen

'$'+FHex[b*2]+FHex[b*2+1]

Wird hier jedesmal ein neuer String erzeugt und

Code: Alles auswählen

FHex:=FHex + IntToHex(FBytes[b],2);

hier für jede iteration der string geresized. Das ist viel teurer als jeder StrToInt aufruf (obwohl ich da auch der meinung bin das, solang du die funktion nicht ein paar millionen mal hintereinander aufrufst das kein merkliches problem sein sollte).

Wenn du das effizient machen willst würde ich es so machen:

Code: Alles auswählen

function HexCharToHalfByte(const c: Char): Byte; inline;
begin
  case c of
  '0'..'9': Result := ord(c) - ord('0');
  'A'..'F': Result := ord(c) - ord('A') + 10;
  else raise EFormatError.Create('Not a Hex char');
  end;
end;
 
function HexToBytes(Hex: String): TBytes;
var
  i: Integer;
begin
  Hex := Hex.ToUpper; // sicher gehen das es auf jeden fall uppercase ist
  // Speicheralloziieren
  SetLength(Result, Hex.Length div 2);
  for i:=0 to High(Result) do
    Result[i] := (HexCharToHalfByte(Hex.Chars[i*2]) shl 4) + HexCharToHalfByte(Hex.Chars[i*2 + 1]);
end;
 
function HalfByteToHex(const hb: Byte): Char; inline;
begin
  case hb of
  0..9: Result := chr(hb + ord('0'));
  10..15: Result := chr(hb - 10 + ord('A'));
  else raise EFormatError.Create('Not a Halfbyte');
  end;
end;
 
function BytesToHex(const Bytes: TBytes): String;
var
  i: Integer;
begin
  SetLength(Result, Length(Bytes) * 2);
  for i:=0 to High(Bytes) do
  begin
    Result[i*2 + 1] := HalfByteToHex(Bytes[i] shr 4);
    Result[i*2 + 2] := HalfByteToHex(Bytes[i] And $0F);
  end;
end


Aber wie gesagt, wahrscheinlich ist das ein kompletter overkill, da das sehr wahrscheinlich eh keine relevanten performanceprobleme geben wird

PS:

Code: Alles auswählen

  SetLength(FBytes,0);
  FHex:='';

Ist unnötig, dynamische arrays und Strings werden komplett vom Compiler gehandled, und solang du keine bösen schweinereien machst (wie mit Move, Fillchar, etc. zu arbeiten) wird der speicher von selbst aufgeräumt. Diese beiden zeilen im Destruktor haben also absolut keinen Effekt. Da es noch dazu keinen grund gibt nur für den inherited call einen Konstruktor und Destruktor zu schreiben (da wenn die fehlen würden eh der von der Superklasse direkt aufgerufen wird), sind die beiden funktionen bei dir einfach nur unnötig, und du kannst einfach beide rauswerfen.
Und ich persönlich würde die methode Copy_Binary zu AssignTo umbenennen, um die Pascal typischen Namenskonvention einzuhalten (eventuell macht es für dich sogar direkt sinn von TPersitent abzuleiten, wenn dein Objekt kopierbar ist)

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von hubblec4 »

Das mit dem Integer und String ist völlig klar nun, Danke.
Ebenso das umwandeln von Hex nach TBytes und zurück. Hatte diese Umwandlungen von diversen Webseiten mir abgeschaut.
Deine 4 Funktionen sind sicher besser und ich kann die ja nehmen.

Also keine doppelte Datenhaltung, dafür lieber immer bissl rechnen.

Wegen dem Constructor und destructor:
Wenn da nichts drin steht ausser inherited dann gleich weglassen.

Ich hatte bei meinem Programm beobachtet das es immer mehr Speicher will je mehr man ändert/hinzufügt aber beim löschen wird nichts freigegeben.

Tatsächlich arbeite ich mit Move(); da viele Objecte (Kapitel und anderes) verschoben werden kann. Sogar ausschneiden und wo anderes einfügen ist möglich.
Wenn ich also Move() benutzte muss ich selber aufräumen?

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von Warf »

hubblec4 hat geschrieben:Das mit dem Integer und String ist völlig klar nun, Danke.
Ebenso das umwandeln von Hex nach TBytes und zurück. Hatte diese Umwandlungen von diversen Webseiten mir abgeschaut.
Deine 4 Funktionen sind sicher besser und ich kann die ja nehmen.


Hab grade gesehen es gibt auch HexToBin und BinToHex die praktisch genau das machen was meine funktionen machen, bis auf das alloziieren des Buffers.

hubblec4 hat geschrieben:Also keine doppelte Datenhaltung, dafür lieber immer bissl rechnen.

Genau, wenn die Responsetime zu lang wird, kannst du immernoch drüber nachdenken

hubblec4 hat geschrieben:Ich hatte bei meinem Programm beobachtet das es immer mehr Speicher will je mehr man ändert/hinzufügt aber beim löschen wird nichts freigegeben.

Klassen, new und GetMem müssen per hand freigegeben werden, Strings und Arrays sind Referenzgezählt, da macht der Compiler das ganz von selbst

hubblec4 hat geschrieben:Tatsächlich arbeite ich mit Move(); da viele Objecte (Kapitel und anderes) verschoben werden kann. Sogar ausschneiden und wo anderes einfügen ist möglich.
Wenn ich also Move() benutzte muss ich selber aufräumen?

Wenn du auf den daten movest dann ist das kein problem, Problematisch ists nur wenn du den Pointer movest. Z.B.

Code: Alles auswählen

type TTestRec = record
  Str: String;
end;
...
var r1, r2: TTestRec;
...
  Move(r1, r2, sizeOf(TTestRec);

An dieser stelle hast du den compilercheck für Str umgangen, das Move dumm die bytes kopiert, und nicht wissen kann das es ein string ist und damit der String bearbeitet werden muss. ABER, dann musst du nicht nur den speicher selbst aufräumen, sondern dann ist dein Programm in einem Kapputen state. Intern denkt es es gäb nur eine Referenz auf Str, aber es gibt 2 (da Move kopiert ohne den Referenzzähler zu eröhen). Was dann passiert ist entweder: R1 wird "zerstört" und damit Str's refcount dekrementiert, da sie auf 1 war wird der speicher freigegeben. Das darf aber nicht geschehen weil R2 noch einen Pointer drauf hat. Spätestens wenn R2 dann zerstört wird und der versucht bereits freien speicher nochmal zu Free'n kracht es. Das musst du auf jeden fall vermeiden.
Was man machen kann ist sowas

Code: Alles auswählen

type TTestRec = record
  Str: String;
end;
...
var r1, r2: TTestRec;
...
  Move(r1, r2, sizeOf(TTestRec);
  FillChar(r1, SizeOf(TTestRec), #00);

Das überschreibt den string im nachhinein mit 0, sodass die referenz gelöscht wird ohne das der RefCounter ins spiel kommt, damit stellst du sicher das es keine zweite referenz gibt. Das ist aber extrem unschön, und wenn es kracht, bekommst du einen Fehler ohne Source Zeile (da es in den tiefen der FPC internals kaputt geht). Daher Strings und Arrays immer mit := rüberschieben, also

Code: Alles auswählen

type TTestRec = record
  Str: String;
end;
...
var r1, r2: TTestRec;
...
  r2 := r1;

Wenn du garantieren willst das du eine eigene Kopie des Arrays hast, kannst du auch SetLength(array, Length(Array)); aufrufen, also

Code: Alles auswählen

type TTestRec = record
  Str: String;
end;
...
var r1, r2: TTestRec;
...
  r2 := r1;
  SetLength(r2.Str, r2.Str.Length);

Denn bei := wird nur eine Referenz kopiert, da Strings Lazy copy machen, wird der tatsächliche String erst kopiert wenn du was dran änderst. SetLength garantiert das der Referenzcounter nach dem SetLength Command immer 1 ist, d.H. wenn zwei referenzen auf den selben string zeigen, wird nach SetLength der String kopiert, sodass du zwei verschiedene strings mit je einer referenz hast.
Für das direkte Kopieren kannst du aber auch einfach Copy nehmen(was du ja schon benutzt). Der SetLength trick ist vor allem für Felder eines Records brauchbar.

Was hingegen kein problem ist ist sowas:

Code: Alles auswählen

var s1, s2: String;
...
SetLength(s2, s1.Length div 2);
Move(s1[1], s2[1], s1.Length div 2);

Da du hier nur auf den Inhalt zugreifst.

Alles was ich oben zu Strings geschrieben hab gilt übrigens auch für dyn arrays

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Variable als Integer und als String(oder Binär <> Hex)

Beitrag von hubblec4 »

HexToBin und BinToHex kenne ich auch schon und nutzte das auch.

Was das Move() angeht, denke ich habe ich dich falsch verstanden. Ich nutze Move was direkt an einer Klasse sitzt. Zum beispiel bei einer TObjectList gibt es .Move(old,new) ich denke das ist nicht das gleiche wie die eigenständige Move() procedure.

Für das Kopieren mit dem SetLenght hatte ich erst vor kurzem gelesen.


Klassen gebe ich immer selbstständig frei, hatte das so gelesen. Dennoch hatte sich der Speicherbedarf danach nicht reduziert.
Hatte dann ein anderes Programm geschrieben und dort im destrctor immer alles "geleert" und dadurch wurde dann auch immer wieder Speicher frei.
Daher ging ich davon aus man muss sich darum selbst kümmern.

Hast mir auf jedenfall schon wieder viel geholfen.

Antworten