@@ -138,21 +138,35 @@ def benchmark_frame_view( # noqa: C901
138138
139139 is_newton = api == "isaaclab-newton-site"
140140
141+ # Synchronize around timed regions using Warp directly (rather than torch),
142+ # since all backend kernels here are Warp launches and ``wp.synchronize()``
143+ # covers CPU/CUDA Warp devices consistently. We only need it for GPU runs,
144+ # where kernel launches are asynchronous; guard on device to avoid paying
145+ # it needlessly on CPU.
146+ _needs_sync = str (device ).startswith ("cuda" )
147+
148+ def _sync () -> None :
149+ if _needs_sync :
150+ wp .synchronize ()
151+
141152 def to_torch (a ):
142- return wp .to_torch (a ) if isinstance (a , wp .array ) else a
153+ if isinstance (a , wp .array ):
154+ return wp .to_torch (a )
155+ if hasattr (a , "torch" ):
156+ return a .torch
157+ return a
143158
144159 try :
145160 # -- Warmup --------------------------------------------------------
146161 xform_view .get_world_poses ()
162+ xform_view .get_world_scales ()
147163
148164 # -- get_world_poses -----------------------------------------------
149- if is_newton :
150- torch .cuda .synchronize ()
165+ _sync ()
151166 start_time = time .perf_counter ()
152167 for _ in range (num_iterations ):
153168 positions , orientations = xform_view .get_world_poses ()
154- if is_newton :
155- torch .cuda .synchronize ()
169+ _sync ()
156170 timing_results ["get_world_poses" ] = (time .perf_counter () - start_time ) / num_iterations
157171
158172 positions_t = to_torch (positions )
@@ -161,34 +175,36 @@ def to_torch(a):
161175 computed_results ["initial_world_orientations" ] = orientations_t .clone ()
162176
163177 # -- set_world_poses -----------------------------------------------
178+ # ``.warp`` unwraps the ProxyArray returned by ``get_*_poses`` /
179+ # ``get_*_scales`` to the underlying ``wp.array`` that ``wp.clone``
180+ # requires. ProxyArray was introduced in PR #5304 ("ProxyArray and
181+ # Asset/Sensor level property caching") which changed the FrameView
182+ # getter return type. Applies to every ``wp.clone`` call below.
164183 if is_newton :
165- new_positions = wp .clone (positions )
184+ new_positions = wp .clone (positions . warp )
166185 wp .to_torch (new_positions )[:, 2 ] += 0.1
167186 else :
168187 new_positions = positions_t .clone ()
169188 new_positions [:, 2 ] += 0.1
170189
171- if is_newton :
172- torch .cuda .synchronize ()
190+ _sync ()
173191 start_time = time .perf_counter ()
174192 for _ in range (num_iterations ):
175- xform_view .set_world_poses ( new_positions , orientations )
176- if is_newton :
177- torch . cuda . synchronize ()
193+ with xform_view .xform_world_space_writer () as w :
194+ w . set_poses ( new_positions , orientations )
195+ _sync ()
178196 timing_results ["set_world_poses" ] = (time .perf_counter () - start_time ) / num_iterations
179197
180198 pa , oa = xform_view .get_world_poses ()
181199 computed_results ["world_positions_after_set" ] = to_torch (pa ).clone ()
182200 computed_results ["world_orientations_after_set" ] = to_torch (oa ).clone ()
183201
184202 # -- get_local_poses -----------------------------------------------
185- if is_newton :
186- torch .cuda .synchronize ()
203+ _sync ()
187204 start_time = time .perf_counter ()
188205 for _ in range (num_iterations ):
189206 translations , orientations_local = xform_view .get_local_poses ()
190- if is_newton :
191- torch .cuda .synchronize ()
207+ _sync ()
192208 timing_results ["get_local_poses" ] = (time .perf_counter () - start_time ) / num_iterations
193209
194210 translations_t = to_torch (translations )
@@ -198,45 +214,99 @@ def to_torch(a):
198214
199215 # -- set_local_poses -----------------------------------------------
200216 if is_newton :
201- new_translations = wp .clone (translations )
217+ new_translations = wp .clone (translations . warp )
202218 wp .to_torch (new_translations )[:, 2 ] += 0.1
203219 else :
204220 new_translations = translations_t .clone ()
205221 new_translations [:, 2 ] += 0.1
206222
207- if is_newton :
208- torch .cuda .synchronize ()
223+ _sync ()
209224 start_time = time .perf_counter ()
210225 for _ in range (num_iterations ):
211- xform_view .set_local_poses ( new_translations , orientations_local )
212- if is_newton :
213- torch . cuda . synchronize ()
226+ with xform_view .xform_local_space_writer () as w :
227+ w . set_poses ( new_translations , orientations_local )
228+ _sync ()
214229 timing_results ["set_local_poses" ] = (time .perf_counter () - start_time ) / num_iterations
215230
216231 ta , ola = xform_view .get_local_poses ()
217232 computed_results ["local_translations_after_set" ] = to_torch (ta ).clone ()
218233 computed_results ["local_orientations_after_set" ] = to_torch (ola ).clone ()
219234
220- # -- get_both (world + local) --------------------------------------
235+ # -- get_world_scales ----------------------------------------------
236+ _sync ()
237+ start_time = time .perf_counter ()
238+ for _ in range (num_iterations ):
239+ world_scales = xform_view .get_world_scales ()
240+ _sync ()
241+ timing_results ["get_world_scales" ] = (time .perf_counter () - start_time ) / num_iterations
242+
243+ world_scales_t = to_torch (world_scales )
244+ computed_results ["initial_world_scales" ] = world_scales_t .clone ()
245+
246+ # -- set_world_scales ----------------------------------------------
247+ if is_newton :
248+ new_world_scales = wp .clone (world_scales .warp )
249+ wp .to_torch (new_world_scales )[:] = 1.1
250+ else :
251+ new_world_scales = world_scales_t .clone ()
252+ new_world_scales [:] = 1.1
253+
254+ _sync ()
255+ start_time = time .perf_counter ()
256+ for _ in range (num_iterations ):
257+ with xform_view .xform_world_space_writer () as w :
258+ w .set_scales (new_world_scales )
259+ _sync ()
260+ timing_results ["set_world_scales" ] = (time .perf_counter () - start_time ) / num_iterations
261+
262+ computed_results ["world_scales_after_set" ] = to_torch (xform_view .get_world_scales ()).clone ()
263+
264+ # -- get_local_scales ----------------------------------------------
265+ _sync ()
266+ start_time = time .perf_counter ()
267+ for _ in range (num_iterations ):
268+ local_scales = xform_view .get_local_scales ()
269+ _sync ()
270+ timing_results ["get_local_scales" ] = (time .perf_counter () - start_time ) / num_iterations
271+
272+ local_scales_t = to_torch (local_scales )
273+ computed_results ["initial_local_scales" ] = local_scales_t .clone ()
274+
275+ # -- set_local_scales ----------------------------------------------
221276 if is_newton :
222- torch .cuda .synchronize ()
277+ new_local_scales = wp .clone (local_scales .warp )
278+ wp .to_torch (new_local_scales )[:] = 0.9
279+ else :
280+ new_local_scales = local_scales_t .clone ()
281+ new_local_scales [:] = 0.9
282+
283+ _sync ()
284+ start_time = time .perf_counter ()
285+ for _ in range (num_iterations ):
286+ with xform_view .xform_local_space_writer () as w :
287+ w .set_scales (new_local_scales )
288+ _sync ()
289+ timing_results ["set_local_scales" ] = (time .perf_counter () - start_time ) / num_iterations
290+
291+ computed_results ["local_scales_after_set" ] = to_torch (xform_view .get_local_scales ()).clone ()
292+
293+ # -- get_both (world + local) --------------------------------------
294+ _sync ()
223295 start_time = time .perf_counter ()
224296 for _ in range (num_iterations ):
225297 xform_view .get_world_poses ()
226298 xform_view .get_local_poses ()
227- if is_newton :
228- torch .cuda .synchronize ()
299+ _sync ()
229300 timing_results ["get_both" ] = (time .perf_counter () - start_time ) / num_iterations
230301
231302 # -- interleaved set -> get ----------------------------------------
232- if is_newton :
233- torch .cuda .synchronize ()
303+ _sync ()
234304 start_time = time .perf_counter ()
235305 for _ in range (num_iterations ):
236- xform_view .set_world_poses (new_positions , orientations )
306+ with xform_view .xform_world_space_writer () as w :
307+ w .set_poses (new_positions , orientations )
237308 xform_view .get_world_poses ()
238- if is_newton :
239- torch .cuda .synchronize ()
309+ _sync ()
240310 timing_results ["interleaved_world_set_get" ] = (time .perf_counter () - start_time ) / num_iterations
241311
242312 finally :
@@ -267,15 +337,26 @@ def print_results(results_dict: dict[str, dict[str, float]], num_prims: int, num
267337 print (header )
268338 print ("-" * 120 )
269339
270- operations = [
271- ("Initialization" , "init" ),
340+ # ``init`` is the one-time view-construction cost. We display it in the
341+ # per-operation table but EXCLUDE it from the steady-state totals and the
342+ # overall speedup -- otherwise a backend whose construction is dominated
343+ # by stage population (e.g. Newton, where the first call materializes the
344+ # site cache) shows a misleading "0.00x" overall and crushes the rest of
345+ # the table. The overall row is intended to compare per-iteration cost.
346+ init_op = ("Initialization (one-time)" , "init" )
347+ per_iter_operations = [
272348 ("Get World Poses" , "get_world_poses" ),
273349 ("Set World Poses" , "set_world_poses" ),
274350 ("Get Local Poses" , "get_local_poses" ),
275351 ("Set Local Poses" , "set_local_poses" ),
352+ ("Get World Scales" , "get_world_scales" ),
353+ ("Set World Scales" , "set_world_scales" ),
354+ ("Get Local Scales" , "get_local_scales" ),
355+ ("Set Local Scales" , "set_local_scales" ),
276356 ("Get Both (World+Local)" , "get_both" ),
277357 ("Interleaved World Set->Get" , "interleaved_world_set_get" ),
278358 ]
359+ operations = [init_op , * per_iter_operations ]
279360
280361 for op_name , op_key in operations :
281362 row = f"{ op_name :<28} "
@@ -286,15 +367,16 @@ def print_results(results_dict: dict[str, dict[str, float]], num_prims: int, num
286367
287368 print ("=" * 120 )
288369
289- total_row = f"{ 'Total' :<28} "
370+ total_row = f"{ 'Total (per-iter ops) ' :<28} "
290371 for name in api_names :
291- total_row += f" { sum (results_dict [name ].values ()) * 1000 :>{col_width }.4f} "
372+ per_iter_total = sum (results_dict [name ].get (k , 0 ) for _ , k in per_iter_operations )
373+ total_row += f" { per_iter_total * 1000 :>{col_width }.4f} "
292374 print (f"\n { total_row } " )
293375
294376 baseline = "isaaclab-usd"
295377 if baseline in results_dict and len (api_names ) > 1 :
296378 print ("\n " + "=" * 120 )
297- print (f"SPEEDUP vs { baseline .replace ('-' , ' ' ).title ()} " )
379+ print (f"SPEEDUP vs { baseline .replace ('-' , ' ' ).title ()} (per-iter ops; one-time init excluded) " )
298380 print ("=" * 120 )
299381 header = f"{ 'Operation' :<28} "
300382 for name in api_names :
@@ -304,7 +386,7 @@ def print_results(results_dict: dict[str, dict[str, float]], num_prims: int, num
304386 print ("-" * 120 )
305387
306388 base = results_dict [baseline ]
307- for op_name , op_key in operations :
389+ for op_name , op_key in per_iter_operations :
308390 row = f"{ op_name :<28} "
309391 base_t = base .get (op_key , 0 )
310392 for name in api_names :
@@ -317,11 +399,11 @@ def print_results(results_dict: dict[str, dict[str, float]], num_prims: int, num
317399 print (row )
318400
319401 print ("=" * 120 )
320- print (f"{ 'Overall' :>28} " , end = "" )
321- total_base = sum (base .values () )
402+ print (f"{ 'Overall (per-iter ops) ' :>28} " , end = "" )
403+ total_base = sum (base .get ( k , 0 ) for _ , k in per_iter_operations )
322404 for name in api_names :
323405 if name != baseline :
324- total_impl = sum (results_dict [name ].values () )
406+ total_impl = sum (results_dict [name ].get ( k , 0 ) for _ , k in per_iter_operations )
325407 if total_base > 0 and total_impl > 0 :
326408 print (f" { total_base / total_impl :>{col_width }.2f} x" , end = "" )
327409 else :
0 commit comments