@@ -146,7 +146,8 @@ function format_bytes(bytes; binary=true) # also used by InteractiveUtils
146146 end
147147end
148148
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 )
150151 timestr = Ryu. writefixed (Float64 (elapsedtime/ 1e9 ), 6 )
151152 str = sprint () do io
152153 if msg isa String
@@ -172,6 +173,10 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti
172173 end
173174 print (io, Ryu. writefixed (Float64 (100 * gctime/ elapsedtime), 2 ), " % gc time" )
174175 end
176+ if lock_conflicts > 0
177+ plural = lock_conflicts == 1 ? " " : " s"
178+ print (io, " , " , lock_conflicts, " lock conflict$plural " )
179+ end
175180 if compile_time > 0
176181 if bytes != 0 || allocs != 0 || gctime > 0
177182 print (io, " , " )
@@ -190,11 +195,11 @@ function time_print(io::IO, elapsedtime, bytes=0, gctime=0, allocs=0, compile_ti
190195 nothing
191196end
192197
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 )
194199 allocs = gc_alloc_count (diff)
195200 compile_time = first (compile_times)
196201 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)
198203 padded_nonzero_print (elapsedtime, " elapsed time (ns)" )
199204 padded_nonzero_print (diff. total_time, " gc time (ns)" )
200205 padded_nonzero_print (diff. allocd, " bytes allocated" )
226231A macro to execute an expression, printing the time it took to execute, the number of
227232allocations, and the total number of bytes its execution caused to be allocated, before
228233returning 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 lock had to wait are shown as a count.
230236
231237Optionally provide a description string to print before the time report.
232238
@@ -247,6 +253,9 @@ See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@
247253
248254 Recompilation time being shown separately from compilation time was introduced in Julia 1.8
249255
256+ !!! compat "Julia 1.11"
257+ The reporting of any lock conflicts was added in Julia 1.11.
258+
250259```julia-repl
251260julia> x = rand(10,10);
252261
@@ -283,7 +292,7 @@ macro time(msg, ex)
283292 quote
284293 local ret = @timed $ (esc (ex))
285294 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)
287296 ret. value
288297 end
289298end
@@ -355,7 +364,7 @@ macro timev(msg, ex)
355364 quote
356365 local ret = @timed $ (esc (ex))
357366 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)
359368 ret. value
360369 end
361370end
@@ -449,19 +458,55 @@ macro allocations(ex)
449458 end
450459end
451460
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 resulted in a wait
466+ 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+ Experimental. @force_compile
488+ Threads. lock_profiling (true )
489+ local lock_conflicts = Threads. LOCK_CONFLICT_COUNT[]
490+ $ (esc (ex))
491+ Threads. lock_profiling (false )
492+ Threads. LOCK_CONFLICT_COUNT[] - lock_conflicts
493+ end
494+ end
495+
452496"""
453497 @timed
454498
455499A macro to execute an expression, and return the value of the expression, elapsed time in seconds,
456500total bytes allocated, garbage collection time, an object with various memory allocation
457- counters, compilation time in seconds, and recompilation time in seconds.
501+ counters, compilation time in seconds, and recompilation time in seconds. Any lock conflicts
502+ where a lock had to wait are shown as a count.
458503
459504In some cases the system will look inside the `@timed` expression and compile some of the
460505called code before execution of the top-level expression begins. When that happens, some
461506compilation time will not be counted. To include this time you can run `@timed @eval ...`.
462507
463508See also [`@time`](@ref), [`@timev`](@ref), [`@elapsed`](@ref),
464- [`@allocated`](@ref), and [`@allocations `](@ref).
509+ [`@allocated`](@ref), [`@allocations`](@ref), and [`@lock_conflicts `](@ref).
465510
466511```julia-repl
467512julia> stats = @timed rand(10^6);
@@ -493,21 +538,34 @@ julia> stats.recompile_time
493538 The return type of this macro was changed from `Tuple` to `NamedTuple` in Julia 1.5.
494539
495540!!! compat "Julia 1.11"
496- The `compile_time` and `recompile_time` fields were added in Julia 1.11.
541+ The `lock_conflicts`, ` compile_time`, and `recompile_time` fields were added in Julia 1.11.
497542"""
498543macro timed (ex)
499544 quote
500545 Experimental. @force_compile
546+ Threads. lock_profiling (true )
547+ local lock_conflicts = Threads. LOCK_CONFLICT_COUNT[]
501548 local stats = gc_num ()
502549 local elapsedtime = time_ns ()
503550 cumulative_compile_timing (true )
504551 local compile_elapsedtimes = cumulative_compile_time_ns ()
505552 local val = @__tryfinally ($ (esc (ex)),
506553 (elapsedtime = time_ns () - elapsedtime;
507554 cumulative_compile_timing (false );
508- compile_elapsedtimes = cumulative_compile_time_ns () .- compile_elapsedtimes)
555+ compile_elapsedtimes = cumulative_compile_time_ns () .- compile_elapsedtimes;
556+ lock_conflicts = Threads. LOCK_CONFLICT_COUNT[] - lock_conflicts;
557+ Threads. lock_profiling (false ))
509558 )
510559 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 )
560+ (
561+ value= val,
562+ time= elapsedtime/ 1e9 ,
563+ bytes= diff. allocd,
564+ gctime= diff. total_time/ 1e9 ,
565+ gcstats= diff,
566+ lock_conflicts= lock_conflicts,
567+ compile_time= compile_elapsedtimes[1 ]/ 1e9 ,
568+ recompile_time= compile_elapsedtimes[2 ]/ 1e9
569+ )
512570 end
513571end
0 commit comments