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:
- Establezca valores de campo utilizando pares de nombre / valor.
- 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.