11/*
22 * Barcode Benchmark: Dynamsoft Capture Vision vs zxing-cpp
33 *
4- * Usage: benchmark <image_or_folder> [iterations]
4+ * Usage: benchmark [--threads N] <image_or_folder> [iterations]
55 * image_or_folder : path to a single image file or a directory of images
66 * iterations : number of decoding passes per image (default: 10)
7+ * --threads N : run multi-threaded throughput benchmark with N threads
78 */
89
910#include < iostream>
1617#include < cstdio>
1718#include < cstring>
1819#include < numeric>
20+ #include < thread>
21+ #include < mutex>
22+ #include < atomic>
1923
2024// Prevent Windows macros from conflicting with std::min/std::max
2125#ifdef _WIN32
@@ -194,6 +198,143 @@ static BenchResult benchZXing(const string& filePath, int iterations)
194198 return res;
195199}
196200
201+ // ---------------------------------------------------------------------------
202+ // Multi-threading types
203+ // ---------------------------------------------------------------------------
204+ struct MultiThreadResult
205+ {
206+ string libraryName;
207+ int totalBarcodes = 0 ;
208+ double wallTimeSec = 0.0 ;
209+ double throughput = 0.0 ;
210+ int totalDecodes = 0 ;
211+ };
212+
213+ // ---------------------------------------------------------------------------
214+ // Dynamsoft multi-thread benchmark
215+ // ---------------------------------------------------------------------------
216+ static MultiThreadResult benchDynamsoftMulti (CCaptureVisionRouter* cvr,
217+ const vector<string>& images,
218+ int iterations, int numThreads)
219+ {
220+ (void )cvr; // unused in this implementation (each thread creates its own router)
221+ MultiThreadResult res;
222+ res.libraryName = " Dynamsoft" ;
223+
224+ auto t0 = high_resolution_clock::now ();
225+ atomic<int > totalBarcodes{0 };
226+ vector<thread> workers;
227+
228+ for (int t = 0 ; t < numThreads; ++t)
229+ {
230+ workers.emplace_back ([&, t]() {
231+ CCaptureVisionRouter* localCvr = new CCaptureVisionRouter;
232+ char err[512 ] = {0 };
233+ localCvr->InitSettingsFromFile (" Templates/DBR-PresetTemplates.json" , err, 512 );
234+
235+ int localBc = 0 ;
236+ for (size_t i = t; i < images.size (); i += numThreads)
237+ {
238+ for (int it = 0 ; it < iterations; ++it)
239+ {
240+ CCapturedResultArray* arr = localCvr->CaptureMultiPages (
241+ images[i].c_str (), CPresetTemplate::PT_READ_BARCODES );
242+
243+ int count = 0 ;
244+ int pageCount = arr->GetResultsCount ();
245+ for (int p = 0 ; p < pageCount; ++p)
246+ {
247+ const CCapturedResult* r = arr->GetResult (p);
248+ if (r->GetErrorCode () != 0 ) continue ;
249+ CDecodedBarcodesResult* br = r->GetDecodedBarcodesResult ();
250+ if (br && br->GetErrorCode () == 0 )
251+ count += br->GetItemsCount ();
252+ if (br) br->Release ();
253+ }
254+ arr->Release ();
255+ if (it == 0 ) localBc = count;
256+ }
257+ totalBarcodes.fetch_add (localBc);
258+ }
259+ delete localCvr;
260+ });
261+ }
262+ for (auto & w : workers) w.join ();
263+
264+ auto t1 = high_resolution_clock::now ();
265+ res.wallTimeSec = duration_cast<microseconds>(t1 - t0).count () / 1e6 ;
266+ res.totalBarcodes = totalBarcodes.load ();
267+ res.totalDecodes = static_cast <int >(images.size ()) * iterations;
268+ res.throughput = res.totalDecodes / res.wallTimeSec ;
269+ return res;
270+ }
271+
272+ // ---------------------------------------------------------------------------
273+ // zxing-cpp multi-thread benchmark
274+ // ---------------------------------------------------------------------------
275+ static MultiThreadResult benchZXingMulti (const vector<string>& images,
276+ int iterations, int numThreads)
277+ {
278+ MultiThreadResult res;
279+ res.libraryName = " zxing-cpp" ;
280+
281+ // Filter out PDFs (stb_image cannot load them)
282+ vector<string> validImages;
283+ for (const auto & img : images) {
284+ string ext = filesystem::path (img).extension ().string ();
285+ transform (ext.begin (), ext.end (), ext.begin (), ::tolower);
286+ if (ext != " .pdf" )
287+ validImages.push_back (img);
288+ }
289+ if (validImages.empty ()) return res;
290+
291+ ZXing::ReaderOptions options;
292+ options.tryHarder (true );
293+ static const array<ZXing::ImageFormat, 5 > fmtMap = {
294+ ZXing::ImageFormat::None, ZXing::ImageFormat::Lum,
295+ ZXing::ImageFormat::LumA, ZXing::ImageFormat::RGB ,
296+ ZXing::ImageFormat::RGBA
297+ };
298+
299+ auto t0 = high_resolution_clock::now ();
300+ atomic<int > totalBarcodes{0 };
301+ vector<thread> workers;
302+
303+ for (int t = 0 ; t < numThreads; ++t)
304+ {
305+ workers.emplace_back ([&, t]() {
306+ for (size_t i = t; i < validImages.size (); i += numThreads)
307+ {
308+ int width = 0 , height = 0 , channels = 0 ;
309+ unsigned char * buf = stbi_load (validImages[i].c_str (), &width, &height, &channels, 0 );
310+ if (!buf) continue ;
311+
312+ ZXing::ImageFormat fmt = (channels >= 1 && channels <= 4 )
313+ ? fmtMap[channels]
314+ : ZXing::ImageFormat::None;
315+ ZXing::ImageView iv (buf, width, height, fmt);
316+
317+ int localBc = 0 ;
318+ for (int it = 0 ; it < iterations; ++it)
319+ {
320+ auto bc = ZXing::ReadBarcodes (iv, options);
321+ if (it == 0 ) localBc = static_cast <int >(bc.size ());
322+ }
323+ totalBarcodes.fetch_add (localBc);
324+ stbi_image_free (buf);
325+ }
326+ });
327+ }
328+ for (auto & w : workers) w.join ();
329+
330+ auto t1 = high_resolution_clock::now ();
331+ res.wallTimeSec = duration_cast<microseconds>(t1 - t0).count () / 1e6 ;
332+ res.totalBarcodes = totalBarcodes.load ();
333+ res.totalDecodes = static_cast <int >(validImages.size ()) * iterations;
334+ res.throughput = res.totalDecodes / res.wallTimeSec ;
335+ return res;
336+ }
337+
197338// ---------------------------------------------------------------------------
198339// Print helpers
199340// ---------------------------------------------------------------------------
@@ -227,6 +368,33 @@ static void printRow(const BenchResult& r)
227368 << " |\n " ;
228369}
229370
371+ // ---------------------------------------------------------------------------
372+ // Multi-thread print helpers
373+ // ---------------------------------------------------------------------------
374+ static void printMultiHeader (int numThreads)
375+ {
376+ cout << " --- Multi-Threaded Benchmark (" << numThreads << " threads) ---\n " ;
377+ cout << " |" << left << setw (12 ) << " Library"
378+ << " |" << right << setw (14 ) << " Wall Time (s)"
379+ << " |" << right << setw (14 ) << " Decodes/s"
380+ << " |" << right << setw (14 ) << " Total Decodes"
381+ << " |" << right << setw (14 ) << " Barcodes"
382+ << " |\n " ;
383+ cout << " |" << string (12 , ' -' ) << " |" << string (14 , ' -' )
384+ << " |" << string (14 , ' -' ) << " |" << string (14 , ' -' )
385+ << " |" << string (14 , ' -' ) << " |\n " ;
386+ }
387+
388+ static void printMultiRow (const MultiThreadResult& r)
389+ {
390+ cout << " |" << left << setw (12 ) << (" " + r.libraryName )
391+ << " |" << right << setw (14 ) << fixed << setprecision (3 ) << r.wallTimeSec
392+ << " |" << right << setw (14 ) << fixed << setprecision (1 ) << r.throughput
393+ << " |" << right << setw (14 ) << r.totalDecodes
394+ << " |" << right << setw (14 ) << r.totalBarcodes
395+ << " |\n " ;
396+ }
397+
230398// ---------------------------------------------------------------------------
231399// main
232400// ---------------------------------------------------------------------------
@@ -239,17 +407,40 @@ int main(int argc, char* argv[])
239407 << " vs zxing-cpp v" << ZXING_VERSION << " \n " ;
240408 cout << " =============================================================\n\n " ;
241409
242- if (argc < 2 ) {
410+ // --- Parse arguments ---
411+ int numThreads = 0 ;
412+ int iterations = 10 ;
413+ string inputPath;
414+
415+ vector<string> posArgs;
416+ for (int i = 1 ; i < argc; ++i) {
417+ if (strcmp (argv[i], " --threads" ) == 0 ) {
418+ if (i + 1 < argc) {
419+ numThreads = atoi (argv[++i]);
420+ if (numThreads < 1 ) numThreads = 1 ;
421+ } else {
422+ cerr << " Error: --threads requires a number argument\n " ;
423+ return 1 ;
424+ }
425+ } else {
426+ posArgs.push_back (argv[i]);
427+ }
428+ }
429+
430+ if (posArgs.empty ()) {
243431 cout << " Usage: " << (argc > 0 ? argv[0 ] : " benchmark" )
244- << " <image_or_folder> [iterations]\n "
432+ << " [--threads N] <image_or_folder> [iterations]\n "
245433 << " image_or_folder : path to an image file or a directory\n "
246- << " iterations : decoding passes per image (default: 10)\n " ;
434+ << " iterations : decoding passes per image (default: 10)\n "
435+ << " --threads N : run multi-threaded throughput benchmark with N threads\n " ;
247436 return 0 ;
248437 }
249438
250- string inputPath = argv[1 ];
251- int iterations = (argc >= 3 ) ? atoi (argv[2 ]) : 10 ;
252- if (iterations < 1 ) iterations = 1 ;
439+ inputPath = posArgs[0 ];
440+ if (posArgs.size () >= 2 ) {
441+ iterations = atoi (posArgs[1 ].c_str ());
442+ if (iterations < 1 ) iterations = 1 ;
443+ }
253444
254445 if (!filesystem::exists (inputPath)) {
255446 cerr << " Error: path does not exist: " << inputPath << " \n " ;
@@ -262,10 +453,13 @@ int main(int argc, char* argv[])
262453 return 1 ;
263454 }
264455
265- cout << " Found " << images.size () << " image(s). Iterations per image: "
266- << iterations << " \n\n " ;
456+ cout << " Found " << images.size () << " image(s). "
457+ << " Iterations per image: " << iterations;
458+ if (numThreads > 0 )
459+ cout << " , Threads: " << numThreads;
460+ cout << " \n\n " ;
267461
268- // Initialize Dynamsoft
462+ // --- Initialize Dynamsoft ---
269463 char szErrorMsg[256 ] = {0 };
270464 int iRet = CLicenseManager::InitLicense (
271465 " DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==" ,
@@ -284,7 +478,9 @@ int main(int argc, char* argv[])
284478 return 1 ;
285479 }
286480
287- // Accumulators for overall summary
481+ // ================================================================
482+ // Single-thread benchmark (per-image detail)
483+ // ================================================================
288484 double totalDynTime = 0.0 ;
289485 int totalDynBc = 0 ;
290486 double totalZxTime = 0.0 ;
@@ -326,9 +522,9 @@ int main(int argc, char* argv[])
326522 cout << " \n " ;
327523 }
328524
329- // Summary
525+ // Single-thread summary
330526 cout << " =============================================================\n " ;
331- cout << " Summary (" << processedCount << " image(s), "
527+ cout << " Single-Thread Summary (" << processedCount << " image(s), "
332528 << iterations << " iterations each)\n " ;
333529 cout << " =============================================================\n " ;
334530 cout << " |" << left << setw (12 ) << " Library"
@@ -347,6 +543,36 @@ int main(int argc, char* argv[])
347543 << " |" << right << setw (14 ) << totalZxBc << " |\n " ;
348544 cout << " \n " ;
349545
546+ // ================================================================
547+ // Multi-thread benchmark (optional)
548+ // ================================================================
549+ if (numThreads > 0 )
550+ {
551+ cout << " =============================================================\n " ;
552+ printMultiHeader (numThreads);
553+
554+ MultiThreadResult dynMulti = benchDynamsoftMulti (cvr, images, iterations, numThreads);
555+ printMultiRow (dynMulti);
556+
557+ MultiThreadResult zxMulti = benchZXingMulti (images, iterations, numThreads);
558+ printMultiRow (zxMulti);
559+
560+ // Speedup vs single-thread
561+ if (totalDynTime > 0 && dynMulti.wallTimeSec > 0 ) {
562+ double dynSeqTotal = totalDynTime * iterations / 1000.0 ;
563+ double dynSpeedup = dynSeqTotal / dynMulti.wallTimeSec ;
564+ double zxSeqTime = totalZxTime / 1000.0 ;
565+ if (zxSeqTime > 0 && zxMulti.wallTimeSec > 0 ) {
566+ double zxSeqTotal = zxSeqTime * iterations;
567+ double zxSpeedup = zxSeqTotal / zxMulti.wallTimeSec ;
568+ cout << fixed << setprecision (2 );
569+ cout << " Speedup: Dynamsoft " << dynSpeedup << " x, zxing-cpp "
570+ << zxSpeedup << " x" << std::endl;
571+ }
572+ }
573+ cout << " \n " ;
574+ }
575+
350576 delete cvr;
351577 return 0 ;
352578}
0 commit comments