33import subprocess
44import urllib .request
55from concurrent .futures import ThreadPoolExecutor , wait
6+ from dataclasses import dataclass
67from itertools import product
78from pathlib import Path
89
@@ -479,13 +480,51 @@ def get_metadata(video_file_path: str) -> VideoStreamMetadata:
479480 return VideoDecoder (video_file_path ).metadata
480481
481482
483+ @dataclass
484+ class BatchParameters :
485+ num_threads : int
486+ batch_size : int
487+
488+
489+ def run_batch_using_threads (
490+ function ,
491+ * args ,
492+ batch_parameters : BatchParameters = BatchParameters (num_threads = 8 , batch_size = 40 ),
493+ ):
494+ executor = ThreadPoolExecutor (max_workers = batch_parameters .num_threads )
495+ futures = []
496+ for _ in range (batch_parameters .batch_size ):
497+ futures .append (executor .submit (function , * args ))
498+ for f in futures :
499+ assert f .result ()
500+ executor .shutdown (wait = True )
501+
502+
503+ def convert_result_to_df_item (
504+ result , decoder_name , video_file_path , num_samples , decode_pattern
505+ ):
506+ df_item = {}
507+ df_item ["decoder" ] = decoder_name
508+ df_item ["video" ] = str (video_file_path )
509+ df_item ["description" ] = result .description
510+ df_item ["frame_count" ] = num_samples
511+ df_item ["median" ] = result .median
512+ df_item ["iqr" ] = result .iqr
513+ df_item ["type" ] = decode_pattern
514+ df_item ["fps_median" ] = num_samples / result .median
515+ df_item ["fps_p75" ] = num_samples / result ._p75
516+ df_item ["fps_p25" ] = num_samples / result ._p25
517+ return df_item
518+
519+
482520def run_benchmarks (
483521 decoder_dict : dict [str , AbstractDecoder ],
484522 video_files_paths : list [Path ],
485523 num_samples : int ,
486524 num_sequential_frames_from_start : list [int ],
487525 min_runtime_seconds : float ,
488526 benchmark_video_creation : bool ,
527+ batch_parameters : BatchParameters = None ,
489528) -> list [dict [str , str | float | int ]]:
490529 # Ensure that we have the same seed across benchmark runs.
491530 torch .manual_seed (0 )
@@ -532,18 +571,44 @@ def run_benchmarks(
532571 results .append (
533572 seeked_result .blocked_autorange (min_run_time = min_runtime_seconds )
534573 )
535- df_item = {}
536- df_item ["decoder" ] = decoder_name
537- df_item ["video" ] = str (video_file_path )
538- df_item ["description" ] = results [- 1 ].description
539- df_item ["frame_count" ] = num_samples
540- df_item ["median" ] = results [- 1 ].median
541- df_item ["iqr" ] = results [- 1 ].iqr
542- df_item ["type" ] = f"{ kind } :seek()+next()"
543- df_item ["fps_median" ] = num_samples / results [- 1 ].median
544- df_item ["fps_p75" ] = num_samples / results [- 1 ]._p75
545- df_item ["fps_p25" ] = num_samples / results [- 1 ]._p25
546- df_data .append (df_item )
574+ df_data .append (
575+ convert_result_to_df_item (
576+ results [- 1 ],
577+ decoder_name ,
578+ video_file_path ,
579+ num_samples ,
580+ f"{ kind } seek()+next()" ,
581+ )
582+ )
583+
584+ if batch_parameters :
585+ seeked_result = benchmark .Timer (
586+ stmt = "run_batch_using_threads(decoder.get_frames_from_video, video_file, pts_list, batch_parameters=batch_parameters)" ,
587+ globals = {
588+ "video_file" : str (video_file_path ),
589+ "pts_list" : pts_list ,
590+ "decoder" : decoder ,
591+ "run_batch_using_threads" : run_batch_using_threads ,
592+ "batch_parameters" : batch_parameters ,
593+ },
594+ label = f"video={ video_file_path } { metadata_label } " ,
595+ sub_label = decoder_name ,
596+ description = f"batch { kind } { num_samples } seek()+next()" ,
597+ )
598+ results .append (
599+ seeked_result .blocked_autorange (
600+ min_run_time = min_runtime_seconds
601+ )
602+ )
603+ df_data .append (
604+ convert_result_to_df_item (
605+ results [- 1 ],
606+ decoder_name ,
607+ video_file_path ,
608+ num_samples * batch_parameters .batch_size ,
609+ f"batch { kind } seek()+next()" ,
610+ )
611+ )
547612
548613 for num_consecutive_nexts in num_sequential_frames_from_start :
549614 consecutive_frames_result = benchmark .Timer (
@@ -562,18 +627,44 @@ def run_benchmarks(
562627 min_run_time = min_runtime_seconds
563628 )
564629 )
565- df_item = {}
566- df_item ["decoder" ] = decoder_name
567- df_item ["video" ] = str (video_file_path )
568- df_item ["description" ] = results [- 1 ].description
569- df_item ["frame_count" ] = num_consecutive_nexts
570- df_item ["median" ] = results [- 1 ].median
571- df_item ["iqr" ] = results [- 1 ].iqr
572- df_item ["type" ] = "next()"
573- df_item ["fps_median" ] = num_consecutive_nexts / results [- 1 ].median
574- df_item ["fps_p75" ] = num_consecutive_nexts / results [- 1 ]._p75
575- df_item ["fps_p25" ] = num_consecutive_nexts / results [- 1 ]._p25
576- df_data .append (df_item )
630+ df_data .append (
631+ convert_result_to_df_item (
632+ results [- 1 ],
633+ decoder_name ,
634+ video_file_path ,
635+ num_consecutive_nexts ,
636+ f"{ num_consecutive_nexts } next()" ,
637+ )
638+ )
639+
640+ if batch_parameters :
641+ consecutive_frames_result = benchmark .Timer (
642+ stmt = "run_batch_using_threads(decoder.get_consecutive_frames_from_video, video_file, consecutive_frames_to_extract, batch_parameters=batch_parameters)" ,
643+ globals = {
644+ "video_file" : str (video_file_path ),
645+ "consecutive_frames_to_extract" : num_consecutive_nexts ,
646+ "decoder" : decoder ,
647+ "run_batch_using_threads" : run_batch_using_threads ,
648+ "batch_parameters" : batch_parameters ,
649+ },
650+ label = f"video={ video_file_path } { metadata_label } " ,
651+ sub_label = decoder_name ,
652+ description = f"batch { num_consecutive_nexts } next()" ,
653+ )
654+ results .append (
655+ consecutive_frames_result .blocked_autorange (
656+ min_run_time = min_runtime_seconds
657+ )
658+ )
659+ df_data .append (
660+ convert_result_to_df_item (
661+ results [- 1 ],
662+ decoder_name ,
663+ video_file_path ,
664+ num_consecutive_nexts * batch_parameters .batch_size ,
665+ f"batch { num_consecutive_nexts } next()" ,
666+ )
667+ )
577668
578669 first_video_file_path = video_files_paths [0 ]
579670 if benchmark_video_creation :
0 commit comments