Skip to content

Commit 7ea50a5

Browse files
authored
Merge pull request #108 from blowekamp/more_iteration_benchmark
ENH: benchmark for iterators doing a static cast of pixel
2 parents 93bd538 + 7fb08c1 commit 7ea50a5

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed

examples/Core/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,14 @@ add_test(
2929
set_property(TEST VectorIterationBenchmark APPEND PROPERTY LABELS Core)
3030
## performance tests should not be run in parallel
3131
set_tests_properties(VectorIterationBenchmark PROPERTIES RUN_SERIAL TRUE)
32+
33+
add_executable(CopyIterationBenchmark itkCopyIterationBenchmark.cxx )
34+
target_link_libraries(CopyIterationBenchmark ${ITK_LIBRARIES})
35+
add_test(
36+
NAME CopyIterationBenchmark
37+
COMMAND CopyIterationBenchmark
38+
${BENCHMARK_RESULTS_OUTPUT_DIR}/__DATESTAMP__CopyIterationBenchmark.json
39+
25 128 )
40+
set_property(TEST CopyIterationBenchmark APPEND PROPERTY LABELS Core)
41+
## performance tests should not be run in parallel
42+
set_tests_properties(CopyIterationBenchmark PROPERTIES RUN_SERIAL TRUE)
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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 compares the performance of ImageRegionIterator, ImageScanlineIterator,
20+
// and ImageRegionRange for simple pixel copying with static_cast operations.
21+
22+
#include <iostream>
23+
#include "itkImage.h"
24+
#include "itkVectorImage.h"
25+
#include "itkFixedArray.h"
26+
#include "itkImageRegionRange.h"
27+
#include "itkImageScanlineIterator.h"
28+
#include "itkImageRegionIterator.h"
29+
#include "itkImageAlgorithm.h"
30+
#include "itkHighPriorityRealTimeProbesCollector.h"
31+
#include "PerformanceBenchmarkingUtilities.h"
32+
#include <iomanip>
33+
#include <fstream>
34+
35+
36+
// Helper function to initialize an image with random values
37+
template <typename TImage>
38+
typename TImage::Pointer
39+
CreateAndInitializeImage(const typename TImage::SizeType & size, unsigned int numberOfComponentsPerPixel = 0)
40+
{
41+
auto image = TImage::New();
42+
typename TImage::RegionType region{ size };
43+
image->SetRegions(region);
44+
if (numberOfComponentsPerPixel > 0)
45+
{
46+
image->SetNumberOfComponentsPerPixel(numberOfComponentsPerPixel);
47+
}
48+
image->Allocate();
49+
50+
// Initialize with simple pattern (pixel index-based)
51+
using PixelType = typename TImage::PixelType;
52+
unsigned int count = 0;
53+
54+
itk::ImageRegionIterator<TImage> it(image, region);
55+
for (; !it.IsAtEnd(); ++it)
56+
{
57+
it.Set(static_cast<PixelType>(count));
58+
++count;
59+
}
60+
61+
return image;
62+
}
63+
64+
65+
// Method 0: ImageAlgorithm::Copy
66+
template <typename TInputImage, typename TOutputImage>
67+
void
68+
CopyImageAlgorithm(const TInputImage * inputPtr, TOutputImage * outputPtr)
69+
{
70+
itk::ImageAlgorithm::Copy(inputPtr, outputPtr, inputPtr->GetBufferedRegion(), outputPtr->GetBufferedRegion());
71+
}
72+
73+
// Method 1: ImageRegionIterator approach
74+
template <typename TInputImage, typename TOutputImage>
75+
void
76+
CopyRegionIterator(const TInputImage * inputPtr, TOutputImage * outputPtr)
77+
{
78+
using InputPixelType = typename TInputImage::PixelType;
79+
using OutputPixelType = typename TOutputImage::PixelType;
80+
using ImageRegionConstIterator = itk::ImageRegionConstIterator<TInputImage>;
81+
using ImageRegionIterator = itk::ImageRegionIterator<TOutputImage>;
82+
83+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
84+
typename TInputImage::RegionType inputRegion = outputRegion;
85+
86+
ImageRegionConstIterator inputIt(inputPtr, inputRegion);
87+
ImageRegionIterator outputIt(outputPtr, outputRegion);
88+
89+
for (; !inputIt.IsAtEnd(); ++inputIt, ++outputIt)
90+
{
91+
outputIt.Set(static_cast<OutputPixelType>(inputIt.Get()));
92+
}
93+
}
94+
95+
96+
// Method 2: ImageScanlineIterator approach
97+
template <typename TInputImage, typename TOutputImage>
98+
void
99+
CopyScanlineIterator(const TInputImage * inputPtr, TOutputImage * outputPtr)
100+
{
101+
using InputPixelType = typename TInputImage::PixelType;
102+
using OutputPixelType = typename TOutputImage::PixelType;
103+
using ImageScanlineConstIterator = itk::ImageScanlineConstIterator<TInputImage>;
104+
using ImageScanlineIterator = itk::ImageScanlineIterator<TOutputImage>;
105+
106+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
107+
typename TInputImage::RegionType inputRegion = outputRegion;
108+
109+
ImageScanlineConstIterator inputIt(inputPtr, inputRegion);
110+
ImageScanlineIterator outputIt(outputPtr, outputRegion);
111+
112+
while (!inputIt.IsAtEnd())
113+
{
114+
while (!inputIt.IsAtEndOfLine())
115+
{
116+
outputIt.Set(static_cast<OutputPixelType>(inputIt.Get()));
117+
++inputIt;
118+
++outputIt;
119+
}
120+
inputIt.NextLine();
121+
outputIt.NextLine();
122+
}
123+
}
124+
125+
126+
// Method 3: ImageRegionRange approach
127+
template <typename TInputImage, typename TOutputImage>
128+
void
129+
CopyImageRegionRange(const TInputImage * inputPtr, TOutputImage * outputPtr)
130+
{
131+
using InputPixelType = typename TInputImage::PixelType;
132+
using OutputPixelType = typename TOutputImage::PixelType;
133+
134+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
135+
typename TInputImage::RegionType inputRegion = outputRegion;
136+
137+
auto inputRange = itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion);
138+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
139+
140+
auto inputIt = inputRange.begin();
141+
auto outputIt = outputRange.begin();
142+
const auto inputEnd = inputRange.end();
143+
144+
while (inputIt != inputEnd)
145+
{
146+
*outputIt = OutputPixelType(InputPixelType(*inputIt));
147+
++inputIt;
148+
++outputIt;
149+
}
150+
}
151+
152+
153+
// Method 4: ImageRegionRange with range-based for loop
154+
template <typename TInputImage, typename TOutputImage>
155+
void
156+
CopyImageRegionRangeForLoop(const TInputImage * inputPtr, TOutputImage * outputPtr)
157+
{
158+
using InputPixelType = typename TInputImage::PixelType;
159+
using OutputPixelType = typename TOutputImage::PixelType;
160+
161+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
162+
typename TInputImage::RegionType inputRegion = outputRegion;
163+
164+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
165+
auto outputIt = outputRange.begin();
166+
167+
for (const InputPixelType & inputPixel : itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion))
168+
{
169+
*outputIt = static_cast<OutputPixelType>(inputPixel);
170+
++outputIt;
171+
}
172+
}
173+
174+
175+
// Helper function to time a single method
176+
template <typename TInputImage, typename TOutputImage, typename TCopyFunction>
177+
void
178+
TimeMethod(itk::HighPriorityRealTimeProbesCollector & collector,
179+
const std::string & methodName,
180+
TCopyFunction copyFunc,
181+
const TInputImage * inputImage,
182+
typename TOutputImage::Pointer & outputImage,
183+
int iterations)
184+
{
185+
// Allocate output image
186+
outputImage = TOutputImage::New();
187+
outputImage->SetRegions(inputImage->GetLargestPossibleRegion());
188+
if (inputImage->GetNumberOfComponentsPerPixel() > 0)
189+
{
190+
outputImage->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel());
191+
}
192+
outputImage->Allocate();
193+
194+
// Warm-up run
195+
copyFunc(inputImage, outputImage.GetPointer());
196+
197+
// Timed runs
198+
for (int ii = 0; ii < iterations; ++ii)
199+
{
200+
collector.Start(methodName.c_str());
201+
copyFunc(inputImage, outputImage.GetPointer());
202+
collector.Stop(methodName.c_str());
203+
}
204+
}
205+
206+
207+
// Performance testing function
208+
template <typename TInputImage, typename TOutputImage>
209+
void
210+
TimeIterationMethods(itk::HighPriorityRealTimeProbesCollector & collector,
211+
const typename TInputImage::SizeType & size,
212+
const std::string & description,
213+
int iterations,
214+
unsigned int numberOfComponentsPerPixel = 0)
215+
{
216+
// Create and initialize input image
217+
auto inputImage = CreateAndInitializeImage<TInputImage>(size, numberOfComponentsPerPixel);
218+
219+
typename TOutputImage::Pointer outputImage;
220+
221+
// Test Method 0: ImageAlgorithm::Copy
222+
TimeMethod<TInputImage, TOutputImage>(collector,
223+
description + "-ImageAlgorithm",
224+
CopyImageAlgorithm<TInputImage, TOutputImage>,
225+
inputImage.GetPointer(),
226+
outputImage,
227+
iterations);
228+
229+
// Test Method 1: Region Iterator
230+
TimeMethod<TInputImage, TOutputImage>(collector,
231+
description + "-RegionIterator",
232+
CopyRegionIterator<TInputImage, TOutputImage>,
233+
inputImage.GetPointer(),
234+
outputImage,
235+
iterations);
236+
237+
// Test Method 2: Scanline Iterator
238+
TimeMethod<TInputImage, TOutputImage>(collector,
239+
description + "-ScanlineIterator",
240+
CopyScanlineIterator<TInputImage, TOutputImage>,
241+
inputImage.GetPointer(),
242+
outputImage,
243+
iterations);
244+
245+
// Test Method 3: ImageRegionRange
246+
TimeMethod<TInputImage, TOutputImage>(collector,
247+
description + "-Range",
248+
CopyImageRegionRange<TInputImage, TOutputImage>,
249+
inputImage.GetPointer(),
250+
outputImage,
251+
iterations);
252+
253+
// Test Method 4: ImageRegionRange with range-based for loop
254+
TimeMethod<TInputImage, TOutputImage>(collector,
255+
description + "-RangeForLoop",
256+
CopyImageRegionRangeForLoop<TInputImage, TOutputImage>,
257+
inputImage.GetPointer(),
258+
outputImage,
259+
iterations);
260+
}
261+
262+
263+
int
264+
main(int argc, char * argv[])
265+
{
266+
if (argc < 4)
267+
{
268+
std::cerr << "Usage: " << std::endl;
269+
std::cerr << argv[0] << " timingsFile iterations imageSize" << std::endl;
270+
return EXIT_FAILURE;
271+
}
272+
const std::string timingsFileName = ReplaceOccurrence(argv[1], "__DATESTAMP__", PerfDateStamp());
273+
const int iterations = std::stoi(argv[2]);
274+
const int imageSize = std::stoi(argv[3]);
275+
276+
constexpr unsigned int Dimension = 3;
277+
const auto size = itk::Size<Dimension>::Filled(imageSize);
278+
279+
itk::HighPriorityRealTimeProbesCollector collector;
280+
281+
// Test 1: uint16 to int16
282+
TimeIterationMethods<itk::Image<unsigned short, Dimension>, itk::Image<short, Dimension>>(
283+
collector, size, "Iu2->Ii2", iterations);
284+
285+
// Test 2: FixedArray<float,3> to FixedArray<double,3>
286+
TimeIterationMethods<itk::Image<itk::FixedArray<float, 3>, Dimension>,
287+
itk::Image<itk::FixedArray<double, 3>, Dimension>>(collector, size, "IFf3->IFd3", iterations);
288+
289+
// Test 3: VectorImage<float> to VectorImage<double> with 3 components
290+
TimeIterationMethods<itk::VectorImage<float, Dimension>, itk::VectorImage<double, Dimension>>(
291+
collector, size, "IVf->IVd", iterations, 3);
292+
293+
WriteExpandedReport(timingsFileName, collector, true, true, false);
294+
295+
return EXIT_SUCCESS;
296+
}

0 commit comments

Comments
 (0)