@@ -64,7 +64,7 @@ def time_call(function, repeats: int, warmup: int) -> list[float]:
6464 return samples
6565
6666
67- def build_cases (shape : tuple [int , ...], include_geodesic : bool ):
67+ def build_cases (shape : tuple [int , ...], include_geodesic : bool , number_of_threads : int ):
6868 mask = make_mask (shape )
6969 source = np .full (len (shape ), 2 , dtype = np .int64 )
7070 target = np .asarray (shape , dtype = np .int64 ) - 3
@@ -79,25 +79,48 @@ def build_cases(shape: tuple[int, ...], include_geodesic: bool):
7979 (
8080 "dijkstra/physical-field" ,
8181 lambda : bic .distance .dijkstra_distance_field (
82- mask , source , spacing = spacing
82+ mask , source , spacing = spacing , number_of_threads = number_of_threads
8383 ),
8484 ),
8585 (
8686 "dijkstra/field+parents" ,
8787 lambda : bic .distance .dijkstra_distance_field (
88- mask , source , spacing = spacing , return_predecessors = True
88+ mask ,
89+ source ,
90+ spacing = spacing ,
91+ return_predecessors = True ,
92+ number_of_threads = number_of_threads ,
8993 ),
9094 ),
9195 (
9296 "dijkstra/node-field" ,
9397 lambda : bic .distance .dijkstra_distance_field (
94- mask , source , costs = costs , cost_mode = "node"
98+ mask ,
99+ source ,
100+ costs = costs ,
101+ cost_mode = "node" ,
102+ number_of_threads = number_of_threads ,
103+ ),
104+ ),
105+ (
106+ "dijkstra/node-times-physical-field" ,
107+ lambda : bic .distance .dijkstra_distance_field (
108+ mask ,
109+ source ,
110+ costs = costs ,
111+ cost_mode = "node_times_physical" ,
112+ spacing = spacing ,
113+ number_of_threads = number_of_threads ,
95114 ),
96115 ),
97116 (
98117 "dijkstra/early-path" ,
99118 lambda : bic .distance .dijkstra_path (
100- mask , source , target , spacing = spacing
119+ mask ,
120+ source ,
121+ target ,
122+ spacing = spacing ,
123+ number_of_threads = number_of_threads ,
101124 ),
102125 ),
103126 ]
@@ -106,7 +129,10 @@ def build_cases(shape: tuple[int, ...], include_geodesic: bool):
106129 (
107130 "geodesic/FMM-field" ,
108131 lambda : bic .distance .geodesic_distance_field (
109- mask , source , sampling = spacing , number_of_threads = 1
132+ mask ,
133+ source ,
134+ sampling = spacing ,
135+ number_of_threads = number_of_threads ,
110136 ),
111137 )
112138 )
@@ -121,6 +147,10 @@ def main() -> int:
121147 parser .add_argument ("--repeats" , type = int , default = 7 )
122148 parser .add_argument ("--warmup" , type = int , default = 1 )
123149 parser .add_argument ("--include-geodesic" , action = "store_true" )
150+ parser .add_argument (
151+ "--threads" , type = int , nargs = "+" , default = [1 ],
152+ help = "thread counts to benchmark (0 uses hardware concurrency)" ,
153+ )
124154 parser .add_argument ("--json" , default = "" , help = "optional JSON result path" )
125155 args = parser .parse_args ()
126156 if args .repeats < 1 or args .warmup < 0 :
@@ -134,29 +164,34 @@ def main() -> int:
134164 shapes = ((1024 , 1024 ), (64 , 160 , 160 ))
135165
136166 rows = []
137- header = f"{ 'case' :>29 } { 'shape' :>16} { 'median ms' :>11} { 'min ms' :>10} { 'ns/fg voxel' :>13} "
167+ header = f"{ 'case' :>36 } { 'threads' :>7 } { 'shape' :>16} { 'median ms' :>11} { 'min ms' :>10} { 'ns/fg voxel' :>13} "
138168 print (header )
139169 print ("-" * len (header ))
140170 for shape in shapes :
141- mask , cases = build_cases (shape , args .include_geodesic )
142- foreground = int (np .count_nonzero (mask ))
143- for name , function in cases :
144- samples = time_call (function , args .repeats , args .warmup )
145- median_s = median (samples )
146- row = {
147- "case" : name ,
148- "shape" : list (shape ),
149- "foreground_voxels" : foreground ,
150- "samples_s" : samples ,
151- "median_s" : median_s ,
152- "min_s" : min (samples ),
153- "ns_per_foreground_voxel" : median_s * 1e9 / foreground ,
154- }
155- rows .append (row )
156- print (
157- f"{ name :>29} { str (shape ):>16} { median_s * 1e3 :11.2f} "
158- f"{ min (samples ) * 1e3 :10.2f} { row ['ns_per_foreground_voxel' ]:13.1f} "
171+ for number_of_threads in args .threads :
172+ mask , cases = build_cases (
173+ shape , args .include_geodesic , number_of_threads
159174 )
175+ foreground = int (np .count_nonzero (mask ))
176+ for name , function in cases :
177+ samples = time_call (function , args .repeats , args .warmup )
178+ median_s = median (samples )
179+ row = {
180+ "case" : name ,
181+ "number_of_threads" : number_of_threads ,
182+ "shape" : list (shape ),
183+ "foreground_voxels" : foreground ,
184+ "samples_s" : samples ,
185+ "median_s" : median_s ,
186+ "min_s" : min (samples ),
187+ "ns_per_foreground_voxel" : median_s * 1e9 / foreground ,
188+ }
189+ rows .append (row )
190+ print (
191+ f"{ name :>36} { number_of_threads :7d} { str (shape ):>16} "
192+ f"{ median_s * 1e3 :11.2f} { min (samples ) * 1e3 :10.2f} "
193+ f"{ row ['ns_per_foreground_voxel' ]:13.1f} "
194+ )
160195
161196 if args .json :
162197 with open (args .json , "w" , encoding = "utf-8" ) as file :
0 commit comments