Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2023-07

ShortString still relevant today?

5 messages · 3 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Hairy Pixels

I've been exploring the string types and I'm curious now, does the classic Pascal "ShortString" even make sense anymore on modern computers? I'm running tests and I can't seem to find a way in which AnsiString overall performs worse than ShortString.

Are there any examples where AnsiString is worse? I think if you passed strings around lots that would trigger the ref counting and InterlockedExchange (I saw this in my own code before and it unnerved me) but that's been hard to test.

Regards,
Ryan Joseph

Reply by email

Nikolay Nikolov
to Hairy Pixels
Hairy Pixels wrote
I've been exploring the string types and I'm curious now, does the classic Pascal "ShortString" even make sense anymore on modern computers? I'm running tests and I can't seem to find a way in which AnsiString overall performs worse than ShortString. Are there any examples where AnsiString is worse? I think if you passed strings around lots that would trigger the ref counting and InterlockedExchange (I saw this in my own code before and it unnerved me) but that's been hard to test.

ShortString is mainly for compatibility with Turbo Pascal, not for performance, IMHO. Although the FPC compiler itself still uses ShortString for performance reasons (I think the main advantage is the avoidance of the implicit try..finally blocks, needed for ansistrings). It might be interesting to benchmark the compiler with AnsiStrings instead of ShortStrings and see if there's a performance difference. But even if there is, a compiler is an extreme example. For 99% of the programs, performance impact of AnsiString is not an issue. I put {$H+} in almost all my new programs. I'd say that in 99% of the legit use cases, ShortString is used and needed for compatibility with legacy code, not for performance. Switching legacy code to {$H+} doesn't always work and may need additional fixes. Old code does things like S[0] := x instead of SetLength(S, x), etc. It also does uglier things, like FillChar() or Move() directly to/from string memory, or saves ShortStrings to files, as a part of a record, etc.

Nikolay

Reply by email

Hairy Pixels
to Nikolay Nikolov
On Jul 4, 2023, at 10:11 AM, Nikolay Nikolov via fpc-pascal wrote: ShortString is mainly for compatibility with Turbo Pascal, not for performance, IMHO. Although the FPC compiler itself still uses ShortString for performance reasons (I think the main advantage is the avoidance of the implicit try..finally blocks, needed for ansistrings). It might be interesting to benchmark the compiler with AnsiStrings instead of ShortStrings and see if there's a performance difference. But even if there is, a compiler is an extreme example. For 99% of the programs, performance impact of AnsiString is not an issue. I put {$H+} in almost all my new programs. I'd say that in 99% of the legit use cases, ShortString is used and needed for compatibility with legacy code, not for performance. Switching legacy code to {$H+} doesn't always work and may need additional fixes. Old code does things like S[0] := x instead of SetLength(S, x), etc. It also does uglier things, like FillChar() or Move() directly to/from string memory, or saves ShortStrings to files, as a part of a record, etc.

One thing I can think of now is that adding an AnsiString to a record or class makes that type "managed" and so it needs extra finalization when going out of scope. Static arrays will need to finalize their members too and the RTL has to do extra work in the lists classes to ensure this happens , which bloats the generic container types.

Regards,
Ryan Joseph

Reply by email

Hairy Pixels
to Hairy Pixels

Here is my test unit I'm playing with. It's crude but can anyone suggest what other things I could test?

I'm playing with a string pointer also to see how ref counting/finalization plays in. Making your own managed typed using management operators is not tested but I'm sure it will be terrible compared to everything else.

* test_short_string time: 143ms
* test_ansi_string time: 115ms
* test_mem_string time: 115ms

* test_short_string_record time: 165ms
* test_ansi_string_record time: 75ms
* test_mem_string_record time: 47ms

* test_short_string_mutate time: 203ms
* test_ansi_string_mutate time: 181ms

=======================================================================================

{$mode objfpc}
program string_test;
uses
  SysUtils, DateUtils;
const
  ITERATIONS = 1000 * 1000;
  TEST_STRING = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit';
type
  TTestProc = procedure;
procedure test_mem_string;
  procedure do_pass(const s: PString; len: Integer);
  var
    c: Char;
    i: Integer;
  begin
    for i := 0 to len - 1 do
      c := s^[i];
  end;
var
  s: PString;
  i, len: Integer;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      len := Length(TEST_STRING);
      s := GetMem(len);
      s^ := TEST_STRING;
      do_pass(s, len);
      FreeMem(s);
    end;
end;
procedure test_short_string;
  procedure do_pass(const s: ShortString);
  var
    c: Char;
    i: Integer;
  begin
    for i := 0 to length(s) - 1 do
      c := s[i];
  end;
var
  s: Shortstring;
  i: Integer;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      s := TEST_STRING;
      do_pass(s);
    end;
end;
procedure test_ansi_string;
  procedure ansi_string_pass(const s: AnsiString);
  var
    c: Char;
    i: Integer;
  begin
    for i := 0 to length(s) - 1 do
      c := s[i];
  end;
var
  s: AnsiString;
  i: Integer;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      s := TEST_STRING;
      ansi_string_pass(s);
    end;
end;
procedure test_ansi_string_mutate;
var
  i, j: Integer;
  s1, s2: AnsiString;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      s1 := TEST_STRING;
      s2 := s1 + IntToStr(i);
      for j := 1 to length(s2) - 1 do
        s2[j] := 'x';
    end;
end;
procedure test_short_string_mutate;
var
  i, j: Integer;
  s1, s2: ShortString;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      s1 := TEST_STRING;
      s2 := s1 + IntToStr(i);
      for j := 1 to length(s2) - 1 do
        s2[j] := 'x';
    end;
end;
procedure test_short_string_record;
type
  TMyRecord = record
    a: ShortString;
    b: ShortString;
    c: ShortString;
  end;
function do_pass(rec: TMyRecord): TMyRecord;
begin
  result := rec;
end;
var
  i: Integer;
  r: TMyRecord;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      r.a := TEST_STRING;
      r.b := TEST_STRING;
      r.c := TEST_STRING;
      do_pass(r);
    end;
end;
procedure test_ansi_string_record;
type
  TMyRecord = record
    a: AnsiString;
    b: AnsiString;
    c: AnsiString;
  end;
function do_pass(rec: TMyRecord): TMyRecord;
begin
  result := rec;
end;
var
  i: Integer;
  r: TMyRecord;
begin
  for i := 0 to ITERATIONS - 1 do
    begin
      r.a := TEST_STRING;
      r.b := TEST_STRING;
      r.c := TEST_STRING;
      do_pass(r);
    end;
end;
procedure test_mem_string_record;
type
  TMyRecord = record
    a: PString;
    b: PString;
    c: PString;
  end;
function do_pass(rec: TMyRecord): TMyRecord;
begin
  result := rec;
end;
var
  i: Integer;
  r: TMyRecord;
  len: Integer;
begin
  len := Length(TEST_STRING);
  for i := 0 to ITERATIONS - 1 do
    begin
      r.a := GetMem(len);
      r.b := GetMem(len);
      r.c := GetMem(len);
      r.a^ := TEST_STRING;
      r.b^ := TEST_STRING;
      r.c^ := TEST_STRING;
      do_pass(r);
    end;
end;
procedure run_test(name: String; test: TTestProc);
var
  startTime: Double;
begin
  startTime := Now;
  test;
  writeln('* ', name,' time: ', MilliSecondsBetween(Now, StartTime), 'ms');
end;
begin
  run_test('test_short_string', @test_short_string);
  run_test('test_ansi_string', @test_ansi_string);
  run_test('test_mem_string', @test_ansi_string);
  run_test('test_short_string_record', @test_short_string_record);
  run_test('test_ansi_string_record', @test_ansi_string_record);
  run_test('test_mem_string_record', @test_mem_string_record);
  run_test('test_short_string_mutate', @test_short_string_mutate);
  run_test('test_ansi_string_mutate', @test_ansi_string_mutate);
end.

Regards,
Ryan Joseph

Reply by email

Guillermo Martínez Jiménez
to Hairy Pixels

Hi,

I'm not sure but I think that CPU caches (L1, L2...) have some impact on this example. Also I think PChar assignation should use string copy instead (Strings.strcopy). Not time to do my own tests right now though u_u) .

Regards,
Guillermo "Ñuño" Martínez

Show quoted text 252 lines
Here is my test unit I'm playing with. It's crude but can anyone suggest what other things I could test? I'm playing with a string pointer also to see how ref counting/finalization plays in. Making your own managed typed using management operators is not tested but I'm sure it will be terrible compared to everything else. * test_short_string time: 143ms * test_ansi_string time: 115ms * test_mem_string time: 115ms * test_short_string_record time: 165ms * test_ansi_string_record time: 75ms * test_mem_string_record time: 47ms * test_short_string_mutate time: 203ms * test_ansi_string_mutate time: 181ms ======================================================================================= {$mode objfpc} program string_test; uses SysUtils, DateUtils; const ITERATIONS = 1000 * 1000; TEST_STRING = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'; type TTestProc = procedure; procedure test_mem_string; procedure do_pass(const s: PString; len: Integer); var c: Char; i: Integer; begin for i := 0 to len - 1 do c := s^[i]; end; var s: PString; i, len: Integer; begin for i := 0 to ITERATIONS - 1 do begin len := Length(TEST_STRING); s := GetMem(len); s^ := TEST_STRING; do_pass(s, len); FreeMem(s); end; end; procedure test_short_string; procedure do_pass(const s: ShortString); var c: Char; i: Integer; begin for i := 0 to length(s) - 1 do c := s[i]; end; var s: Shortstring; i: Integer; begin for i := 0 to ITERATIONS - 1 do begin s := TEST_STRING; do_pass(s); end; end; procedure test_ansi_string; procedure ansi_string_pass(const s: AnsiString); var c: Char; i: Integer; begin for i := 0 to length(s) - 1 do c := s[i]; end; var s: AnsiString; i: Integer; begin for i := 0 to ITERATIONS - 1 do begin s := TEST_STRING; ansi_string_pass(s); end; end; procedure test_ansi_string_mutate; var i, j: Integer; s1, s2: AnsiString; begin for i := 0 to ITERATIONS - 1 do begin s1 := TEST_STRING; s2 := s1 + IntToStr(i); for j := 1 to length(s2) - 1 do s2[j] := 'x'; end; end; procedure test_short_string_mutate; var i, j: Integer; s1, s2: ShortString; begin for i := 0 to ITERATIONS - 1 do begin s1 := TEST_STRING; s2 := s1 + IntToStr(i); for j := 1 to length(s2) - 1 do s2[j] := 'x'; end; end; procedure test_short_string_record; type TMyRecord = record a: ShortString; b: ShortString; c: ShortString; end; function do_pass(rec: TMyRecord): TMyRecord; begin result := rec; end; var i: Integer; r: TMyRecord; begin for i := 0 to ITERATIONS - 1 do begin r.a := TEST_STRING; r.b := TEST_STRING; r.c := TEST_STRING; do_pass(r); end; end; procedure test_ansi_string_record; type TMyRecord = record a: AnsiString; b: AnsiString; c: AnsiString; end; function do_pass(rec: TMyRecord): TMyRecord; begin result := rec; end; var i: Integer; r: TMyRecord; begin for i := 0 to ITERATIONS - 1 do begin r.a := TEST_STRING; r.b := TEST_STRING; r.c := TEST_STRING; do_pass(r); end; end; procedure test_mem_string_record; type TMyRecord = record a: PString; b: PString; c: PString; end; function do_pass(rec: TMyRecord): TMyRecord; begin result := rec; end; var i: Integer; r: TMyRecord; len: Integer; begin len := Length(TEST_STRING); for i := 0 to ITERATIONS - 1 do begin r.a := GetMem(len); r.b := GetMem(len); r.c := GetMem(len); r.a^ := TEST_STRING; r.b^ := TEST_STRING; r.c^ := TEST_STRING; do_pass(r); end; end; procedure run_test(name: String; test: TTestProc); var startTime: Double; begin startTime := Now; test; writeln('* ', name,' time: ', MilliSecondsBetween(Now, StartTime), 'ms'); end; begin run_test('test_short_string', @test_short_string); run_test('test_ansi_string', @test_ansi_string); run_test('test_mem_string', @test_ansi_string); run_test('test_short_string_record', @test_short_string_record); run_test('test_ansi_string_record', @test_ansi_string_record); run_test('test_mem_string_record', @test_mem_string_record); run_test('test_short_string_mutate', @test_short_string_mutate); run_test('test_ansi_string_mutate', @test_ansi_string_mutate); end. Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email