Saltar al contenido

¿Cómo evitar la instanciación de un objeto dentro de un bucle?

Ya no busques más por todo internet porque llegaste al lugar perfecto, poseemos la solución que buscas sin problemas.

Solución:

Recomendación

La creación de instancias de objetos es bastante barata. Sin embargo, puede hacerlo más eficiente de dos maneras:

  1. Establezca valores de campo utilizando pares de nombre / valor.
  2. No almacene el objeto en caché, simplemente agréguelo directamente a la lista.

Entonces eso se vería así:

for (Case record : createdCases)

    tasks.add(new Task(
        OwnerId=someValue,
        Subject='Some other value',
        Priority='etc.'
    ));


Perfilado

Hice algunos perfiles para averiguar cómo estos dos factores afectan el costo de la CPU. Hice diez ejecuciones de una prueba de cada tipo que se muestra a continuación. Las ejecuciones posteriores fueron mucho más rápidas, por lo que las excluí de mis resultados (o más bien dejé de ejecutarlas).

TL; DR

La mayor parte del costo que puede compensar está en los pares de nombre / valor. Dado que la eliminación del almacenamiento en caché tiene un efecto insignificante en el consumo de CPU, ese aspecto parece principalmente estilístico.

Formato tabular

Operation         Average    Minimum    Maximum
Empty                64.0         56         74
Efficient           477.0        432        516
Caching             482.1        438        581
Setting Fields      555.1        512        664

Costo de bucle vacío

Primero, perfilé un bucle vacío para poder restar las operaciones que no nos importan. Algo como:

final Integer COUNT = 100;
List records = [SELECT OwnerId FROM Account LIMIT :COUNT];
Long start = Datetime.now().getTime();
for (Integer i = 0; i < COUNT; i++)

    List tasks = new List();
    for (Account record : records) continue;

system.debug(Datetime.now().getTime() - start);

En promedio, este bucle tomó 64 ms, con un tiempo de ejecución mínimo de 56 ms y un tiempo de ejecución máximo de 74 ms. Eso significa que podemos asumir que cuesta menos de 1 ms crear una instancia del List e iterar a través del Account graba una sola vez.

Costo de bucle eficiente

A continuación, verifiqué el rendimiento de mi refactor de bucle recomendado.

final Integer COUNT = 100;
List records = [SELECT OwnerId FROM Account LIMIT :COUNT];
Long start = Datetime.now().getTime();
for (Integer i = 0; i < COUNT; i++)

    List tasks = new List();
    for (Account record : records)
        tasks.add(new Task(
            OwnerId=record.OwnerId, WhatId=record.Id
        ));

system.debug(Datetime.now().getTime() - start);

Promedio: 477ms, Mínimo: 432ms, Máximo: 516ms.

Almacenamiento en caché de registros

final Integer COUNT = 100;
List records = [SELECT OwnerId FROM Account LIMIT :COUNT];
Long start = Datetime.now().getTime();
for (Integer i = 0; i < COUNT; i++)

    List tasks = new List();
    for (Account record : records)
    
        Task newTask = new Task(
            OwnerId=record.OwnerId, WhatId=record.Id
        );
        tasks.add(newTask);
    

system.debug(Datetime.now().getTime() - start);

Promedio: 482,1 ms, mínimo: 438 ms, máximo: 581 ms.

Configuración del costo de campos individuales

final Integer COUNT = 100;
List records = [SELECT OwnerId FROM Account LIMIT :COUNT];
Long start = Datetime.now().getTime();
for (Integer i = 0; i < COUNT; i++)

    List tasks = new List();
    for (Account record : records)
    
        Task newTask = new Task();
        newTask.OwnerId = record.OwnerId;
        newTask.WhatId = record.Id;
        tasks.add(newTask);
    

system.debug(Datetime.now().getTime() - start);

Promedio: 555.1ms, Mínimo: 512ms, Máximo: 664ms.

Como señaló Adrian Larson, la creación de instancias de objetos es bastante barata.

Un patrón que he usado en algunos lugares es crear una instancia base fuera de un bucle, configurando tantos campos comunes como sea posible y luego clonar la instancia base dentro del bucle, configurando campos específicos solo donde sea necesario.

Task baseTask = new Task(
    ActivityDate = Date.TODAY().addDays(3),
    Prioity = 'High'
    // ...other common fields here
);

Task cloneTask;
for (Case record : createdCases)

    cloneTask = baseTask.clone(false, true, false, false);
    cloneTask.whatId = record.Id;
    tasks.add(cloneTask);

No tengo idea de cuán eficaz es el objeto clone() El método es (probablemente debería comparar eso), pero sé con certeza que usar object.field = value es más lento que configurar campos a través de pares de nombre / valor en el constructor sObject.

En cualquier caso, es poco probable que esto le afecte a menos que esté intentando acercarse al límite de 10.000 filas de DML por transacción.

+ editar:

redactó un guión de evaluación comparativa

Decimal time1;
Decimal time2;
Integer iterations = 20000;

Decimal bareLoop;
Decimal instantiateInLoop;
Decimal cloneIntoList;
Decimal cloneInLoop;
Decimal cloneInLoopAndSet1Field;
Decimal cloneInLoopAndSet2Fields;
Decimal clone3Fields;
Decimal clone4Fields;

time1 = Limits.getCpuTime();
for(Integer i = 0; i < iterations; i++)

time2 = Limits.getCpuTime();
bareLoop = time2-time1;

List testOppList = new List();
time1 = Limits.getCpuTime();
for(Integer i = 0; i < iterations; i++)
    testOppList.add(new Opportunity(
        description = 'test description',
        stageName = '1 - Working',
        Amount = i,
        CloseDate = Date.Today().addDays(3)
    ));

time2 = Limits.getCpuTime();
instantiateInLoop = time2-time1 - bareLoop;

testOppList.clear();
Opportunity baseInstance;
Opportunity cloneInstance;
time1 = Limits.getCpuTime();
baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    CloseDate = Date.Today().addDays(3)
);

for(Integer i = 0; i < iterations; i++)
    testOppList.add(baseInstance.clone(false, true, false, false));

time2 = Limits.getCpuTime();
cloneIntoList = time2-time1 - bareLoop;
testOppList.clear();

time1 = Limits.getCpuTime();
baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    CloseDate = Date.Today().addDays(3)
);
for(Integer i = 0; i < iterations; i++)
    cloneInstance = baseInstance.clone(false, true, false, false);
    testOppList.add(cloneInstance);

time2 = Limits.getCpuTime();
cloneInLoop = time2-time1 - bareLoop;
testOppList.clear();

time1 = Limits.getCpuTime();
baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    CloseDate = Date.Today().addDays(3)
);
for(Integer i = 0; i < iterations; i++)
    cloneInstance = baseInstance.clone(false, true, false, false);
    cloneInstance.Amount = i;
    testOppList.add(cloneInstance);

time2 = Limits.getCpuTime();
cloneInLoopAndSet1Field = time2-time1 - bareLoop;
testOppList.clear();

time1 = Limits.getCpuTime();
baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    CloseDate = Date.Today().addDays(3)
);
for(Integer i = 0; i < iterations; i++)
    cloneInstance = baseInstance.clone(false, true, false, false);
    cloneInstance.Amount = i;
    cloneInstance.Name = 'Opp-' + i;
    testOppList.add(cloneInstance);

time2 = Limits.getCpuTime();
cloneInLoopAndSet2Fields = time2-time1 - bareLoop;
testOppList.clear();

baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    CloseDate = Date.Today().addDays(3)
);
time1 = Limits.getCpuTime();
for(Integer i = 0; i < iterations; i++)
    testOppList.add(baseInstance.clone(false, true, false, false));

time2 = Limits.getCpuTime();
clone3Fields = time2-time1 - bareLoop;
testOppList.clear();

baseInstance = new Opportunity(
    description = 'test description',
    stageName = '1 - Working',
    Amount = 100,
    CloseDate = Date.Today().addDays(3)
);

time1 = Limits.getCpuTime();
for(Integer i = 0; i < iterations; i++)
    testOppList.add(baseInstance.clone(false, true, false, false));

time2 = Limits.getCpuTime();
clone4Fields = time2-time1 - bareLoop;
testOppList.clear();

system.debug('Time taken in bare loop (just instantiating, comparing, and incrementing i): ' + bareLoop);
system.debug('Time taken directly adding new instance to list (minus bareLoop): ' + instantiateInLoop);
system.debug('Time taken cloning instance direcly into list (minus bareLoop): ' + cloneIntoList);
system.debug('Time taken cloning instance direcly into list, 3 fields (minus bareLoop): ' + clone3Fields);
system.debug('Time taken cloning instance direcly into list, 4 fields (minus bareLoop): ' + clone4Fields);
system.debug('Time taken cloning instance direcly into list, per record, 1 extra field (minus bareLoop): ' + ((clone4Fields - clone3Fields)/iterations));
system.debug('Time taken cloning, then adding instance to list (minus bareLoop): ' + cloneInLoop);
system.debug('Time taken cloning, setting 1 field, then adding instance to list (minus bareLoop): ' + cloneInLoopAndSet1Field);
system.debug('Time taken cloning, setting 2 fields, then adding instance to list (minus bareLoop): ' + cloneInLoopAndSet2Fields);
system.debug('Time taken (per record) to set 1 field using dot notation (minus bareLoop): ' + ((cloneInLoopAndSet1Field - cloneInLoop)/iterations));
system.debug('Time taken (per record) to set an additional field using dot notation (minus bareLoop): ' + ((cloneInLoopAndSet2Fields - cloneInLoopAndSet1Field)/iterations));

resultados (20.000 iteraciones, tenga en cuenta que habrá una variación no determinista entre ejecuciones):

Tiempo necesario en bucle desnudo (solo instanciar, comparar e incrementar i): 11

Tiempo necesario para agregar directamente una nueva instancia a la lista (menos bareLoop): 672

Tiempo necesario para la clonación de la instancia directamente en la lista (menos bareLoop): 331

Tiempo necesario para la clonación de la instancia directamente en la lista, 3 campos (menos bareLoop): 334

Tiempo necesario para la clonación de la instancia directamente en la lista, 4 campos (menos bareLoop): 373

Tiempo empleado en la clonación de la instancia directamente en la lista, por registro, 1 campo adicional (menos bareLoop): 0,00195

Tiempo de clonación, luego agregando instancia a la lista (menos bareLoop): 354

Tiempo de clonación, configuración de 1 campo y luego adición de instancia a la lista (menos bareLoop): 970

Tiempo de clonación, configuración de 2 campos y luego adición de instancia a la lista (menos bareLoop): 1459

Tiempo necesario (por registro) para establecer 1 campo usando notación de puntos (menos bareLoop): 0.0312

Tiempo necesario (por registro) para establecer un campo adicional usando notación de puntos (menos bareLoop): 0.02445

Hice una prueba por separado para ver cuál era el costo incremental de un campo adicional que se establece en el constructor.

Costo por registro por campo adicional instanciado en bucle: 0.01655

Conclusiones:

  • La clonación es rápida, aproximadamente la mitad del costo de CPU de crear repetidamente nuevas instancias y establecer pares de nombre / valor en el constructor (incluso cuando se almacena en una variable temporal
  • La clonación siempre debe ser más rápida que las llamadas repetidas al constructor, ya que el costo incremental de la clonación de un campo adicional es un orden de magnitud (es decir, 10 veces más bajo)
  • Este beneficio desaparece tan pronto como necesite establecer incluso un valor único en un registro utilizando notación de puntos
  • Parece que no hay una cantidad de campos que pueda establecer a través del constructor que harían que la clonación + notación de puntos sea favorable (el costo de la notación de puntos es ~ 2 veces el de establecer un campo adicional en el constructor)

Además de la respuesta de @ AdrianLarson, investigué un poco sobre esto por lo que vale la pena usar el siguiente código:

System.debug('Start: ' + System.now());

List contactList = new List();

for (Integer i = 0; i < 2000; i++) 
  Contact con = new Contact(
    FirstName = 'Foo' + i,
    LastName = 'Bar'
  );

  contactList.add(con);


System.debug('Finish: ' + System.now());

Esto devolvió lo siguiente:

15: 08: 19.30 (31134544) | USER_DEBUG |[1]| DEPURACIÓN | Inicio: 2016-10-11 14:08:19

15: 08: 19.30 (85516226) | USER_DEBUG |[10]| DEPURACIÓN | Finalización: 2016-10-11 14:08:19

Y cuando hice lo mismo usando el otro método:

System.debug('Start: ' + System.now());

List accountList = new List();

for (Integer i = 0; i < 2000; i++) 
  Account acc = new Account();

  acc.Name = 'Foo Bar ' + i;

  accountList.add(acc);


System.debug('Finish: ' + System.now());

Devuelto:

15: 12: 09.19 (20452341) | USER_DEBUG |[1]| DEPURACIÓN | Inicio: 2016-10-11 14:12:09

15: 12: 09.19 (117639487) | USER_DEBUG |[13]| DEPURACIÓN | Finalizar: 2016-10-11 14:12:10

Y finalmente...

System.debug('Start: ' + System.now());

List taskList = new List();

for (Integer i = 0; i < 2000; i++) 
  taskList.add(new Task(
    Subject='Foo Bar'
  ));


System.debug('Finish: ' + System.now());

15: 17: 12.20 (21014329) | USER_DEBUG |[1]| DEPURACIÓN | Inicio: 2016-10-11 14:17:12

15: 17: 12.20 (59016945) | USER_DEBUG |[11]| DEPURACIÓN | Finalización: 2016-10-11 14:17:12

Entonces, cuando Adrian dice:

La creación de instancias de objetos es bastante barata.

No está bromeando.

De hecho, tuve que crear una instancia de 200.000 registros solo para obtener una diferencia de 6 ms entre la depuración de inicio y finalización.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *