6
$\begingroup$

Let's restart the kernel and take the first example from ref/ComponentMeasurements:

enter image description here

Timings indicate some kind of caching mechanism. It seems useful in many cases.

Not when you are trying to profile / optimize your code.

Question:

How to clear that cache so I can run a function again and get timings as it was the first time it sees that image?


Here's another example, which uses randomly generated input images. This way it is easy to generate a new image to run the benchmark on.

rim = RandomImage[1, {2000, 2000}];
testIm = MaxDetect[ImageAdjust@Blur[rim, 20], 0.02]

ComponentMeasurements[testIm, "Circularity"]; // AbsoluteTiming
(* {3.26826, Null} *)

ComponentMeasurements[testIm, "Circularity"]; // AbsoluteTiming
(* {0.078708, Null} *)

Another example, i1 i2 are images with dimensions: {4096, 3000}.

enter image description here

$\endgroup$
  • 1
    $\begingroup$ Okay, ClearSystemCache[] doesn't make a difference here. But my guess is that the additional time at first execution is due to loading some packages and libraries, because when I repeat that with different images in the same session, this timing difference is not as severe anymore. $\endgroup$ – Henrik Schumacher Jan 30 '19 at 10:08
  • 2
    $\begingroup$ The first call probably has to load some library files. All calls after that seem to be fast, even for different images. $\endgroup$ – Niki Estner Jan 30 '19 at 10:08
  • $\begingroup$ @NikiEstner I just commented the same, but it seems that there really is a timing difference: i.stack.imgur.com/7wAXT.png $\endgroup$ – Szabolcs Jan 30 '19 at 10:29
  • 1
    $\begingroup$ @Szabolcs: You're right. One workaround would be to call testIm = ReplacePixelValue[testIm, {1, 1} -> RandomReal[]]; between ComponentMeasurements calls, but that's a hack of course $\endgroup$ – Niki Estner Jan 30 '19 at 10:48
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$ – Kuba Jan 30 '19 at 11:37
5
$\begingroup$

As other people have noticed, there is more to a function call than just the execution of the desired computation. The most useful thing is to break apart all the important steps.

Definition loading

The first thing that happens in a fresh kernel and takes a little time, is the autoloading of the definitions of symbols that have some top level evaluation semantic defined the the binary .mx files

(* In[1] *)
OwnValues[ComponentMeasurements]

(* Out[1] *)
(* >> {HoldPattern[ComponentMeasurements] :> 
  System`Dump`AutoLoad[Hold[ComponentMeasurements], 
    Hold[ComponentMeasurements, 
     Image`MeasurementsDump`contourMeasurements, 
     Image`MeasurementsDump`findStringSlot, 
     Image`MeasurementsDump`findNumericSlot, 
     Image`MeasurementsDump`unsupportedMeasurements3D], 
    "ImageProcessing`Measurements`MeasurementSegments`"] /; 
   System`Dump`TestLoad} *)

The first evaluation of ComponentMeasurements will trigger the ownvalue rule and load the specified file

(* In[2] *)
StringDelete[
 FileNames["*MeasurementSegments*", $InstallationDirectory, 7],
 $InstallationDirectory
 ]

(* Out[2] *)
(* >> {"/SystemFiles/Kernel/SystemResources/MacOSX-x86-64/ImageProcessing/Measurements/MeasurementSegments.mx"} *)

We can isolate this timing by evaluating the symbol alone

(* In[3] *)
Table[ComponentMeasurements // AbsoluteTiming // First, 5]

(* Out[3] *)
(* >> {0.004618, 0., 0., 0., 0.} *)

Extra code loading

The first time we use a symbol as a function, more loading may happen, triggered by the downvalue code. A nice way to track that is via the TraceLoading utility, that will print all the symbols triggering extra loading.

(* In[4] *)
Get["GeneralUtilities`"];
i = Image[{{1}}];
ComponentMeasurements[i, "Elongation"] // AbsoluteTiming // First // TraceLoading

:6: "SetImage" -> "ImageProcessing`Utilities`ImageUtilities`"

:6: "MorphologicalComponents" -> "ImageProcessing`MorphOps`Components`"

:6: "Padding" -> "ImageProcessing`SpatialOps`ImagePad`"

:6: "ColorQ" -> "ImageProcessing`ColorOps`Colors`"

:6: "ContainsOnly" -> "Language`Contains`"

:6: "VectorOfImagesWithSameProperties" -> "ImageProcessing`LanguageIntegration`ImageOperations`"

:6: "Image3D" -> "ImageProcessing`ImageBasics`Image3D`"

:6: "Dataset" -> "DatasetLoader`"

"SystemFiles/Components/TypeSystem/Kernel/64Bit/TypeSystem.mx"

"SystemFiles/Components/Dataset/Kernel/64Bit/Dataset.mx"

(* Out[6] *)
(* >> 0.136793 *)

Function call

The list of dependences above reminded me that ComponentMeasurements[-image-] needs a segmentation mask, and will call MorphologicalComponents if that's not provided. To build a more accurate test we should exclude that computation from the timing (or not, depending on what exactly you want to measure).

I am going to use a random image generator for this test. It is called once to again exclude potential loading times.

(* In[7] *)
img := Binarize[Blur[RandomImage[], 2], .55]
img;

Now we can finally measure ComponentMeasurements performance

(* In[9] *)
SeedRandom[1]
Module[{i, m},
 i = img;
 m = MorphologicalComponents[i];
 ClearSystemCache[];
 ComponentMeasurements[{i, m}, "Elongation"] // AbsoluteTiming // First
]

(* Out[10] *)
(* >> 0.007916 *)

Considering that we are cleaning the cache before every run, the subsequent timings are in line with the previous result, both on the same image and a different one.

(* In[11] *)
SeedRandom[1]
Module[{i, m},
 i = img;
 m = MorphologicalComponents[i];
 ClearSystemCache[];
 ComponentMeasurements[{i, m}, "Elongation"] // AbsoluteTiming // First
]

(* Out[12] *)
(* >> 0.007862 *)

(* In[13] *)
SeedRandom[2]
Module[{i, m},
 i = img;
 m = MorphologicalComponents[i];
 ClearSystemCache[];
 ComponentMeasurements[{i, m}, "Elongation"] // AbsoluteTiming // First
]

(* Out[14] *)
(* >> 0.008051 *)
|improve this answer|||||
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.