Lightroom SDK: Severe performance bug in developPresetFolder:getDevelopPresets ()

  • 2
  • Problem
  • Updated 9 months ago
  • Acknowledged
  • (Edited)
Ever since LR 7.3 introduced the new develop preset format, it takes O(n^2) time for a plugin to access all of the develop presets. Some users have 5K, 10K, or 15K presets, and it can take minutes for a plugin to access all of the presets:
for _, folder in ipairs (LrApplication.developPresetFolders ()) do 
    for _, preset in ipairs (folder:getDevelopPresets ()) do
        ...
Users think the plugin has crashed or hung! While having 10K presets may seem unusual, there's no excuse for LR to take minutes to return all the presets.

Here's how fast my LR / Mac OS loads presets versus the total number of presets:



Most likely, LR is storing all the presets in all the folders in a single flat list.  When the method folder:getDevelopPresets() is called, it scans the entire list for presets with a matching group.  The correct implementation is obvious: Use a hierarchical representation, with a list of folders, each folder with a list of presets.
Photo of John R. Ellis

John R. Ellis, Champion

  • 4600 Posts
  • 1242 Reply Likes

Posted 9 months ago

  • 2
Photo of Alan Harper

Alan Harper

  • 457 Posts
  • 94 Reply Likes
John, they don't pay you enough for these bug reports. I do hope that Adobe has a plan for addressing them.
Photo of tpnotes

tpnotes

  • 70 Posts
  • 15 Reply Likes
Since reducing the number of preset to <2000 is an official recommendation on Adobes' support pages for opimtizing performance, I assume they do not consider this a bug: https://helpx.adobe.com/lightroom/kb/optimize-performance-lightroom.html (heading "Reduce the number of presets").
Photo of John R. Ellis

John R. Ellis, Champion

  • 4600 Posts
  • 1242 Reply Likes
That help note is talking about a different issue, the cost of generating preview thumbnails for presets: 

"Adding presets to Lightroom (whether created by you or a third-party) can reduce performance because the Develop module generates thumbnails in the Navigator panel for each preset. This is most strongly seen once you have 2,000 or more presets. Reduce the number of presets loaded into Lightroom to only those you use most often to avoid this type of slow down." [Emphasis added]

That has no bearing on a blatant bug in the SDK API that causes the simple enumeration of the presets to slow down from milliseconds to minutes.   Internally, LR has no issue with enumerating thousands of presets, since it populates the Presets panel  nearly instantaneously even with 5,000 presets.

Further, that recommendation is long out-of-date, since LR (at least since 7.3) doesn't pre-generate Navigator thumbnail previews for presets when you first open a photo in Develop or make significant changes to the sliders (which would necessitate re-generation of the thumbnails).  Since at least LR 6, LR has been fast enough to generate full-sized previews on the fly as you hover the mouse over a preset.  My Any Preset plugin did that in LR 6, and Adobe introduced that functionality into LR 7.3, and that functionality works just as fast with 5000 presets as 5.

Confirming that, my LR 8.1 shows no difference in CPU utilization when opening a photo in Develop with 5000 presets versus 5 presets.
Photo of Simon Chen

Simon Chen, Principal Computer Scientist

  • 1635 Posts
  • 550 Reply Likes
What is your use case of looping through every develop preset known by Lr? Do you just want to show the list in some UI? This SDK style of enumerating the presets is expensive because it needs to load and parse each preset from the disk, which could be slow.

If your use case are only interested in enumerating the presets to retrieve its metadata (name, path, uuid etc), it could be made fast. Once your plug-in has set its sight on a particular preset with its uuid, you can map to the actual preset settings pretty efficiently (because it only need to load only the presets that you're interested in. IOW, depending on your plug-in's use case, the up-front penalty to load all presets from the disk can or cannot be avoided. The current SDK implementation does not make this possible it seems. That should be addressed.






Photo of Simon Chen

Simon Chen, Principal Computer Scientist

  • 1635 Posts
  • 550 Reply Likes
"Adding presets to Lightroom (whether created by you or a third-party) can reduce performance because the Develop module generates thumbnails in the Navigator panel for each preset. This is most strongly seen once you have 2,000 or more presets. Reduce the number of presets loaded into Lightroom to only those you use most often to avoid this type of slow down." 

This description is not accurate for desktop version of Lightroom (maybe true for Lr mobile). Lightroom desktop does not generate thumbnails previews for Navigator for each preset when you just switch to a new photo. It only load the full XMP preset and render it on-demand (lazily fashion) as one hovers the mouse over a preset. Switching the photos, just read the preset metadata which is optimized for fast access.
Photo of John R. Ellis

John R. Ellis, Champion

  • 4600 Posts
  • 1242 Reply Likes
My Export LUT plugin displays the hierarchical list of presets, letting the user select one or more to export their develop settings as LUTs (see the screenshot below).  It just needs the folder and preset names for the entire list.

Here's example code:
local t = {}
for _, folder in ipairs (LrApplication.developPresetFolders ()) do
    for _, preset in ipairs (folder:getDevelopPresets ()) do
        table.insert (t, {folder = folder:getName (), 
            preset = preset:getName ()})
        end
    end
I don't think the cost of reading and parsing the .xmp files is related to this issue, since it appears that the SDK is already deferring the reading until preset:getSetting() is called.  I've verified this two ways:

1. Windows Process Monitor shows that the .xmp files aren't read by the code above. They're only read when preset:getSetting() is called.

2. Timing the code above and similar code that also calls preset:getSetting() shows a 5x increase in time per preset when preset:getSetting() is called (scripts below):




Also, the cost to read and parse should be O(n), directly proportional to the number of presets, not increasing as O(n^2), as is the case with getting the entire list of folder and preset names.

This problem only surfaced in 7.3 with the new preset format shared with ACR. I'd bet money that the new implementation of folder:getDevelopPresets() scans the entire list of in-memory presets, returning those whose group (folder) is equal to folder:getName().  Another hint that suggests this: The new .xmp format stores the group name in the .xmp file itself, and the directory structure is ignored, so the UI implementation may just create a flat list of all the presets, constructing the group structure on the fly.  The UI does this correctly, the SDK not.
--------------------------------------------------



--------------------------------------------------
local LrApplication = import "LrApplication"
local LrDate = import "LrDate"
local LrDialogs = import "LrDialogs"
local t, time = {}, LrDate.currentTime ()
for _, folder in ipairs (LrApplication.developPresetFolders ()) do
    for _, preset in ipairs (folder:getDevelopPresets ()) do
        table.insert (t, {folder = folder:getName (), 
            preset = preset:getName ()})
        end
    end
time = LrDate.currentTime () - time 
LrDialogs.message ("Folder name, preset name",
    "# presets = " .. #t .. "\ntime/preset = " .. time / #t)
--------------------------------------------------
local LrApplication = import "LrApplication"
local LrDate = import "LrDate"
local LrDialogs = import "LrDialogs"
local t, time = {}, LrDate.currentTime ()
for _, folder in ipairs (LrApplication.developPresetFolders ()) do
    for _, preset in ipairs (folder:getDevelopPresets ()) do
        table.insert (t, {folder = folder:getName (), 
            preset = preset:getName (), 
            settings = preset:getSetting ()})
        end
    end
time = LrDate.currentTime () - time 
LrDialogs.message ("Folder name, preset name, settings",
    "# presets = " .. #t .. "\ntime/preset = " .. time / #t)