@@ -472,20 +472,25 @@ function gc_bytes()
472472 b[]
473473end
474474
475- function allocated (f, args:: Vararg{Any,N} ) where {N}
475+ @constprop :none function allocated (f, args:: Vararg{Any,N} ) where {N}
476476 b0 = Ref {Int64} (0 )
477477 b1 = Ref {Int64} (0 )
478478 Base. gc_bytes (b0)
479- f (args... )
479+ @noinline f (args... )
480480 Base. gc_bytes (b1)
481481 return b1[] - b0[]
482482end
483483only (methods (allocated)). called = 0xff
484484
485- function allocations (f, args:: Vararg{Any,N} ) where {N}
485+ @constprop :none function allocations (f, args:: Vararg{Any,N} ) where {N}
486+ # Note this value is unused, but without it `allocated` and `allocations`
487+ # are sufficiently different that the compiler can remove allocations here
488+ # that it cannot remove there, giving inconsistent numbers.
489+ b1 = Ref {Int64} (0 )
486490 stats = Base. gc_num ()
487- f (args... )
491+ @noinline f (args... )
488492 diff = Base. GC_Diff (Base. gc_num (), stats)
493+ gc_bytes (b1)
489494 return Base. gc_alloc_count (diff)
490495end
491496only (methods (allocations)). called = 0xff
@@ -501,12 +506,54 @@ function is_simply_call(@nospecialize ex)
501506 return true
502507end
503508
509+ function _gen_allocation_measurer (ex, fname:: Symbol )
510+ if isexpr (ex, :call )
511+ if ! is_simply_call (ex)
512+ ex = :((() -> $ ex)())
513+ end
514+ pushfirst! (ex. args, GlobalRef (Base, fname))
515+ return esc (ex)
516+ elseif fname === :allocated
517+ # v1.11-compatible implementation
518+ return quote
519+ Experimental. @force_compile
520+ local b0 = Ref {Int64} (0 )
521+ local b1 = Ref {Int64} (0 )
522+ gc_bytes (b0)
523+ $ (esc (ex))
524+ gc_bytes (b1)
525+ b1[] - b0[]
526+ end
527+ else
528+ @assert fname === :allocations
529+ return quote
530+ Experimental. @force_compile
531+ local b1 = Ref {Int64} (0 ) # See note above in `allocations`
532+ local stats = Base. gc_num ()
533+ $ (esc (ex))
534+ local diff = Base. GC_Diff (Base. gc_num (), stats)
535+ gc_bytes (b1)
536+ Base. gc_alloc_count (diff)
537+ end
538+ end
539+ end
540+
504541"""
505542 @allocated
506543
507544A macro to evaluate an expression, discarding the resulting value, instead returning the
508545total number of bytes allocated during evaluation of the expression.
509546
547+ If the expression is a function call, an effort is made to measure only allocations
548+ during the function, excluding any overhead from calling it and not performing
549+ constant propagation with the provided argument values. This mode of use is recommended.
550+ If you want to include those effects, i.e. measuring the call site as well,
551+ use the syntax `@allocated (()->f(1))()`.
552+
553+ For more complex expressions, the code is simply run in place and therefore may see
554+ allocations due to the surrounding context. For example it is possible for
555+ `@allocated f(1)` and `@allocated x = f(1)` to give different results.
556+
510557See also [`@allocations`](@ref), [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref),
511558and [`@elapsed`](@ref).
512559
@@ -516,11 +563,37 @@ julia> @allocated rand(10^6)
516563```
517564"""
518565macro allocated (ex)
519- if ! is_simply_call (ex)
520- ex = :((() -> $ ex)())
566+ _gen_allocation_measurer (ex, :allocated )
567+ end
568+
569+ """
570+ @allocated inside f(...)
571+
572+ Return the number of bytes allocated during the execution of a given function,
573+ excluding any allocations in the argument expressions or due to the call itself.
574+
575+ ```julia-repl
576+ julia> @allocated identity([])
577+ 32
578+
579+ julia> @allocated inside identity([])
580+ 0
581+ ```
582+
583+ !!! compat "Julia 1.12"
584+ This macro was added in Julia 1.12.
585+ """
586+ macro allocated (how, ex)
587+ if how === :inside
588+ if isexpr (ex, :call )
589+ pushfirst! (ex. args, GlobalRef (Base, :allocated ))
590+ return esc (ex)
591+ else
592+ error (" `@allocated inside` requires a call expression" )
593+ end
594+ else
595+ error (" Unrecognized symbol argument to `@allocated`; must be `inside`" )
521596 end
522- pushfirst! (ex. args, GlobalRef (Base, :allocated ))
523- return esc (ex)
524597end
525598
526599"""
@@ -541,11 +614,37 @@ julia> @allocations rand(10^6)
541614 This macro was added in Julia 1.9.
542615"""
543616macro allocations (ex)
544- if ! is_simply_call (ex)
545- ex = :((() -> $ ex)())
617+ _gen_allocation_measurer (ex, :allocations )
618+ end
619+
620+ """
621+ @allocations inside f(...)
622+
623+ Return the number of allocations during the execution of a given function,
624+ excluding any allocations in the argument expressions or due to the call itself.
625+
626+ ```julia-repl
627+ julia> @allocations identity([])
628+ 1
629+
630+ julia> @allocations inside identity([])
631+ 0
632+ ```
633+
634+ !!! compat "Julia 1.12"
635+ This macro was added in Julia 1.12.
636+ """
637+ macro allocations (how, ex)
638+ if how === :inside
639+ if isexpr (ex, :call )
640+ pushfirst! (ex. args, GlobalRef (Base, :allocations ))
641+ return esc (ex)
642+ else
643+ error (" `@allocations inside` requires a call expression" )
644+ end
645+ else
646+ error (" Unrecognized symbol argument to `@allocations`; must be `inside`" )
546647 end
547- pushfirst! (ex. args, GlobalRef (Base, :allocations ))
548- return esc (ex)
549648end
550649
551650
0 commit comments