Sitemap

ExtendScript Performance Tips

6 min readSep 27, 2025

..for Adobe Illustrator

Press enter or click to view image in full size

I have written a small test case for investigating performance bottlenecks in extendscript environment. You can get the latest script version here or scroll below. This is the meat of the test:


function timeFunction(fn) {
var start = new Date().getTime();
var comment = fn() || "";
var time = ((new Date().getTime() - start) / 1000).toFixed(3);
log(fn.name + ["\t\t\t", "\t\t", "\t"][fn.name.length / 8 | 0] + time + "\t" + comment);
}

It’s simply defining a bunch of functions and timing their executions, you are welcome to use it for your needs and tests.

Of course, common optimization techniques apply in ExtendScript too. Function calling and object creation are costly operations. Local value access is faster than property access. Caching calculations outside cycles, arrays over objects, etc. However the main speed clog is adding/changing stuff on Artboard.

To replicate my tests and use it as it is you should create a new illustrator doc, draw any shape, name it Test Item in layer panel, then duplicate it and drag to Symbol Panel.

Script will create a log file, make sure it runs from a location where it can write files.

It should then generate output like this. Or throw an error.

// Starting test, doc has 1 pageItems
findItem 0.000 item type:PathItem
findSymbol 0.000
dupItem 0.020
dupItemAndLeftTop. 4.393
dupItemAndMatrix 3.450
dupItemAndTranslate 4.790
placeSymbol 0.006
placeSymbolAndLeftTop 13.173
placeSymbolAndMatrix 8.089
placeSymbolAndTranslate 9.989
itemsLeftTop 21.693
symbolsLeftTop 21.381
itemsMatrix 10.663
symbolsMatrix 10.593
itemsPosition 10.832
symbolsPosition 10.878
itemsScaleWH 20.734
symbolsScaleWH 21.453
itemsResize 10.456
symbolsResize 10.756
itemsScaleMatrix 10.516
symbolsScaleMatrix. 10.863

Now, a few obvious and not-so-obvious points arise.
For starters, moving items by left or top property or scaling items by accessing width/height is 2 times slower. And that makes perfect sense,
we are accessing 2 properties after all.

What is less obvious, however is symbol access. Placing symbols is 3–5 faster than duplicating items! But transformations seem slower, why?

Another weird thing happens. If we create an item and transform it instantly, it will be 2–5 times faster than if we collect it into an array and transform them later.. dupItemAndLeftTop is 4.393 while itemsLeftTop 21.693!

Lets try changing the order first; lets try placing symbols and then duping items:

// Starting test, doc has 4001 pageItems
findItem 0.014 item type:PathItem
findSymbol 0.000
placeSymbol 0.012
placeSymbolAndLeftTop 2.417
placeSymbolAndMatrix 2.734
placeSymbolAndTranslate 4.229
dupItem 0.020
dupItemAndLeftTop 14.840
dupItemAndMatrix 9.001
dupItemAndTranslate 10.327
itemsLeftTop 22.596
symbolsLeftTop 22.643
itemsMatrix 11.285
symbolsMatrix 11.403
itemsPosition 11.317
symbolsPosition 11.558
itemsScaleWH 23.350
symbolsScaleWH 23.911
itemsResize 11.471
symbolsResize 12.043
itemsScaleMatrix 11.661
symbolsScaleMatrix 12.109

Now symbols are way faster and then we get speed drop further. What is happening here? Lets try to clear memory:

function callGarbageMan(){
$.gc();
}

..but nothing changes:

// Starting test, doc has 8001 pageItems
findItem 0.029 item type:PathItem
findSymbol 0.001
placeSymbol 0.012
placeSymbolAndLeftTop 2.714
placeSymbolAndMatrix 2.883
placeSymbolAndTranslate 4.528
callGarbageMan 0.002
dupItem 0.021
dupItemAndLeftTop 15.145
dupItemAndMatrix 9.107
....

What if we call painter?
(app. functions can be accessed without specifying)

function callPainter(){
redraw()
}

Hooray!

// Starting test, doc has 15683 pageItems
findItem 0.045 item type:PathItem
findSymbol 0.000
placeSymbol 0.012
placeSymbolAndLeftTop 3.461
placeSymbolAndMatrix 3.438
placeSymbolAndTranslate 5.194
callPainter 1.041
dupItem 0.028
dupItemAndLeftTop 5.541
dupItemAndMatrix 4.269
dupItemAndTranslate 5.974
callPainter 0.860
itemsLeftTop 2.671
symbolsLeftTop 5.944
itemsMatrix 3.823
symbolsMatrix 4.013
itemsPosition 3.784
symbolsPosition 3.974
itemsScaleWH 7.590
symbolsScaleWH 8.686
itemsResize 3.944
symbolsResize 4.481
itemsScaleMatrix 4.001
symbolsScaleMatrix 4.580

It seems that adding app.redraw() call between accessing large object batches will improve performance! app.redraw() is relatively expensive, but can save significant time when creating/accessing lots of items.

Still, it appears initial placement is fastest, but further tests might be needed.

Pleasant surprise is .getByName() function, easily iterating over 15k page items.

Full code:

// (c) Aivaras Gontis
// www.illustratorscripts.com
// MIT License

try {
(function () {

var DUP_COUNT = 500;
var ITEM_NAME = "Test Item";
var SYMBOL_NAME = "New Symbol";
var STEP = 2;
var SCALE = 1.1;

var pageItem, symbolItem, newPageItems, newSymbols;

function log(argz) {
var s = []
for (var i = 0; i < arguments.length; i++)
s.push(String(arguments[i]));
var file = new File(File($.fileName).parent + "/log.txt");
file.open("e", "TEXT", "????");
file.seek(0, 2);
file.lineFeed = $.os.search(/windows/i) != -1 ? 'windows' : 'macintosh';
file.writeln(s.join(" "));
$.writeln(s.join(" "));
file.close();
}


function timeFunction(fn) {
var start = new Date().getTime();
var comment = fn() || "";
var time = ((new Date().getTime() - start) / 1000).toFixed(3);
log(fn.name + ["\t\t\t", "\t\t", "\t"][fn.name.length / 8 | 0] + time + "\t" + comment);
}

function findItem() {
try {
pageItem = activeDocument.pageItems.getByName(ITEM_NAME);
} catch (e) { throw "no item found, named " + ITEM_NAME }

return "item type:" + pageItem.constructor.name
}

function findSymbol() {
try {
symbolItem = activeDocument.symbols.getByName(SYMBOL_NAME);
} catch (e) { throw "no symbol found, named " + SYMBOL_NAME }
}

function dupItem() {
newPageItems = [];
for (var i = 0; i < DUP_COUNT; i++) {
var copy = pageItem.duplicate();
copy.name = "";
newPageItems.push(copy);
}
}

function dupItemAndLeftTop() {
newPageItems = [];
for (var i = 0; i < DUP_COUNT; i++) {
var copy = pageItem.duplicate();
copy.name = "";
copy.left += i * STEP;
copy.top -= i * STEP;
newPageItems.push(copy);
}
}

function dupItemAndMatrix() {
newPageItems = [];
for (var i = 0; i < DUP_COUNT; i++) {
var copy = pageItem.duplicate();
copy.name = "";
copy.transform(getTranslationMatrix(i * STEP, -i * STEP));
newPageItems.push(copy);
}
}

function dupItemAndTranslate() {
newPageItems = [];
for (var i = 0; i < DUP_COUNT; i++) {
var copy = pageItem.duplicate();
copy.name = "";
copy.translate(i * STEP, -i * STEP);
newPageItems.push(copy);
}
}

function placeSymbol() {
newSymbols = [];
for (var i = 0; i < DUP_COUNT; i++) {
var instance = activeDocument.symbolItems.add(symbolItem);
newSymbols.push(instance);
}
}

function placeSymbolAndLeftTop() {
newSymbols = [];
for (var i = 0; i < DUP_COUNT; i++) {
var instance = activeDocument.symbolItems.add(symbolItem);
instance.left += i * STEP;
instance.top -= i * STEP;
newSymbols.push(instance);
}
}

function placeSymbolAndMatrix() {
newSymbols = [];
for (var i = 0; i < DUP_COUNT; i++) {
var instance = activeDocument.symbolItems.add(symbolItem);
instance.transform(getTranslationMatrix(i * STEP, -i * STEP));
newSymbols.push(instance);
}
}

function placeSymbolAndTranslate() {
newSymbols = [];
for (var i = 0; i < DUP_COUNT; i++) {
var instance = activeDocument.symbolItems.add(symbolItem);
instance.translate(i * STEP, -i * STEP);
newSymbols.push(instance);
}
}

function itemsLeftTop() {
for (var i = 0, len = newPageItems.length; i < len; i++) {
newPageItems[i].left += STEP;
newPageItems[i].top += STEP;
}
}

function symbolsLeftTop() {
for (var i = 0, len = newSymbols.length; i < len; i++) {
newSymbols[i].left += STEP;
newSymbols[i].top += STEP;
}
}

function itemsMatrix() {
var matrix = getTranslationMatrix(STEP, STEP);
for (var i = 0, len = newPageItems.length; i < len; i++) {
newPageItems[i].transform(matrix);
}
}

function symbolsMatrix() {
var matrix = getTranslationMatrix(STEP, STEP);
for (var i = 0, len = newSymbols.length; i < len; i++) {
newSymbols[i].transform(matrix);
}
}

function itemsPosition() {
for (var i = 0, len = newPageItems.length; i < len; i++) {
var p = newPageItems[i].position;
newPageItems[i].position = [p[0] + STEP, p[1] + STEP];
}
}

function symbolsPosition() {
for (var i = 0, len = newSymbols.length; i < len; i++) {
var p = newSymbols[i].position;
newSymbols[i].position = [p[0] + STEP, p[1] + STEP];
}
}

function itemsScaleWH() {
for (var i = 0, len = newPageItems.length; i < len; i++) {
newPageItems[i].width *= SCALE;
newPageItems[i].height *= SCALE;
}
}

function symbolsScaleWH() {
for (var i = 0, len = newSymbols.length; i < len; i++) {
newSymbols[i].width *= SCALE;
newSymbols[i].height *= SCALE;
}
}

function itemsResize() {
var s = SCALE * 100;
for (var i = 0, len = newPageItems.length; i < len; i++) {
newPageItems[i].resize(s, s);
}
}

function symbolsResize() {
var s = SCALE * 100;
for (var i = 0, len = newSymbols.length; i < len; i++) {
newSymbols[i].resize(s, s);
}
}

function itemsScaleMatrix() {
var scaleMatrix = getScaleMatrix(SCALE, SCALE);
for (var i = 0, len = newPageItems.length; i < len; i++) {
newPageItems[i].transform(scaleMatrix);
}
}

function symbolsScaleMatrix() {
var scaleMatrix = getScaleMatrix(SCALE, SCALE);
for (var i = 0, len = newSymbols.length; i < len; i++) {
newSymbols[i].transform(scaleMatrix);
}
}

function callGarbageMan(){
$.gc();
}

function callPainter(){
redraw()
}

log("// Starting test, doc has " + activeDocument.pageItems.length + " pageItems");

var fns = [
findItem,
findSymbol,

placeSymbol,
placeSymbolAndLeftTop,
placeSymbolAndMatrix,
placeSymbolAndTranslate,

callPainter,

dupItem,
dupItemAndLeftTop,
dupItemAndMatrix,
dupItemAndTranslate,

callPainter,

itemsLeftTop,
symbolsLeftTop,
itemsMatrix,
symbolsMatrix,
itemsPosition,
symbolsPosition,
itemsScaleWH,
symbolsScaleWH,
itemsResize,
symbolsResize,
itemsScaleMatrix,
symbolsScaleMatrix
];

for (var i = 0, len = fns.length; i < len; i++) {
timeFunction(fns[i]);
}

})();
} catch (e) {
log(e);
}

--

--

No responses yet