@@ -392,26 +392,52 @@ function _finalize_macro(
392392 source:: LineNumberNode ;
393393 register_name:: Union{Nothing,Symbol} = nothing ,
394394 wrap_let:: Bool = false ,
395+ time_it:: Union{Nothing,String} = nothing ,
395396)
396397 @assert Meta. isexpr (model, :escape )
397- if wrap_let && model. args[1 ] isa Symbol
398- code = quote
399- let $ model = $ model
398+ ret = gensym ()
399+ code = if wrap_let && model. args[1 ] isa Symbol
400+ quote
401+ $ ret = let $ model = $ model
400402 $ code
401403 end
402404 end
405+ else
406+ :($ ret = $ code)
403407 end
404408 if register_name != = nothing
405409 sym_name = Meta. quot (register_name)
406410 code = quote
407411 _error_if_cannot_register ($ model, $ sym_name)
408- $ (esc (register_name)) = $ model[$ sym_name] = $ code
412+ $ code
413+ $ (esc (register_name)) = $ model[$ sym_name] = $ ret
414+ end
415+ end
416+ if time_it != = nothing
417+ code = quote
418+ start_time = time ()
419+ $ code
420+ _add_or_set_macro_time (
421+ $ model,
422+ ($ (QuoteNode (source)), $ time_it),
423+ time () - start_time,
424+ )
425+ $ ret
409426 end
410427 end
411428 is_valid_code = :(_valid_model ($ model, $ (Meta. quot (model. args[1 ]))))
412429 return Expr (:block , source, is_valid_code, code)
413430end
414431
432+ _add_or_set_macro_time (model:: AbstractModel , key, value) = nothing
433+
434+ function _add_or_set_macro_time (model:: GenericModel , key, value)
435+ if model. enable_macro_timing
436+ model. macro_times[key] = get! (model. macro_times, key, 0.0 ) + value
437+ end
438+ return
439+ end
440+
415441function _error_if_cannot_register (model:: AbstractModel , name:: Symbol )
416442 obj_dict = object_dictionary (model)
417443 if haskey (obj_dict, name)
@@ -474,6 +500,100 @@ function _plural_macro_code(model, block, macro_sym)
474500 return code
475501end
476502
503+ """
504+ set_macro_timing(::GenericModel, value::Bool)
505+
506+ Turn on (if `value`, or off, if `!value`) JuMP's built-in profiling of model
507+ construction macros.
508+
509+ Use [`print_macro_timing_summary`](@ref) to display a summary.
510+
511+ ## Example
512+
513+ ```jldoctest; filter=[r"Total time inside macros: .+ seconds", r"├.+", r"└.+"]
514+ julia> begin
515+ model = Model()
516+ set_macro_timing(model, true)
517+ @variable(model, x[1:2])
518+ @objective(model, Min, sum(x))
519+ end;
520+
521+ julia> print_macro_timing_summary(model)
522+ Total time inside macros: 5.33690e-02 seconds
523+ │
524+ ├ 2.96490e-02 s [55.55%]
525+ │ ├ REPL[8]:3
526+ │ └ `@variable(model, x[1:2])`
527+ │
528+ └ 2.37200e-02 s [44.45%]
529+ ├ REPL[8]:4
530+ └ `@objective(model, Min, sum(x))`
531+ ```
532+ """
533+ function set_macro_timing (model:: GenericModel , value:: Bool )
534+ model. enable_macro_timing = value
535+ return
536+ end
537+
538+ function _string_summary (x)
539+ if length (x) <= 75
540+ return x
541+ end
542+ return x[1 : 32 ] * " [...] " * x[end - 31 : end ]
543+ end
544+
545+ _format_time (x:: Float64 ) = string (_format (x), " seconds" )
546+
547+ """
548+ print_macro_timing_summary([io::IO = stdout], model::GenericModel)
549+
550+ Print a summary of the runtime of each macro.
551+
552+ Before calling this method, you must have enabled the macro timing feature using
553+ [`set_macro_timing`](@ref).
554+
555+ ## Example
556+
557+ ```jldoctest; filter=[r"Total time inside macros: .+ seconds", r"├.+", r"└.+"]
558+ julia> begin
559+ model = Model()
560+ set_macro_timing(model, true)
561+ @variable(model, x[1:2])
562+ @objective(model, Min, sum(x))
563+ end;
564+
565+ julia> print_macro_timing_summary(model)
566+ Total time inside macros: 5.33690e-02 seconds
567+ │
568+ ├ 2.96490e-02 s [55.55%]
569+ │ ├ REPL[8]:3
570+ │ └ `@variable(model, x[1:2])`
571+ │
572+ └ 2.37200e-02 s [44.45%]
573+ ├ REPL[8]:4
574+ └ `@objective(model, Min, sum(x))`
575+ ```
576+ """
577+ function print_macro_timing_summary (io:: IO , model:: GenericModel )
578+ total_time = sum (values (model. macro_times))
579+ times = sort! (collect (model. macro_times); by = last, rev = true )
580+ println (io, " Total time inside macros: " , _format_time (total_time))
581+ for i in 1 : length (times)
582+ (source, expr), time = times[i]
583+ percent = round (100 * time / total_time; digits = 2 )
584+ a, b = ifelse (i < length (times), (' ├' , ' │' ), (' └' , ' ' ))
585+ println (io, " │" )
586+ println (io, " $a " , _format (time), " s [$percent %]" )
587+ println (io, " $b ├ $(source. file) :$(source. line) " )
588+ println (io, " $b └ " , replace (_string_summary (expr), " \n " => " " ))
589+ end
590+ return
591+ end
592+
593+ function print_macro_timing_summary (model:: GenericModel )
594+ return print_macro_timing_summary (stdout , model)
595+ end
596+
477597for file in readdir (joinpath (@__DIR__ , " macros" ))
478598 # The check for .jl is necessary because some users may have other files
479599 # like .cov from running code coverage. See JuMP.jl#3746.
0 commit comments