@@ -146,7 +146,8 @@ function format_bytes(bytes; binary=true) # also used by InteractiveUtils
146
146
end
147
147
end
148
148
149
- function time_print (io:: IO , elapsedtime, bytes= 0 , gctime= 0 , allocs= 0 , compile_time= 0 , recompile_time= 0 , newline= false ; msg:: Union{String,Nothing} = nothing )
149
+ function time_print (io:: IO , elapsedtime, bytes= 0 , gctime= 0 , allocs= 0 , lock_conflicts= 0 , compile_time= 0 , recompile_time= 0 , newline= false ;
150
+ msg:: Union{String,Nothing} = nothing )
150
151
timestr = Ryu. writefixed (Float64 (elapsedtime/ 1e9 ), 6 )
151
152
str = sprint () do io
152
153
if msg isa String
@@ -172,6 +173,10 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti
172
173
end
173
174
print (io, Ryu. writefixed (Float64 (100 * gctime/ elapsedtime), 2 ), " % gc time" )
174
175
end
176
+ if lock_conflicts > 0
177
+ plural = lock_conflicts == 1 ? " " : " s"
178
+ print (io, " , " , lock_conflicts, " lock conflict$plural " )
179
+ end
175
180
if compile_time > 0
176
181
if bytes != 0 || allocs != 0 || gctime > 0
177
182
print (io, " , " )
@@ -190,11 +195,11 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti
190
195
nothing
191
196
end
192
197
193
- function timev_print (elapsedtime, diff:: GC_Diff , compile_times; msg:: Union{String,Nothing} = nothing )
198
+ function timev_print (elapsedtime, diff:: GC_Diff , lock_conflicts, compile_times; msg:: Union{String,Nothing} = nothing )
194
199
allocs = gc_alloc_count (diff)
195
200
compile_time = first (compile_times)
196
201
recompile_time = last (compile_times)
197
- time_print (stdout , elapsedtime, diff. allocd, diff. total_time, allocs, compile_time, recompile_time, true ; msg)
202
+ time_print (stdout , elapsedtime, diff. allocd, diff. total_time, allocs, lock_conflicts, compile_time, recompile_time, true ; msg)
198
203
padded_nonzero_print (elapsedtime, " elapsed time (ns)" )
199
204
padded_nonzero_print (diff. total_time, " gc time (ns)" )
200
205
padded_nonzero_print (diff. allocd, " bytes allocated" )
226
231
A macro to execute an expression, printing the time it took to execute, the number of
227
232
allocations, and the total number of bytes its execution caused to be allocated, before
228
233
returning the value of the expression. Any time spent garbage collecting (gc), compiling
229
- new code, or recompiling invalidated code is shown as a percentage.
234
+ new code, or recompiling invalidated code is shown as a percentage. Any lock conflicts
235
+ where a [`ReentrantLock`](@ref) had to wait are shown as a count.
230
236
231
237
Optionally provide a description string to print before the time report.
232
238
@@ -247,6 +253,9 @@ See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@
247
253
248
254
Recompilation time being shown separately from compilation time was introduced in Julia 1.8
249
255
256
+ !!! compat "Julia 1.11"
257
+ The reporting of any lock conflicts was added in Julia 1.11.
258
+
250
259
```julia-repl
251
260
julia> x = rand(10,10);
252
261
@@ -283,7 +292,7 @@ macro time(msg, ex)
283
292
quote
284
293
local ret = @timed $ (esc (ex))
285
294
local _msg = $ (esc (msg))
286
- time_print (stdout , ret. time* 1e9 , ret. gcstats. allocd, ret. gcstats. total_time, gc_alloc_count (ret. gcstats), ret. compile_time* 1e9 , ret. recompile_time* 1e9 , true ; msg= _msg)
295
+ time_print (stdout , ret. time* 1e9 , ret. gcstats. allocd, ret. gcstats. total_time, gc_alloc_count (ret. gcstats), ret. lock_conflicts, ret . compile_time* 1e9 , ret. recompile_time* 1e9 , true ; msg= _msg)
287
296
ret. value
288
297
end
289
298
end
@@ -355,7 +364,7 @@ macro timev(msg, ex)
355
364
quote
356
365
local ret = @timed $ (esc (ex))
357
366
local _msg = $ (esc (msg))
358
- timev_print (ret. time* 1e9 , ret. gcstats, (ret. compile_time* 1e9 , ret. recompile_time* 1e9 ); msg= _msg)
367
+ timev_print (ret. time* 1e9 , ret. gcstats, ret . lock_conflicts, (ret. compile_time* 1e9 , ret. recompile_time* 1e9 ); msg= _msg)
359
368
ret. value
360
369
end
361
370
end
@@ -449,19 +458,57 @@ macro allocations(ex)
449
458
end
450
459
end
451
460
461
+ """
462
+ @lock_conflicts
463
+
464
+ A macro to evaluate an expression, discard the resulting value, and instead return the
465
+ total number of lock conflicts during evaluation, where a lock attempt on a [`ReentrantLock`](@ref)
466
+ resulted in a wait because the lock was already held.
467
+
468
+ See also [`@time`](@ref), [`@timev`](@ref) and [`@timed`](@ref).
469
+
470
+ ```julia-repl
471
+ julia> @lock_conflicts begin
472
+ l = ReentrantLock()
473
+ Threads.@threads for i in 1:Threads.nthreads()
474
+ lock(l) do
475
+ sleep(1)
476
+ end
477
+ end
478
+ end
479
+ 5
480
+ ```
481
+
482
+ !!! compat "Julia 1.11"
483
+ This macro was added in Julia 1.11.
484
+ """
485
+ macro lock_conflicts (ex)
486
+ quote
487
+ Threads. lock_profiling (true )
488
+ local lock_conflicts = Threads. LOCK_CONFLICT_COUNT[]
489
+ try
490
+ $ (esc (ex))
491
+ finally
492
+ Threads. lock_profiling (false )
493
+ end
494
+ Threads. LOCK_CONFLICT_COUNT[] - lock_conflicts
495
+ end
496
+ end
497
+
452
498
"""
453
499
@timed
454
500
455
501
A macro to execute an expression, and return the value of the expression, elapsed time in seconds,
456
502
total bytes allocated, garbage collection time, an object with various memory allocation
457
- counters, compilation time in seconds, and recompilation time in seconds.
503
+ counters, compilation time in seconds, and recompilation time in seconds. Any lock conflicts
504
+ where a [`ReentrantLock`](@ref) had to wait are shown as a count.
458
505
459
506
In some cases the system will look inside the `@timed` expression and compile some of the
460
507
called code before execution of the top-level expression begins. When that happens, some
461
508
compilation time will not be counted. To include this time you can run `@timed @eval ...`.
462
509
463
510
See also [`@time`](@ref), [`@timev`](@ref), [`@elapsed`](@ref),
464
- [`@allocated`](@ref), and [`@allocations `](@ref).
511
+ [`@allocated`](@ref), [`@allocations`](@ref), and [`@lock_conflicts `](@ref).
465
512
466
513
```julia-repl
467
514
julia> stats = @timed rand(10^6);
@@ -493,21 +540,34 @@ julia> stats.recompile_time
493
540
The return type of this macro was changed from `Tuple` to `NamedTuple` in Julia 1.5.
494
541
495
542
!!! compat "Julia 1.11"
496
- The `compile_time` and `recompile_time` fields were added in Julia 1.11.
543
+ The `lock_conflicts`, ` compile_time`, and `recompile_time` fields were added in Julia 1.11.
497
544
"""
498
545
macro timed (ex)
499
546
quote
500
547
Experimental. @force_compile
548
+ Threads. lock_profiling (true )
549
+ local lock_conflicts = Threads. LOCK_CONFLICT_COUNT[]
501
550
local stats = gc_num ()
502
551
local elapsedtime = time_ns ()
503
552
cumulative_compile_timing (true )
504
553
local compile_elapsedtimes = cumulative_compile_time_ns ()
505
554
local val = @__tryfinally ($ (esc (ex)),
506
555
(elapsedtime = time_ns () - elapsedtime;
507
556
cumulative_compile_timing (false );
508
- compile_elapsedtimes = cumulative_compile_time_ns () .- compile_elapsedtimes)
557
+ compile_elapsedtimes = cumulative_compile_time_ns () .- compile_elapsedtimes;
558
+ lock_conflicts = Threads. LOCK_CONFLICT_COUNT[] - lock_conflicts;
559
+ Threads. lock_profiling (false ))
509
560
)
510
561
local diff = GC_Diff (gc_num (), stats)
511
- (value= val, time= elapsedtime/ 1e9 , bytes= diff. allocd, gctime= diff. total_time/ 1e9 , gcstats= diff, compile_time= compile_elapsedtimes[1 ]/ 1e9 , recompile_time= compile_elapsedtimes[2 ]/ 1e9 )
562
+ (
563
+ value= val,
564
+ time= elapsedtime/ 1e9 ,
565
+ bytes= diff. allocd,
566
+ gctime= diff. total_time/ 1e9 ,
567
+ gcstats= diff,
568
+ lock_conflicts= lock_conflicts,
569
+ compile_time= compile_elapsedtimes[1 ]/ 1e9 ,
570
+ recompile_time= compile_elapsedtimes[2 ]/ 1e9
571
+ )
512
572
end
513
573
end
0 commit comments