@@ -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 [`ReentrantLock`](@ref) 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,57 @@ 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 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+
452498"""
453499 @timed
454500
455501A macro to execute an expression, and return the value of the expression, elapsed time in seconds,
456502total 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.
458505
459506In some cases the system will look inside the `@timed` expression and compile some of the
460507called code before execution of the top-level expression begins. When that happens, some
461508compilation time will not be counted. To include this time you can run `@timed @eval ...`.
462509
463510See also [`@time`](@ref), [`@timev`](@ref), [`@elapsed`](@ref),
464- [`@allocated`](@ref), and [`@allocations `](@ref).
511+ [`@allocated`](@ref), [`@allocations`](@ref), and [`@lock_conflicts `](@ref).
465512
466513```julia-repl
467514julia> stats = @timed rand(10^6);
@@ -493,21 +540,34 @@ julia> stats.recompile_time
493540 The return type of this macro was changed from `Tuple` to `NamedTuple` in Julia 1.5.
494541
495542!!! 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.
497544"""
498545macro timed (ex)
499546 quote
500547 Experimental. @force_compile
548+ Threads. lock_profiling (true )
549+ local lock_conflicts = Threads. LOCK_CONFLICT_COUNT[]
501550 local stats = gc_num ()
502551 local elapsedtime = time_ns ()
503552 cumulative_compile_timing (true )
504553 local compile_elapsedtimes = cumulative_compile_time_ns ()
505554 local val = @__tryfinally ($ (esc (ex)),
506555 (elapsedtime = time_ns () - elapsedtime;
507556 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 ))
509560 )
510561 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+ )
512572 end
513573end
0 commit comments