Przyśpieszenie działania kodu poprzez dowalenie dodatkowej instrukcji

1

W wątku:
Błąd 'Type mismatch' przy ustalaniu długości tablicy
Mieliśmy z @furious programming odmienne zdanie co do łączenie napisów.
Nie znalazłem swoich starych testów więc napisałem nowe:

SpeedComparatorMain.lfm:

object ASpeedComparator: TASpeedComparator
  Left = 382
  Height = 112
  Top = 208
  Width = 281
  Caption = 'Speed comparator ...'
  ClientHeight = 112
  ClientWidth = 281
  OnCloseQuery = FormCloseQuery
  LCLVersion = '1.4.4.0'
  object TopPanel: TPanel
    Left = 0
    Height = 56
    Top = 0
    Width = 281
    Align = alTop
    BevelInner = bvSpace
    BevelOuter = bvNone
    BevelWidth = 4
    ClientHeight = 56
    ClientWidth = 281
    TabOrder = 0
    object GConcat: TGroupBox
      Left = 4
      Height = 48
      Top = 4
      Width = 119
      Align = alLeft
      Caption = 'Concat'
      ClientHeight = 30
      ClientWidth = 115
      TabOrder = 0
      object CConcat: TLabel
        Left = 0
        Height = 30
        Top = 0
        Width = 115
        Align = alClient
        Alignment = taRightJustify
        Caption = '0'
        Font.Color = clNavy
        Font.Height = -19
        Layout = tlCenter
        ParentColor = False
        ParentFont = False
      end
    end
    object GOperator: TGroupBox
      Left = 158
      Height = 48
      Top = 4
      Width = 119
      Align = alRight
      Caption = 'Operator'
      ClientHeight = 30
      ClientWidth = 115
      TabOrder = 1
      object COperator: TLabel
        Left = 0
        Height = 30
        Top = 0
        Width = 115
        Align = alClient
        Alignment = taRightJustify
        Caption = '0'
        Font.Color = clNavy
        Font.Height = -19
        Layout = tlCenter
        ParentColor = False
        ParentFont = False
      end
    end
  end
  object BtnStart: TButton
    Left = 105
    Height = 25
    Top = 80
    Width = 75
    Caption = 'Start'
    OnClick = BtnStartClick
    TabOrder = 1
  end
end

SpeedComparatorMain.pas:

unit SpeedComparatorMain;

{$mode objfpc}{$H+}

interface uses
  Classes,
  SysUtils,
  FileUtil,
  Forms,
  Controls,
  Graphics,
  Dialogs,
  StdCtrls,
  ExtCtrls,
  SyncObjs;

type
  PThreadRecord=^TThreadRecord;
  TThreadRecord=record
    Th:TThread;
    Count:Cardinal;
    Info:TLabel;
    Func:TNotifyEvent;
    Cs:TCriticalSection;
    Work:Boolean;
  end;

  TCompareThread=class(TThread)
  private
    Data:PThreadRecord;
  protected
    procedure Execute;override;
  public
    constructor Create(AData:PThreadRecord);
  end;

  { TASpeedComparator }
  TASpeedComparator = class(TForm)
    BtnStart: TButton;
    CConcat: TLabel;
    COperator: TLabel;
    GConcat: TGroupBox;
    GOperator: TGroupBox;
    TopPanel: TPanel;
    procedure BtnStartClick(Sender:TObject);
    procedure FormCloseQuery(Sender:TObject;var CanClose:Boolean);
  private
    ThList:array[0..1]of TThreadRecord;
    Cs:TCriticalSection;
    procedure NeedString(const S:String);
    procedure OnConcat(Sender:TObject);
    procedure OnOperator(Sender:TObject);
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
  end;

var ASpeedComparator:TASpeedComparator;
const MakeSize:Integer=1000;
const TestCount:Integer=1000;

implementation

{$R *.lfm}

{ TCompareThread }

constructor TCompareThread.Create(AData:PThreadRecord);
begin
  Data:=AData;
  inherited Create(true);
  FreeOnTerminate:=true;
end;

procedure TCompareThread.Execute;
begin
  Data^.Work:=true;
  while not Terminated do
  begin
    Data^.Func(Self);
    Inc(Data^.Count);
    Data^.Cs.Enter;
    try
      Data^.Info.Caption:=IntToStr(Data^.Count);
    finally
      Data^.Cs.Leave;
    end;
  end;
  Data^.Work:=false;
end;

{ TASpeedComparator }

constructor TASpeedComparator.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  Cs:=TCriticalSection.Create;
  ThList[0].Cs:=Cs;
  ThList[0].Info:=CConcat;
  ThList[0].Func:=@OnConcat;
  ThList[0].Th:=TCompareThread.Create(@ThList[0]);
  ThList[1].Cs:=Cs;
  ThList[1].Info:=COperator;
  ThList[1].Func:=@OnOperator;
  ThList[1].Th:=TCompareThread.Create(@ThList[1]);
end;

destructor TASpeedComparator.Destroy;
begin
  Cs.Free;
  inherited Destroy;
end;

procedure TASpeedComparator.NeedString(const S:String);
begin
  if Length(S)<0 then Application.Terminate;
end;

procedure TASpeedComparator.OnConcat(Sender:TObject);
var T,I:Integer;
var S:String;
begin
  for T:=1 to TestCount do
  begin
    SetLength(S,0);
    for I:=1 to MakeSize do S:=Concat(S,' ',IntToStr(I),#13#10);
    //for I:=1 to MakeSize do S:=Concat(S,IntToStr(I),#13#10);
    NeedString(S); // Aby optymalizator nie wyciął w pień tą zmienną S
  end;
end;

procedure TASpeedComparator.OnOperator(Sender:TObject);
var T,I:Integer;
var S:String;
begin
  for T:=1 to TestCount do
  begin
    SetLength(S,0);
    //for I:=1 to MakeSize do S:=S+' '+IntToStr(I)+#13#10;
    for I:=1 to MakeSize do S:=S+IntToStr(I)+#13#10;
    NeedString(S); // Aby optymalizator nie wyciął w pień tą zmienną S
  end;
end;

procedure TASpeedComparator.BtnStartClick(Sender: TObject);
var I:Integer;
begin
  for I:=Low(ThList) to High(ThList) do ThList[I].Th.Start();
end;

procedure TASpeedComparator.FormCloseQuery(Sender:TObject;var CanClose:Boolean);
var I:Integer;
begin
  CanClose:=true;
  for I:=Low(ThList) to High(ThList) do ThList[I].Th.Terminate;
  for I:=Low(ThList) to High(ThList) do while ThList[I].Work do Application.ProcessMessages;
end;

end.

W załączniku razem z projektem.
Na podstawie kodu z satysfakcją stwierdziłem że miałem racje ponieważ program pokazuje że nawet: S:=Concat(S,' ',IntToStr(I),#13#10); jest szybsze od: S:=S+IntToStr(I)+#13#10;
Z tym że na wszelki wypadek postanowiłem sprawdzić inną sytuacje ... jestem zaskoczony faktem że: S:=S+' '+IntToStr(I)+#13#10; jest szybsze od: S:=Concat(S,IntToStr(I),#13#10);
Czyli dowalamy składnik i mamy szybszy program?
WTF?

Sprawdzałem pod: 831af2a406.png

2

Jeszcze od dawnych czasów pamiętam, że Concat zawsze było wolniejsze od operatora +, bez względu na ilość argumentów; Zapewne Concat jest kompilowany do innego kodu wynikowego, choć sam w sobie nie jest standardową funkcją - to Twór dynamicznie kompilowany, tak samo jak Writeln czy Readln;

Co do samych testów - nieco przekombinowujesz; Ja jeśli potrzebuję sprawdzić szybkość działania jakiegoś prostego algorytmu, zawsze piszę jednowątkową konsolówkę, odpalam ją na nieobciążonej maszynie, a czas mierzę za pomocą QueryPerformanceCounter (z racji tej, że mierzone operacje wykonują się setki czy tysiące razy w jednej milisekundzie); Sporo takich testów napisałem, podczas tworzenia super szybkich algorytmów parsujących linie plików, konwertujących dane na łańcuchy i odwrotnie - wszystko na potrzeby API dla plików TreeStructInfo; Dlatego nie używam w tamtym API regexów, bo działają o wiele wolniej;

Natomiast gołe liczby zwracane przez QueryPerformanceCounter w zupełności wystarczą - nie potrzeba wiedzieć ile mili- czy nanosekund algorytm działał; Porównywanie counterów dwóch algorytmów wystarczy, aby wyłonić zwycięski kod; A jeśli potrzebowałem przerobić rezultat funkcji WinAPI na jednostkę czasu, np. ms - korzystałem dodatkowo z QueryPerformanceFrequency i prostej arytmetyki;

No nic, wykonałem swój test - program konsolowy, jednowątkowy, zwykłe "copy-paste"; Czas (a raczej stan licznika) mierzony za pomocą wspomnianej funkcji WinAPI; Rezultat taki sam - bez względu na ilość składowych, operator jest szybszy od funkcji Concat; A im więcej składowych, tym większa różnica czasów, oczywiście na korzyść operatora; W każdej parze algorytmów (jest ich trzy - dla dwóch, trzech i czterech składowych) dodawane są składowe o takich samych wartościach:

strValue 'first'
strValue 'first' 'second'
strValue 'first' 'second' 'third'

Cały kod konsolówki poniżej:

program Project1;

{$mode objfpc}{$H+}

uses
  Windows, SysUtils;

var
  intStart: Int64 = 0;
  intStop: Int64 = 0;
  intCurrTime, intMinTime: Int64;
  intTestIdx, intConcatIdx: Integer;
  strValue: String;
begin
  { ----- concat - 2 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, 'first');

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [2]: ':14, intMinTime);

  { ----- operator - 2 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue += 'first';

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [2]: ':14, intMinTime, #10);

  { ----- concat - 3 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, 'first', 'second');

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [3]: ':14, intMinTime);

  { ----- operator - 3 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue += 'first' + 'second';

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [3]: ':14, intMinTime, #10);

  { ----- concat - 4 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, 'first', 'second', 'third');

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [4]: ':14, intMinTime);

  { ----- operator - 4 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue += 'first' + 'second' + 'third';

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [4]: ':14, intMinTime);

  { --------------------------------------------------------- }

  ReadLn();
end.

Przykładowe wyjście:

  Concat [2]: 2553
Operator [2]: 2546

  Concat [3]: 4824
Operator [3]: 3456

  Concat [4]: 6207
Operator [4]: 4205

Kod testowany uruchamiany poza debugerem, plik wykonywalny bez symboli debugera, używana optymalizacja - -O1; Czasy konkatenacji dla dwóch argumentów raz na jakiś czas są prawie takie same lub takie same, natomiast bardzo rzadko zdarza się, że Concat wygrywa o 2-3 ticki; W każdym razie od trzech składowych w górę Concat coraz bardziej dostaje w dupsko;

Więc tak jak napisałem - dla dwóch nie ma różnicy, dla trzech też raczej się nie opłaca, bo zysk jest zbyt mały, natomiast dla większej liczby składowych bardziej opłaca się używanie standardowych operatorów := i + lub wersji skróconej - +=; Przy czym operator += to tylko cukier składniowy, bo na kod wynikowy nie wpływa;

PS: Test wykonywałem pod FPC 2.6.4.

1

na D XE8 obie wersję z operatorem są szybsze. Jednak wersja S:=S+' '+IntToStr(I)+#13#10; jest nieznacznie szybsza - 1160 : 1200 natomiast wersja S:=S+IntToStr(I)+#13#10; jest znacznie szybsza 2758:3689

0
furious programming napisał(a):

Co do samych testów - nieco przekombinowujesz
Ale za to nie wpadam w pułapkę przez którą widzę niepoprawny obraz rzeczywistości, zaraz wyjaśniam.

furious programming napisał(a):

Jeszcze od dawnych czasów pamiętam, że Concat zawsze było wolniejsze od operatora +, bez względu na ilość argumentów;
Zapewnie tłumaczy się to wpadaniem we własne sidła.

Sam sobie zrozumiesz na czym ta twoja pułapka polega:

DataString.pas:

unit DataString;

interface

var first,second,third:String;

implementation

initialization
  first:='first';
  second:='second';
  third:='third';

end.

SpeedTest.lpr/SpeedTest.dpr:

program SpeedTest;

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  DataString in 'DataString.pas';

var
  intStart: Int64 = 0;
  intStop: Int64 = 0;
  intCurrTime, intMinTime: Int64;
  intTestIdx, intConcatIdx: Integer;
  strValue: String;
begin
  { ----- concat - 2 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, first);

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [2]: ':14, intMinTime);

  { ----- operator - 2 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := strValue + first;

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [2]: ':14, intMinTime, #10);

  { ----- concat - 3 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, first, second);

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [3]: ':14, intMinTime);

  { ----- operator - 3 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := strValue + first + second;

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [3]: ':14, intMinTime, #10);

  { ----- concat - 4 arguments ------------------------------ }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := Concat(strValue, first, second, third);

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Concat [4]: ':14, intMinTime);

  { ----- operator - 4 arguments ---------------------------- }

  intMinTime := High(Int64);

  for intTestIdx := 0 to 99 do
  begin
    strValue := '';
    QueryPerformanceCounter(intStart);

    for intConcatIdx := 0 to 9999 do
      strValue := strValue + first + second + third;

    QueryPerformanceCounter(intStop);
    intCurrTime := intStop - intStart;

    if intCurrTime < intMinTime then
      intMinTime := intCurrTime;
  end;

  WriteLn('Operator [4]: ':14, intMinTime);

  { --------------------------------------------------------- }

  ReadLn;
end.

Wyniki Delphi 5:

  Concat [2]: 1964
Operator [2]: 2035

  Concat [3]: 1044200
Operator [3]: 1043347

  Concat [4]: 1435277
Operator [4]: 1437730

Wyniki Lazarus 1.4.4:

  Concat [2]: 2231
Operator [2]: 2234

  Concat [3]: 3044
Operator [3]: 3036

  Concat [4]: 3639
Operator [4]: 3656
0

@_13th_Dragon - owszem, Ty w powyższym teście używasz samych zmiennych jako składników, natomiast pierwotna konkatenacja z tamtego wątku przewidywała łączenie literałów i jednej zmiennej, dlatego uprościłem test do samych literałów; Sam optymalizator być może zakłócał mój test, bo prawdopodobnie wyciągał zesumowane literały przed pętlę do osobnej zmiennej; Jednak w tym właśnie tkwi sendo sprawy;

Jeśli wyłączę optymalizacje, to dla literałów (mój poprzedni test) nadal jest przewaga:

  Concat [2]: 2567
Operator [2]: 2583

  Concat [3]: 4826
Operator [3]: 3455

  Concat [4]: 6168
Operator [4]: 4181

Natomiast jeśli literał first zamienię na zmienną - Concat daje lepsze wyniki:

  Concat [2]: 4750
Operator [2]: 2588

  Concat [3]: 4832
Operator [3]: 7428

  Concat [4]: 6170
Operator [4]: 9725

Optymalizacja w takim przypadku nie polepsza sytuacji operatorów;

Jeśli argumenty będą samymi zmiennymi to funkcja Concat nie daje żadnych sensownych powodów aby jej używać, bo jak widzisz pod FPC czasy są niemalże identyczne; A skoro są prawie takie same to IMHO lepiej skorzystać z operatorów += i +, które w takim przypadku umożliwią zapis krótszej linii, bardziej czytelnej;

W takim razie wyniki testów informują, że:

  • dla samych literałów szybszy jest operator,
  • dla samych zmiennych lub zmiennych i literałów szybsza jest funkcja Concat,
  • im więcej składowych, tym większa różnica czasów.
0

Po to została wyprodukowana ta funkcja concat, aby działać lepiej.

Niemniej wszelkie te operacje na stringach są i tak strasznie słabe, marne.
Na tablicach char*, w stylu c, pójdzie to zapewne znacznie szybciej.

2

W dokumentacji Delphi napisano że operator + jest szybszy http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Concat

1

Ja miałem stąd właśnie przekonanie, że od kiedy używałem Delphi (czyli od wersji 7), funkcja ta była wolniejsza i niezalecana; Tak też jest w pomocy napisane:

Description napisał(a)

In Delphi code, use Concat to concatenate an arbitrary number of strings. Each parameter is a string-type expression. The result is the concatenation of all the string parameters.

Using the plus (+) operator has the same effect on two strings as using the Concat function:

S := 'ABC' + 'DEF';

Tip: The plus operator is faster than Concat.

Jednak pod FPC nie jest to już taka oczywista sprawa, bo w dokumentacji już takiego tipa nie ma; Natomiast jest inny:

Description napisał(a)

Concat concatenates the strings S1, S2 etc. to one long string. The resulting string is truncated at a length of 255 bytes. The same operation can be performed with the + operation.

Ciekawe czy tak samo działa dla symbolu $LONGSTRINGS - aż sprawdzę z ciekawości; Edit: No i nie, nie przycina łancucha wynikowego; Dla poniższego kodu:

var
  strValue: AnsiString;
begin
  strValue := Concat(StringOfChar('X', 200), StringOfChar('X', 300));
  Write('Length: ', Length(strValue));
end.

Dostajemy poniższe wyjście:

Length: 500
0

Pod 7 również Concat jest szybszy o ile oczywiście nie zapodasz do scalania stałych plus optymalizacja - to przerzuca operacje + na czas kompilacji, więc wychodzi szybciej.

0

Nie wiem o czym tu dyskutować...

zróbcie sobie dwie funkcje typu:

function scal(var a,b,c) : string;
begin
 result := concat(a,b,c); // a w drugiej :=  a+b+c
end;

i tyle...

1 użytkowników online, w tym zalogowanych: 0, gości: 1