Ищите второе мнение о достоверности результатов, полученных из этого простого локализованного теста производительности при любых других различных условиях - PullRequest
1 голос
/ 05 января 2012

Моя настройка :

  • ОС: Windows 7 SP1 (32 бита)
  • Ram: 4 Go
  • Процессор: Intel Pentium D 3,00 ГГц
  • Delphi XE

Мой простой тест :

Я выполнил тест, запустив следующую программу:

program TestAssign;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Diagnostics;

type
  TTestClazz = class
  private
    FIntProp: Integer;
    FStringProp: string;
  protected
    procedure SetIntProp(const Value: Integer);
    procedure SetStringProp(const Value: string);
  public
    property  IntProp: Integer   read FIntProp write SetIntProp;
    property  StringProp: string read FStringProp write SetStringProp;
  end;

{ TTestClazz }

procedure TTestClazz.SetIntProp(const Value: Integer);
begin
  if FIntProp <> Value then
    FIntProp := Value;
end;

procedure TTestClazz.SetStringProp(const Value: string);
begin
  if FStringProp <> Value then
    FStringProp := Value;
end;

var
  i, j: Integer;
  stopw1, stopw2 : TStopwatch;
  TestObj: TTestClazz;

begin
  ReportMemoryLeaksOnShutdown := True;
  //
  try
    TestObj := TTestClazz.Create;
    //
    try
      j := 10000;

      while j <= 100000 do
      begin
        ///
        /// assignement
        ///
        stopw1 :=  TStopwatch.StartNew;
        for i := 0 to j do
        begin
          TestObj.FIntProp := 666;
          TestObj.FStringProp := 'Hello';
        end;
        stopw1.Stop;

        ///
        /// property assignement using Setter
        ///
        stopw2 := TStopwatch.StartNew;
        for i := 0 to j do
        begin
          TestObj.IntProp := 666;
          TestObj.StringProp := 'Hello';
        end;
        stopw2.Stop;

        ///
        /// Log results
        ///

        Writeln(Format('Ellapsed time for %6.d loops: %5.d %5.d', [j, stopw1.ElapsedMilliseconds, stopw2.ElapsedMilliseconds]));

        //
        Inc(j, 5000);
      end;
      //
      Writeln('');
      Write('Press Return to Quit...');

      Readln;
    finally
      TestObj.Free
    end
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Мой (предварительный) вывод :

Кажется, что:

  • Стоит использовать Setter со свойством при некоторых условиях
  • Затраты на вызов метода и выполнение условного теста занимают меньше времени, чем назначение.

Мой вопрос :

Являются ли эти выводы действительными при любых других условиях или только локализованных (исключение)?

Ответы [ 2 ]

3 голосов
/ 05 января 2012

Я бы сделал следующие наблюдения:

  1. Решение о том, использовать или нет сеттер, должно основываться на таких факторах, как поддержание кода, правильность, удобочитаемость, а не производительность.
  2. Ваш эталон совершенно необоснован, так как операторы if оценивают каждый раз как False. Реальный код, который устанавливает свойства, вероятно, изменит свойства за разумную долю времени, которое запускает установщик.
  3. Я ожидаю, что для многих примеров из реальной жизни сеттер будет работать быстрее без теста на равенство. Если бы этот тест оценивался как True каждый раз, тогда, очевидно, код был бы быстрее без него.
  4. Целочисленный установщик практически свободен, и на самом деле он медленнее, чем прямой доступ к полю.
  5. Время тратится в свойстве string. Здесь есть реальный выигрыш в производительности благодаря оптимизации теста if, который по возможности избегает кода назначения строки.
  6. Установщики будут работать быстрее, если вы добавите их, но не на значительную сумму.

Я верю, что любой код реального мира никогда не сможет обнаружить какие-либо из этих различий в производительности. В действительности узким местом будет получение значений, переданных сеттерам, а не времени, потраченного на сеттеры.

Основная ситуация, в которой такая защита if является ценной, заключается в том, что изменение свойства стоит дорого. Например, возможно, это связано с отправкой сообщения Windows или попаданием в базу данных. Для свойства, обеспеченного полем, вы, вероятно, можете его взять или оставить.


В болтовне в комментариях преждевременная оптимизация задается вопросом, почему сравнение if FStringProp <> Value происходит быстрее, чем задание FStringProp := Value. Я исследовал немного дальше, и это было не совсем так, как я первоначально думал.

Оказывается, в if FStringProp <> Value преобладает вызов System._UStrEqual. Две переданные строки на самом деле не являются одной и той же ссылкой, поэтому необходимо сравнивать каждый символ. Однако этот код сильно оптимизирован, и, что самое важное, для сравнения есть всего 5 символов.

Вызов FStringProp := Value переходит к System._UStrAsg, и, поскольку Value является литералом с отрицательным счетчиком ссылок, необходимо сделать новую строку. Версия кода на Pascal выглядит следующим образом:

procedure _UStrAsg(var Dest: UnicodeString; const Source: UnicodeString); // globals (need copy)
var
  S, D: Pointer;
  P: PStrRec;
  Len: LongInt;
begin
  S := Pointer(Source);
  if S <> nil then
  begin
    if __StringRefCnt(Source) < 0 then   // make copy of string literal
    begin
      Len := __StringLength(Source);
      S := _NewUnicodeString(Len);
      Move(Pointer(Source)^, S^, Len * SizeOf(WideChar));
    end else
    begin
      P := PStrRec(PByte(S) - SizeOf(StrRec));
      InterlockedIncrement(P.refCnt);
    end;
  end;
  D := Pointer(Dest);
  Pointer(Dest) := S;
  _UStrClr(D);
end;

Ключевой частью этого является вызов _NewUnicodeString, который, конечно, вызывает GetMem. Меня нисколько не удивляет, что выделение кучи происходит значительно медленнее, чем сравнение 5 символов.

0 голосов
/ 05 января 2012

Поместите const «Hello» в переменную и используйте его для настройки, затем выполните тест снова

...