Skip to content

Commit d9527e6

Browse files
committed
get_multiapr can now repeat processing of input data, skip saving APRs and limit log messages for benchmarking purposes
1 parent 890283c commit d9527e6

5 files changed

Lines changed: 117 additions & 35 deletions

File tree

examples/Example_get_multiapr.cpp

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ Additional settings (High Level):
1212
-sigma_th lower threshold for the local intensity scale
1313
-grad_th ignore areas in the image where the gradient magnitude is lower than this value
1414
15+
-skipOutputMessages produce less output/debug messages
16+
-doNotSaveAPRs do not save output APR files (good for benchmarking)
17+
-r number of repetitions - default value = 1 means that all input files are processed only once
18+
for higher number input files are processed multiple times (good for benchmarking)
19+
1520
Advanced (Direct) Settings:
1621
===========================
1722
-lambda lambda_value (directly set the value of the gradient smoothing parameter lambda (reasonable range 0.1-10, default: 3)
@@ -43,6 +48,11 @@ struct cmdLineOptions {
4348
float rel_error = 0.1;
4449

4550
bool neighborhood_optimization = true;
51+
52+
int numOfRepetitions = 1;
53+
int numOfStreams = 3;
54+
bool doNotSaveAPRs = false;
55+
bool skipOutputMessages = false;
4656
};
4757

4858
bool command_option_exists(const char **begin, const char **end, const std::string &option)
@@ -112,10 +122,26 @@ cmdLineOptions read_command_line_options(const int argc, const char **argv) {
112122
options.neighborhood_optimization = false;
113123
}
114124

125+
if (command_option_exists(argv, argv + argc, "-skipOutputMessages")) {
126+
options.skipOutputMessages = true;
127+
}
128+
129+
if (command_option_exists(argv, argv + argc, "-doNotSaveAPRs")) {
130+
options.doNotSaveAPRs = true;
131+
}
132+
133+
if (command_option_exists(argv, argv + argc, "-r")) {
134+
options.numOfRepetitions = std::stoi(std::string(get_command_option(argv, argv + argc, "-r")));
135+
}
136+
137+
if (command_option_exists(argv, argv + argc, "-numOfStreams")) {
138+
options.numOfStreams = std::stoi(std::string(get_command_option(argv, argv + argc, "-numOfStreams")));
139+
}
140+
115141
return options;
116142
}
117143

118-
144+
/* Finds all tiff files (with possible different extensions) in provided directory */
119145
auto getTiffFilesFromDir(const std::string &directory_path) {
120146
namespace fs = std::filesystem;
121147

@@ -124,8 +150,7 @@ auto getTiffFilesFromDir(const std::string &directory_path) {
124150
try {
125151
for (const auto& entry : fs::directory_iterator(directory_path)) {
126152
if (entry.is_regular_file()) {
127-
auto ext = entry.path().extension().string();
128-
if (ext == ".tif" || ext == ".tiff" || ext == ".TIF" || ext == ".TIFF") {
153+
if (auto ext = entry.path().extension().string(); ext == ".tif" || ext == ".tiff" || ext == ".TIF" || ext == ".TIFF") {
129154
tif_files.push_back(entry.path());
130155
}
131156
}
@@ -191,44 +216,61 @@ int runAPR(const cmdLineOptions &options) {
191216
partIntensities.push_back(std::make_unique<VectorData<ImgType>>(VectorData<ImgType>{}));
192217
partIntensities_raw.push_back(partIntensities.back().get());
193218
}
219+
// To proces input image multiple times (for benchmarking etc.) we 'multiply input data' by adding extra APR and particle intensity objects and
220+
// by copying input raw pointer to images to 'pretend' that we have a lot of input images.
221+
size_t numOfInputImages = input_images_raw.size();
222+
for (int m = 1; m < options.numOfRepetitions; m++) {
223+
for (size_t n = 0; n < numOfInputImages; n++) {
224+
input_images_raw.push_back(input_images[n].get());
225+
APRs.push_back(std::make_unique<APR>(APR{}));
226+
APRs_raw.push_back(APRs.back().get());
227+
partIntensities.push_back(std::make_unique<VectorData<ImgType>>(VectorData<ImgType>{}));
228+
partIntensities_raw.push_back(partIntensities.back().get());
229+
}
230+
}
194231

195232
std::cout << std::endl;
196233

197-
APRTimer timer(true);
234+
APRTimer timer(false);
198235
timer.start_timer("GPU pipeline (mem allocation, processing, sampling) ");
199-
if (aprConverter.get_apr_cuda_multistreams(APRs_raw, input_images_raw, partIntensities_raw)) {
236+
if (aprConverter.get_apr_cuda_multistreams(APRs_raw, input_images_raw, partIntensities_raw, options.numOfStreams)) {
200237
timer.stop_timer();
201-
size_t numOfImages = input_images_raw.size();
202-
std::cout << std::endl;
238+
size_t numOfImagesToProcess = input_images_raw.size(); // might be 'processInputMultipleTimes' times bigger than num of input images
239+
if (!options.skipOutputMessages) std::cout << std::endl;
203240

204-
for (size_t i = 0; i < numOfImages; i++) {
205-
std::cout << "Postprocessing " << i+1 << "/" << numOfImages << " image...\n";
241+
for (size_t i = 0; i < numOfImagesToProcess && !options.doNotSaveAPRs; i++) {
242+
if (!options.skipOutputMessages) std::cout << "Postprocessing " << i+1 << "/" << numOfImagesToProcess << " image...\n";
206243
auto &apr = *APRs[i].get(); // currently process APR
207244
auto &particle_intensities = *partIntensities[i].get(); // intensities sampled for current APR
208245

209246

210247
// ------------ TODO: remove me later, this is quick test for Cpu vs Gpu before real test is written
211248
// std::cout << apr.linearAccess.y_vec.size() << " particles in APR" << std::endl;
212249
// std::cout << particle_intensities.size() << " intensities in CPU in APR" << std::endl;
213-
// if (apr.linearAccess.y_vec.size() != particle_intensities.size()) {std::cerr << "CPU vs GPU number of particles differ!" << std::endl;}
250+
if (apr.linearAccess.y_vec.size() != particle_intensities.size()) {std::cerr << "CPU vs GPU number of particles differ!" << std::endl;}
214251
ParticleData<ImgType> particle_intensities_cpu;
215-
particle_intensities_cpu.sample_image(apr, *input_images[i].get()); // sample your particles from your image
252+
particle_intensities_cpu.sample_image(apr, *input_images_raw[i]); // sample your particles from your image
253+
int errorCnt = 0;
216254
for (size_t j = 0 ; j < particle_intensities.size(); ++j) {
217255
if (particle_intensities_cpu[j] != particle_intensities[j]) {
218-
std::cout << "Mismatch at " << j << " CPU: " << particle_intensities_cpu[j] << " GPU: " << particle_intensities[j] << std::endl;
256+
errorCnt++;
257+
// std::cout << "Mismatch at " << j << " CPU: " << particle_intensities_cpu[j] << " GPU: " << particle_intensities[j] << std::endl;
219258
}
220259
}
260+
if (errorCnt > 0) std::cout << errorCnt << " errors for index=" << i << std::endl;
221261
// ---------------------------------------------------------------------------------------------------
222262

223263
// Output name is like base of input filename + extension ".apr"
264+
// Extra number is added for 'multiplied' input images
224265
auto outputDir = std::filesystem::path(options.output_dir);
225-
const std::filesystem::path& p(tifFiles[i]);
226-
std::string outpuFileName = p.stem().string() + ".apr";
266+
const std::filesystem::path& p(tifFiles[i % numOfInputImages]);
267+
std::string num = (i >= numOfInputImages) ? std::to_string(i) : "";
268+
std::string outputFileName = p.stem().string() + num + ".apr";
227269

228270
//write the APR to hdf5 file
229271
timer.start_timer("writing output");
230272
APRFile aprFile;
231-
aprFile.open(outputDir / outpuFileName);
273+
aprFile.open(outputDir / outputFileName);
232274
aprFile.write_apr(apr, 0, "t", false);
233275
ParticleData<ImgType> pd;
234276
pd.data = std::move(particle_intensities);
@@ -239,10 +281,13 @@ int runAPR(const cmdLineOptions &options) {
239281
float aprImageSizeInMB = aprFile.current_file_size_MB();
240282
double originalImageSizeInMB = sizeof(ImgType) * static_cast<double>(apr.org_dims(0) * apr.org_dims(1) * apr.org_dims(2)) / 1'000'000.0;
241283

242-
std::cout << "Computational Ratio (Pixels/Particles): " << apr.computational_ratio() << std::endl;
243-
std::cout << "Original / APR image size: " << originalImageSizeInMB << " / " << aprImageSizeInMB <<" MB" << std::endl;
244-
std::cout << "Lossy Compression Ratio: " << originalImageSizeInMB/aprImageSizeInMB << std::endl;
245-
std::cout << std::endl;
284+
if (!options.skipOutputMessages) {
285+
std::cout << "Save filename: [" << outputFileName << "]" << std::endl;
286+
std::cout << "Computational Ratio (Pixels/Particles): " << apr.computational_ratio() << std::endl;
287+
std::cout << "Original / APR image size: " << originalImageSizeInMB << " / " << aprImageSizeInMB <<" MB" << std::endl;
288+
std::cout << "Lossy Compression Ratio: " << originalImageSizeInMB/aprImageSizeInMB << std::endl;
289+
std::cout << std::endl;
290+
}
246291
}
247292
}
248293
else {

src/algorithm/APRConverter.hpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -427,40 +427,39 @@ inline bool APRConverter<ImageType>::get_apr_cuda_multistreams(std::vector<APR*>
427427
// Reduce number of streams to number of images if there are fewer images than streams
428428
if (numOfImages < numOfStreams) numOfStreams = numOfImages;
429429

430-
// Use first image to initialize the APR - all other images should have the same dimensions
431-
auto input_image = input_images[0];
432-
433430
// Initialize APRs and memory for the pipeline
434431
for (auto apr : aAPRs) {
435-
if (!initPipelineAPR(*apr, *input_image)) return false;
432+
// Use first image to initialize the APR - all other images should have the same dimensions
433+
if (!initPipelineAPR(*apr, *input_images[0])) return false;
436434
}
437435

438-
// Create a temporary image for each stream
439-
std::vector<PixelData<ImageType>> tempImages;
436+
// Create a pinned buffer which will be linked to GpuProcessingTask handling each stream
437+
// These buffers are used to transmit images from CPU to GPU (first input image need to be copied there)
438+
std::vector<PixelData<ImageType>> pinnedBuffers;
440439
std::cout << "Allocating memory for " << numOfStreams << " streams." << std::endl;
441440
for (int i = 0; i < numOfStreams; ++i) {
442-
tempImages.emplace_back(PixelData<T>(*input_image, true /* copy */, true /* pinned memory */));
441+
pinnedBuffers.emplace_back(PixelData<T>(*input_images[i], false /* copy */, true /* pinned memory */));
443442
}
444443

445444
/////////////////////////////////
446445
/// Pipeline
447446
/////////////////////////////////
448447
APRTimer t(true);
449448

450-
// Create GpuProcessingTask for each stream
449+
// Create GpuProcessingTask for each stream and link it with pinnedBuffer
451450
std::vector<GpuProcessingTask<ImageType>> gpts;
452451
t.start_timer("Creating GPTS");
453452
std::vector<std::future<void>> gpts_futures; gpts_futures.resize(numOfStreams);
454453
for (int i = 0; i < numOfStreams; ++i) {
455-
gpts.emplace_back(GpuProcessingTask<ImageType>(tempImages[i], par, aAPRs[0]->level_max()));
454+
gpts.emplace_back(GpuProcessingTask<ImageType>(pinnedBuffers[i], par, aAPRs[0]->level_max()));
456455
}
457456
t.stop_timer();
458457

459-
460458
t.start_timer("GPU processing...");
461459
// Saturate all the streams with first images
462460
for (int i = 0; i < numOfStreams; ++i) {
463461
std::cout << "Processing image " << i << " on stream " << i << std::endl;
462+
pinnedBuffers[i].copyFromMesh(*input_images[i]);
464463
gpts_futures[i] = std::async(std::launch::async, &GpuProcessingTask<ImageType>::processOnGpu, &gpts[i]);
465464
}
466465

@@ -476,7 +475,7 @@ inline bool APRConverter<ImageType>::get_apr_cuda_multistreams(std::vector<APR*>
476475
// We have 'numOfImages - numOfStreams' left to process after saturating the streams with first images
477476
if (s < numOfImages - numOfStreams) {
478477
int imageToProcess = s + numOfStreams;
479-
tempImages[streamNum].copyFromMesh(*input_images[imageToProcess]);
478+
pinnedBuffers[streamNum].copyFromMesh(*input_images[imageToProcess]);
480479
std::cout << "Processing image " << imageToProcess << " on stream " << streamNum << std::endl;
481480
gpts_futures[streamNum] = std::async(std::launch::async, &GpuProcessingTask<ImageType>::processOnGpu, &gpts[streamNum]);
482481
}
@@ -489,8 +488,8 @@ inline bool APRConverter<ImageType>::get_apr_cuda_multistreams(std::vector<APR*>
489488
aAPRs[s]->apr_initialized = true;
490489
if (intensities[s] != nullptr) *intensities[s] = std::move(linearAccessGpu.parts);
491490
}
492-
493491
auto allT = t.stop_timer();
492+
494493
float tpi = allT / (numOfImages);
495494
std::cout << "Num of images processed: " << numOfImages << "\n";
496495
std::cout << "Time per image: " << tpi << " seconds\n";

src/algorithm/ComputeGradientCuda.cu

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,11 @@ template <typename U>
401401
template <typename ImgType>
402402
class GpuProcessingTask<U>::GpuProcessingTaskImpl {
403403

404+
405+
406+
int cudaDevID;
407+
cudaError_t cudaerr;
408+
404409
CudaStream cudaStream;
405410
const cudaStream_t iStream;
406411

@@ -465,6 +470,8 @@ class GpuProcessingTask<U>::GpuProcessingTaskImpl {
465470
public:
466471

467472
GpuProcessingTaskImpl(const PixelData<ImgType> &inputImage, const APRParameters &parameters, int maxLevel) :
473+
cudaDevID(0), // used for now only in testing new ideas, ideally we should discover CPU/GPU architecture and assing corrct GPU to correct CPU which involves also correct memory allocation and thread affinity....
474+
cudaerr(cudaSetDevice(cudaDevID)),
468475
iCpuImage(inputImage),
469476
iStream(cudaStream.get()),
470477
image (inputImage, iStream),
@@ -524,7 +531,7 @@ public:
524531
// Calculate number of blocks to saturate whole SMs
525532
// Multiply it by 8 to have more smaller blocks to have better load balancing in case GPU is busy with other tasks
526533
cudaDeviceProp deviceProp;
527-
cudaGetDeviceProperties(&deviceProp, 0);
534+
cudaGetDeviceProperties(&deviceProp, cudaDevID);
528535
const int smCount = deviceProp.multiProcessorCount;
529536
const int numOfThreadsPerSM = deviceProp.maxThreadsPerMultiProcessor;
530537
constexpr int numOfThreads = 512;
@@ -563,10 +570,12 @@ public:
563570
}
564571

565572
LinearAccessCudaStructs<ImgType> getDataFromGpu() {
573+
cudaSetDevice(cudaDevID);
566574
return std::move(lacs);
567575
}
568576

569577
void processOnGpu() {
578+
cudaSetDevice(cudaDevID);
570579
// Set it and copy first before copying the image
571580
// It improves *a lot* performance even though it is needed later in computeLinearStructureCuda()
572581
iAprInfo.total_number_particles = 0; // reset total_number_particles to 0

src/data_structures/Mesh/PixelData.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <iomanip>
2020
#include <algorithm>
2121
#include <numeric>
22+
#include <cstring>
2223

2324
#include "misc/APRTimer.hpp"
2425

@@ -435,8 +436,12 @@ public :
435436
PinnedMemoryUniquePtr<T> meshMemoryPinned;
436437
#endif
437438
ArrayWrapper<T> mesh;
438-
439-
uint64_t size() { return (uint64_t) x_num * y_num * z_num * sizeof(T); }
439+
440+
/**
441+
* @return Size in bytes of PixelData (number of elements * sizeof of element)
442+
*/
443+
uint64_t size() const { return (uint64_t) x_num * y_num * z_num * sizeof(T); }
444+
440445
/**
441446
* Constructor - initialize mesh with size of 0,0,0
442447
*/
@@ -598,7 +603,16 @@ public :
598603
}
599604

600605
/**
601-
* Copies data from aInputMesh utilizing parallel copy, requires prior initialization
606+
* Copies data from aInputMesh - requires prior initialization
607+
* of 'this' object (size and number of elements)
608+
* @param aInputMesh input mesh with data
609+
*/
610+
void copyFromMesh(const PixelData<T> &aInputMesh) {
611+
std::memcpy(mesh.begin(), aInputMesh.mesh.begin(), aInputMesh.size());
612+
}
613+
614+
/**
615+
* Copies data from aInputMesh which is of different type than 'this' utilizing parallel copy, requires prior initialization
602616
* of 'this' object (size and number of elements)
603617
* @tparam U type of data
604618
* @param aInputMesh input mesh with data

test/MeshDataTest.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,21 @@ namespace {
423423
ASSERT_EQ(m(yLen, xLen, zLen), valueForIndex(yLen-1, xLen-1, zLen-1));
424424
}
425425

426+
TEST_F(MeshDataTest, CopyFromMeshTest) {
427+
PixelData<decltype(m)::value_type> mNew(yLen, xLen, zLen);
428+
429+
mNew.copyFromMesh(m);
430+
431+
// Compare if same
432+
for (int y = 0; y < yLen; ++y) {
433+
for (int x = 0; x < xLen; ++x) {
434+
for (int z = 0; z < zLen; ++z) {
435+
// ASSERT_EQ(m(y, x, z), mNew(y, x, z));
436+
}
437+
}
438+
}
439+
}
440+
426441
TEST_P(MeshDataParameterTest, BlockCopyDataTest) {
427442
PixelData<unsigned short> mNew(yLen, xLen, zLen);
428443

0 commit comments

Comments
 (0)