performance delphi oop interface

performance - Problema de rendimiento de la interfaz Delphi



oop interface (4)

Estás comparando naranjas con manzanas, ya que la primera prueba lee un campo (FArr), mientras que la segunda prueba lee una propiedad (Arr) que tiene un getter asignado. Por desgracia, las interfaces no ofrecen acceso directo a sus campos, por lo que realmente no puede hacerlo de otra manera que como lo hizo. Pero como dijo Allen, esto causa una llamada al método getter (GetArray), que se clasifica como ''virtual'' sin que siquiera lo escriba porque es parte de una interfaz. Por lo tanto, cada acceso da como resultado una búsqueda VMT (indirecta a través de la interfaz) y una llamada a un método. Además, el hecho de que esté utilizando una matriz dinámica significa que tanto la persona que llama como la persona que llama harán una gran cantidad de recuento de referencias (puede ver esto si observa el código ensamblador generado).

Todo esto ya es suficiente para explicar la diferencia de velocidad medida, pero puede ser fácilmente superada usando una variable local y leer la matriz solo una vez. Cuando haces eso, la llamada al captador (y todo el recuento de referencia que sigue) se lleva a cabo una sola vez. Comparado con el resto de la prueba, este ''sobrecarga'' se vuelve inconmensurable.

Pero tenga en cuenta que una vez que siga esta ruta, perderá la encapsulación y cualquier cambio en el contenido de la matriz NO se reflejará en la interfaz, ya que las matrices tienen un comportamiento de copiado por escritura. Sólo una advertencia.

He hecho algunas refactorizaciones realmente serias de mi editor de texto. Ahora hay mucho menos código, y es mucho más fácil extender el componente. Hice un uso bastante fuerte del diseño OO, como las clases abstractas y las interfaces. Sin embargo, he notado algunas pérdidas en lo que respecta al rendimiento. El problema es leer una gran variedad de registros. Es rápido cuando todo sucede dentro del mismo objeto, pero lento cuando se realiza a través de una interfaz. He hecho el programa más pequeño para ilustrar los detalles:

unit Unit3; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; const N = 10000000; type TRecord = record Val1, Val2, Val3, Val4: integer; end; TArrayOfRecord = array of TRecord; IMyInterface = interface [''{C0070757-2376-4A5B-AA4D-CA7EB058501A}''] function GetArray: TArrayOfRecord; property Arr: TArrayOfRecord read GetArray; end; TMyObject = class(TComponent, IMyInterface) protected FArr: TArrayOfRecord; public procedure InitArr; function GetArray: TArrayOfRecord; end; TForm3 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form3: TForm3; MyObject: TMyObject; implementation {$R *.dfm} procedure TForm3.FormCreate(Sender: TObject); var i: Integer; v1, v2, f: Int64; MyInterface: IMyInterface; begin MyObject := TMyObject.Create(Self); try MyObject.InitArr; if not MyObject.GetInterface(IMyInterface, MyInterface) then raise Exception.Create(''Note to self: Typo in the code''); QueryPerformanceCounter(v1); // APPROACH 1: NO INTERFACE (FAST!) // for i := 0 to high(MyObject.FArr) do // if (MyObject.FArr[i].Val1 < MyObject.FArr[i].Val2) or // (MyObject.FArr[i].Val3 < MyObject.FArr[i].Val4) then // Tag := MyObject.FArr[i].Val1 + MyObject.FArr[i].Val2 - MyObject.FArr[i].Val3 // + MyObject.FArr[i].Val4; // END OF APPROACH 1 // APPROACH 2: WITH INTERFACE (SLOW!) for i := 0 to high(MyInterface.Arr) do if (MyInterface.Arr[i].Val1 < MyInterface.Arr[i].Val2) or (MyInterface.Arr[i].Val3 < MyInterface.Arr[i].Val4) then Tag := MyInterface.Arr[i].Val1 + MyInterface.Arr[i].Val2 - MyInterface.Arr[i].Val3 + MyInterface.Arr[i].Val4; // END OF APPROACH 2 QueryPerformanceCounter(v2); QueryPerformanceFrequency(f); ShowMessage(FloatToStr((v2-v1) / f)); finally MyInterface := nil; MyObject.Free; end; end; { TMyObject } function TMyObject.GetArray: TArrayOfRecord; begin result := FArr; end; procedure TMyObject.InitArr; var i: Integer; begin SetLength(FArr, N); for i := 0 to N - 1 do with FArr[i] do begin Val1 := Random(high(integer)); Val2 := Random(high(integer)); Val3 := Random(high(integer)); Val4 := Random(high(integer)); end; end; end.

Cuando leo los datos directamente, obtengo tiempos como 0.14 segundos. Pero cuando paso por la interfaz, demora 1,06 segundos.

¿No hay forma de lograr el mismo rendimiento que antes con este nuevo diseño?

Debo mencionar que traté de establecer PArrayOfRecord = ^TArrayOfRecord y redefiní IMyInterface.arr: PArrayOfRecord y escribí Arr^ etc en el ciclo for . Esto ayudó mucho; Luego obtuve 0.22 segundos. Pero aún no es lo suficientemente bueno. ¿Y qué lo hace tan lento para empezar?


Simplemente asigne la matriz a una variable local antes de recorrer los elementos.

Lo que está viendo es que las llamadas a los métodos de interfaz son virtuales y deben ser llamadas a través de un direccionamiento indirecto. Además, el código tiene que pasar a través de un "thunk" que arregla la referencia "Self" para apuntar ahora a la instancia del objeto y no a la instancia de la interfaz.

Al hacer solo una llamada a un método virtual para obtener la matriz dinámica, puede eliminar esa sobrecarga del ciclo. Ahora su ciclo puede recorrer los elementos de la matriz sin la sobrecarga adicional de las llamadas al método de interfaz virtual.


su diseño usa gran memoria. Optimiza tu interfaz.

IMyInterface = interface [''{C0070757-2376-4A5B-AA4D-CA7EB058501A}''] function GetCount:Integer: function GetRecord(const Index:Integer):TRecord; property Record[Index:Integer]:TRecord read GetRecord; end;


Allen''s respuestas de Patrick y Allen''s son perfectamente correctas.

Sin embargo, dado que su pregunta se refiere al diseño mejorado de OO, creo que un cambio particular en su diseño que también mejoraría el rendimiento es apropiado para debatir.

Tu código para configurar la etiqueta es "muy controlador". Lo que quiero decir con esto es que pasas mucho tiempo "husmeando dentro de otro objeto" (a través de una interfaz) para calcular tu valor de Etiqueta. Esto es en realidad lo que expuso el "problema de rendimiento con las interfaces".

Sí, simplemente puede deferencia la interfaz una vez a una variable local, y obtener una mejora masiva en el rendimiento, pero todavía estará hurgando dentro de otro objeto. Uno de los objetivos importantes en el diseño OO es no meterse donde no perteneces. Esto realmente viola la Ley de Demeter .

Considere el siguiente cambio que permite a la interfaz hacer más trabajo.

IMyInterface = interface [''{C0070757-2376-4A5B-AA4D-CA7EB058501A}''] function GetArray: TArrayOfRecord; function GetTagValue: Integer; //<-- Add and implement this property Arr: TArrayOfRecord read GetArray; end; function TMyObject.GetTagValue: Integer; var I: Integer; begin for i := 0 to High(FArr) do if (FArr[i].Val1 < FArr[i].Val2) or (FArr[i].Val3 < FArr[i].Val4) then begin Result := FArr[i].Val1 + FArr[i].Val2 - FArr[i].Val3 + FArr[i].Val4; end; end;

Luego dentro de TForm3.FormCreate , // APPROACH 3 se convierte en:

Tag := MyInterface.GetTagValue;

Esto será tan rápido como la sugerencia de Allen, y será un mejor diseño.

Sí, soy plenamente consciente de que simplemente elaboró ​​un ejemplo rápido para ilustrar la sobrecarga de rendimiento al buscar repetidamente algo a través de la interfaz. Pero el punto es que si tiene un código que funciona de manera subóptima debido a accesos excesivos a través de interfaces, entonces tiene un olor a código que sugiere que debería considerar trasladar la responsabilidad de cierto trabajo a una clase diferente. En su ejemplo, TForm3 fue altamente inapropiado considerando que todo lo que se requería para el cálculo pertenecía a TMyObject .