Problema de Rendimiento de la Interfaz Delphi
He hecho una refactorización muy seria 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 intensivo del diseño OO, como clases abstractas e interfaces. Sin embargo, he notado algunas pérdidas cuando se trata de rendimiento. La cuestión es leer una gran variedad de registros. Es rápido cuando todo sucede dentro del mismo objeto, pero lento cuando se hace a través de una interfaz. He hecho el programa más pequeño para ilustrar el 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 voy a través de la interfaz, se tarda 1,06 segundos.
No hay manera de lograr el mismo rendimiento que antes con este nuevo diseño?
Debo mencionar que traté de establecer PArrayOfRecord = ^TArrayOfRecord
y redefinir IMyInterface.arr: PArrayOfRecord
y escribí Arr^
etc en el bucle for
. Esto ayudó mucho; luego obtuve 0.22 segundos. Pero todavía no es suficiente. ¿Y qué lo hace tan lento para empezar?
4 answers
Simplemente asigne la matriz a una variable local antes de iterar a través de los elementos.
Lo que está viendo es que las llamadas a los métodos de interfaz son virtuales y tienen que ser llamadas a través de una indirección. Además, el código tiene que pasar un " thunk "que arregla la referencia" Self " para que ahora apunte a la instancia del objeto y no a la instancia de la interfaz.
Al hacer una sola llamada al método virtual para obtener la matriz dinámica, puede eliminar esa sobrecarga del bucle. Ahora su bucle puede pasar por los elementos de la matriz sin la sobrecarga adicional de las llamadas al método de la interfaz virtual.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-18 19:42:42
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 con ella. 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 siquiera escribirlo porque es parte de una interfaz. Por lo tanto, cada acceso resulta en una búsqueda VMT (indirectamente a través de la interfaz) y una llamada al método. Además, el hecho de que esté utilizando una matriz dinámica significa que tanto el llamante como el destinatario harán mucho recuento de referencias (puede ver esto si echa un vistazo al código de ensamblado generado).
Todo esto ya es razones suficientes para explicar la diferencia de velocidad medida, pero de hecho se puede superar fácilmente usando una variable local y leer el array solo una vez. Cuando haces eso, la llamada al getter (y toda la referencia de seguimiento contando) se está llevando a cabo solo una vez. En comparación con el resto de la prueba, esta 'sobrecarga' se vuelve inconmensurable.
Pero tenga en cuenta que una vez que vaya por esta ruta, perderá la encapsulación y cualquier cambio en el contenido de la matriz NO se reflejará de nuevo en la interfaz, ya que las matrices tienen un comportamiento de copia al escribir. Sólo una advertencia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-19 06:09:38
Las respuestas de Patrick y de Allen son ambas perfectamente correctas.
Sin embargo, dado que su pregunta habla sobre el diseño mejorado de OO, creo que es apropiado discutir un cambio particular en su diseño que también mejoraría el rendimiento.
El código para establecer la etiqueta es "muy controlador". Lo que quiero decir con esto es que estás pasando mucho tiempo "husmeando dentro de otro objeto" (a través de una interfaz) para calcular el valor de tu etiqueta. Esto es realmente lo que exposed the"performance problem with interfaces".
Sí, simplemente puede deferir la interfaz una vez a una variable local y obtener una mejora masiva en el rendimiento, pero todavía estará husmeando dentro de otro objeto. Uno de los objetivos importantes en el diseño OO es no husmear donde no perteneces. Esto en realidad viola la Ley de Deméter .
Considere el siguiente cambio que faculta a la interfaz para 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
, / / LA APROXIMACIÓN 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 armó un ejemplo rápido para ilustrar la sobrecarga de rendimiento de buscar repetidamente algo a través de la interfaz. Pero el punto es que si el código tiene un rendimiento subóptimo debido a accesos excesivos a través de interfaces, entonces tiene un olor a código que sugiere que debe considerar mover la responsabilidad de cierto trabajo en una clase diferente. En su ejemplo TForm3
fue altamente inapropiado considerando todo requerido para el cálculo pertenecía a TMyObject
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:33
Su diseño utiliza una memoria enorme. 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;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-04 05:00:39