Skip to content

Commit b214630

Browse files
committed
ENH: Add benchmark for iterator loop comparison
Added precommit hook to automatically run clang-format.
1 parent b53f037 commit b214630

File tree

2 files changed

+388
-0
lines changed

2 files changed

+388
-0
lines changed

examples/Core/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@ add_test(
1818
set_property(TEST ThreadOverheadBenchmark APPEND PROPERTY LABELS Core)
1919
## performance tests should not be run in parallel
2020
set_tests_properties(ThreadOverheadBenchmark PROPERTIES RUN_SERIAL TRUE)
21+
22+
add_executable(VectorIterationBenchmark itkVectorIterationBenchmark.cxx )
23+
target_link_libraries(VectorIterationBenchmark ${ITK_LIBRARIES})
24+
add_test(
25+
NAME VectorIterationBenchmark
26+
COMMAND VectorIterationBenchmark
27+
${BENCHMARK_RESULTS_OUTPUT_DIR}/__DATESTAMP__VectorIterationBenchmark.json
28+
50 128 )
29+
set_property(TEST VectorIterationBenchmark APPEND PROPERTY LABELS Core)
30+
## performance tests should not be run in parallel
31+
set_tests_properties(VectorIterationBenchmark PROPERTIES RUN_SERIAL TRUE)
Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
// This Benchmark time the performance of different iteration loop while converting between various vector image and
20+
// pixel types.
21+
22+
23+
#include <iostream>
24+
#include "itkImage.h"
25+
#include "itkVectorImage.h"
26+
#include "itkVector.h"
27+
#include "itkRGBPixel.h"
28+
#include "itkImageRegionRange.h"
29+
#include "itkImageScanlineIterator.h"
30+
#include "itkImageScanlineConstIterator.h"
31+
#include "itkImageRegionIterator.h"
32+
#include "itkHighPriorityRealTimeProbesCollector.h"
33+
#include "PerformanceBenchmarkingUtilities.h"
34+
#include "itkNumericTraits.h"
35+
#include <iomanip>
36+
#include <fstream>
37+
38+
39+
// Helper function to initialize an image with random values
40+
template <typename TImage>
41+
typename TImage::Pointer
42+
CreateAndInitializeImage(const typename TImage::SizeType & size, unsigned int numberOfComponentsPerPixel = 0)
43+
{
44+
auto image = TImage::New();
45+
typename TImage::RegionType region{ size };
46+
image->SetRegions(region);
47+
if (numberOfComponentsPerPixel > 0)
48+
{
49+
image->SetNumberOfComponentsPerPixel(numberOfComponentsPerPixel);
50+
}
51+
image->Allocate();
52+
53+
// Initialize with simple pattern (pixel index-based)
54+
using PixelType = typename TImage::PixelType;
55+
unsigned int count = 0;
56+
57+
const unsigned int length = image->GetNumberOfComponentsPerPixel();
58+
59+
itk::ImageRegionIterator<TImage> it(image, region);
60+
for (it.GoToBegin(); !it.IsAtEnd(); ++it)
61+
{
62+
PixelType pixel{ it.Get() };
63+
for (unsigned int k = 0; k < length; ++k)
64+
{
65+
pixel[k] = static_cast<typename itk::NumericTraits<PixelType>::ValueType>(count + k);
66+
}
67+
it.Set(pixel);
68+
++count;
69+
}
70+
71+
return image;
72+
}
73+
74+
75+
// Method 1: ImageScanlineIterator approach
76+
template <typename TInputImage, typename TOutputImage>
77+
void
78+
CopyScanlineIterator(const TInputImage * inputPtr, TOutputImage * outputPtr)
79+
{
80+
using InputPixelType = typename TInputImage::PixelType;
81+
using OutputPixelType = typename TOutputImage::PixelType;
82+
using ImageScanlineConstIterator = itk::ImageScanlineConstIterator<TInputImage>;
83+
using ImageScanlineIterator = itk::ImageScanlineIterator<TOutputImage>;
84+
85+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
86+
typename TInputImage::RegionType inputRegion = outputRegion;
87+
88+
ImageScanlineConstIterator inputIt(inputPtr, inputRegion);
89+
ImageScanlineIterator outputIt(outputPtr, outputRegion);
90+
91+
const unsigned int componentsPerPixel = inputPtr->GetNumberOfComponentsPerPixel();
92+
while (!inputIt.IsAtEnd())
93+
{
94+
while (!inputIt.IsAtEndOfLine())
95+
{
96+
const InputPixelType & inputPixel = inputIt.Get();
97+
OutputPixelType value(outputIt.Get());
98+
for (unsigned int k = 0; k < componentsPerPixel; ++k)
99+
{
100+
value[k] = static_cast<typename OutputPixelType::ValueType>(inputPixel[k]);
101+
}
102+
outputIt.Set(value);
103+
104+
++inputIt;
105+
++outputIt;
106+
}
107+
inputIt.NextLine();
108+
outputIt.NextLine();
109+
}
110+
}
111+
112+
113+
// Method 1b: ImageScanlineIterator approach using NumericTraits::GetLength()
114+
template <typename TInputImage, typename TOutputImage>
115+
void
116+
CopyScanlineIteratorNumericTraits(const TInputImage * inputPtr, TOutputImage * outputPtr)
117+
{
118+
using InputPixelType = typename TInputImage::PixelType;
119+
using OutputPixelType = typename TOutputImage::PixelType;
120+
using ImageScanlineConstIterator = itk::ImageScanlineConstIterator<TInputImage>;
121+
using ImageScanlineIterator = itk::ImageScanlineIterator<TOutputImage>;
122+
123+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
124+
typename TInputImage::RegionType inputRegion = outputRegion;
125+
126+
ImageScanlineConstIterator inputIt(inputPtr, inputRegion);
127+
ImageScanlineIterator outputIt(outputPtr, outputRegion);
128+
129+
unsigned int componentsPerPixel = itk::NumericTraits<OutputPixelType>::GetLength(outputIt.Get());
130+
while (!inputIt.IsAtEnd())
131+
{
132+
while (!inputIt.IsAtEndOfLine())
133+
{
134+
const InputPixelType & inputPixel = inputIt.Get();
135+
136+
OutputPixelType value{ outputIt.Get() };
137+
for (unsigned int k = 0; k < componentsPerPixel; ++k)
138+
{
139+
value[k] = static_cast<typename OutputPixelType::ValueType>(inputPixel[k]);
140+
}
141+
outputIt.Set(value);
142+
143+
++inputIt;
144+
++outputIt;
145+
}
146+
inputIt.NextLine();
147+
outputIt.NextLine();
148+
}
149+
}
150+
151+
152+
// Method 2: ImageRegionRange approach
153+
template <typename TInputImage, typename TOutputImage>
154+
void
155+
CopyImageRegionRange(const TInputImage * inputPtr, TOutputImage * outputPtr)
156+
{
157+
using InputPixelType = typename TInputImage::PixelType;
158+
using OutputPixelType = typename TOutputImage::PixelType;
159+
160+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
161+
typename TInputImage::RegionType inputRegion = outputRegion;
162+
163+
auto inputRange = itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion);
164+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
165+
166+
auto inputIt = inputRange.begin();
167+
auto outputIt = outputRange.begin();
168+
const auto inputEnd = inputRange.end();
169+
170+
const unsigned int componentsPerPixel = inputPtr->GetNumberOfComponentsPerPixel();
171+
while (inputIt != inputEnd)
172+
{
173+
const InputPixelType & inputPixel = *inputIt;
174+
OutputPixelType outputPixel{ *outputIt };
175+
for (unsigned int k = 0; k < componentsPerPixel; ++k)
176+
{
177+
outputPixel[k] = static_cast<typename OutputPixelType::ValueType>(inputPixel[k]);
178+
}
179+
*outputIt = outputPixel;
180+
181+
++inputIt;
182+
++outputIt;
183+
}
184+
}
185+
186+
187+
// Method 2b: ImageRegionRange approach using NumericTraits::GetLength()
188+
template <typename TInputImage, typename TOutputImage>
189+
void
190+
CopyImageRegionRangeNumericTraits(const TInputImage * inputPtr, TOutputImage * outputPtr)
191+
{
192+
using InputPixelType = typename TInputImage::PixelType;
193+
using OutputPixelType = typename TOutputImage::PixelType;
194+
195+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
196+
typename TInputImage::RegionType inputRegion = outputRegion;
197+
198+
auto inputRange = itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion);
199+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
200+
201+
auto inputIt = inputRange.begin();
202+
auto outputIt = outputRange.begin();
203+
const auto inputEnd = inputRange.end();
204+
205+
const unsigned int componentsPerPixel = itk::NumericTraits<OutputPixelType>::GetLength(*outputIt);
206+
while (inputIt != inputEnd)
207+
{
208+
const InputPixelType & inputPixel = *inputIt;
209+
OutputPixelType outputPixel{ *outputIt };
210+
for (unsigned int k = 0; k < componentsPerPixel; ++k)
211+
{
212+
outputPixel[k] = static_cast<typename OutputPixelType::ValueType>(inputPixel[k]);
213+
}
214+
*outputIt = outputPixel;
215+
++inputIt;
216+
++outputIt;
217+
}
218+
}
219+
220+
221+
// Method 2c: ImageRegionRange and a Range-based loop using NumericTraits::GetLength()
222+
template <typename TInputImage, typename TOutputImage>
223+
void
224+
CopyImageRegionRangeNumericTraitsAsRange(const TInputImage * inputPtr, TOutputImage * outputPtr)
225+
{
226+
using InputPixelType = typename TInputImage::PixelType;
227+
using OutputPixelType = typename TOutputImage::PixelType;
228+
229+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
230+
typename TInputImage::RegionType inputRegion = outputRegion;
231+
232+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
233+
auto outputIt = outputRange.begin();
234+
235+
const unsigned int componentsPerPixel = itk::NumericTraits<OutputPixelType>::GetLength(*outputIt);
236+
for (const InputPixelType & inputPixel : itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion))
237+
{
238+
OutputPixelType outputPixel{ *outputIt };
239+
for (unsigned int k = 0; k < componentsPerPixel; ++k)
240+
{
241+
outputPixel[k] = static_cast<typename OutputPixelType::ValueType>(inputPixel[k]);
242+
}
243+
*outputIt = outputPixel;
244+
++outputIt;
245+
}
246+
}
247+
248+
249+
// Helper function to time a single method
250+
template <typename TInputImage, typename TOutputImage, typename TCopyFunction>
251+
void
252+
TimeMethod(itk::HighPriorityRealTimeProbesCollector & collector,
253+
const std::string & methodName,
254+
TCopyFunction copyFunc,
255+
const TInputImage * inputImage,
256+
typename TOutputImage::Pointer & outputImage,
257+
int iterations)
258+
{
259+
// Allocate output image
260+
outputImage = TOutputImage::New();
261+
outputImage->SetRegions(inputImage->GetLargestPossibleRegion());
262+
outputImage->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel());
263+
outputImage->Allocate();
264+
265+
// Warm-up run
266+
copyFunc(inputImage, outputImage.GetPointer());
267+
268+
// Timed runs
269+
for (int ii = 0; ii < iterations; ++ii)
270+
{
271+
collector.Start(methodName.c_str());
272+
copyFunc(inputImage, outputImage.GetPointer());
273+
collector.Stop(methodName.c_str());
274+
}
275+
}
276+
277+
278+
// Performance testing function
279+
template <typename TInputImage, typename TOutputImage>
280+
void
281+
TimeIterationMethods(itk::HighPriorityRealTimeProbesCollector & collector,
282+
const typename TInputImage::SizeType & size,
283+
const std::string & description,
284+
int iterations)
285+
{
286+
287+
// Create and initialize input image
288+
auto inputImage = CreateAndInitializeImage<TInputImage>(size, 3);
289+
290+
typename TOutputImage::Pointer referenceImage;
291+
typename TOutputImage::Pointer outputImage;
292+
293+
// Test Method 1: Scanline Iterator - serves as reference
294+
TimeMethod<TInputImage, TOutputImage>(collector,
295+
description + "-Scanline",
296+
CopyScanlineIterator<TInputImage, TOutputImage>,
297+
inputImage.GetPointer(),
298+
outputImage,
299+
iterations);
300+
301+
// Test Method 2: ImageRegionRange
302+
TimeMethod<TInputImage, TOutputImage>(collector,
303+
description + "-Range",
304+
CopyImageRegionRange<TInputImage, TOutputImage>,
305+
inputImage.GetPointer(),
306+
outputImage,
307+
iterations);
308+
309+
// Test Method 1b: Scanline Iterator with NumericTraits
310+
TimeMethod<TInputImage, TOutputImage>(collector,
311+
description + "-Scanline NT",
312+
CopyScanlineIteratorNumericTraits<TInputImage, TOutputImage>,
313+
inputImage.GetPointer(),
314+
outputImage,
315+
iterations);
316+
317+
// Test Method 2b: ImageRegionRange with NumericTraits
318+
TimeMethod<TInputImage, TOutputImage>(collector,
319+
description + "-Range NT",
320+
CopyImageRegionRangeNumericTraits<TInputImage, TOutputImage>,
321+
inputImage.GetPointer(),
322+
outputImage,
323+
iterations);
324+
325+
// Test Method 2c: ImageRegionRange with NumericTraits - buggy version
326+
TimeMethod<TInputImage, TOutputImage>(collector,
327+
description + "-Range NT AsRange",
328+
CopyImageRegionRangeNumericTraitsAsRange<TInputImage, TOutputImage>,
329+
inputImage.GetPointer(),
330+
outputImage,
331+
iterations);
332+
}
333+
334+
335+
int
336+
main(int argc, char * argv[])
337+
{
338+
if (argc < 4)
339+
{
340+
std::cerr << "Usage: " << std::endl;
341+
std::cerr << argv[0] << " timingsFile iterations imageSize" << std::endl;
342+
return EXIT_FAILURE;
343+
}
344+
const std::string timingsFileName = ReplaceOccurrence(argv[1], "__DATESTAMP__", PerfDateStamp());
345+
const int iterations = std::stoi(argv[2]);
346+
const int imageSize = std::stoi(argv[3]);
347+
348+
constexpr unsigned int Dimension = 3;
349+
const auto size = itk::Size<Dimension>::Filled(imageSize);
350+
351+
std::ostringstream oss;
352+
oss << "Image Size: " << size;
353+
std::string description = oss.str();
354+
355+
itk::HighPriorityRealTimeProbesCollector collector;
356+
357+
// Test 1: Image<Vector> to Image<RGBPixel>
358+
TimeIterationMethods<itk::Image<itk::Vector<float, 3>, Dimension>, itk::Image<itk::RGBPixel<double>, Dimension>>(
359+
collector, size, "IVf3->IRGB", iterations);
360+
361+
// Test 2: VectorImage to Image<Vector>
362+
TimeIterationMethods<itk::VectorImage<float, Dimension>, itk::Image<itk::Vector<double, 3>, Dimension>>(
363+
collector, size, "VIf->IVd3", iterations);
364+
365+
// Test 3: Image<Vector> to VectorImage
366+
TimeIterationMethods<itk::Image<itk::Vector<float, 3>, Dimension>, itk::VectorImage<double, Dimension>>(
367+
collector, size, "IVf3->VId", iterations);
368+
369+
// Test 4: VectorImage to VectorImage
370+
TimeIterationMethods<itk::VectorImage<float, Dimension>, itk::VectorImage<double, Dimension>>(
371+
collector, size, "VIf->VId", iterations);
372+
373+
WriteExpandedReport(timingsFileName, collector, true, true, false);
374+
375+
376+
return EXIT_SUCCESS;
377+
}

0 commit comments

Comments
 (0)