@@ -472,20 +472,27 @@ 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+ val = @noinline f (args... )
480480 Base. gc_bytes (b1)
481+ donotdelete (val)
481482 return b1[] - b0[]
482483end
483484only (methods (allocated)). called = 0xff
484485
485- function allocations (f, args:: Vararg{Any,N} ) where {N}
486+ @constprop :none function allocations (f, args:: Vararg{Any,N} ) where {N}
487+ # Note this value is unused, but without it `allocated` and `allocations`
488+ # are sufficiently different that the compiler can remove allocations here
489+ # that it cannot remove there, giving inconsistent numbers.
490+ b1 = Ref {Int64} (0 )
486491 stats = Base. gc_num ()
487- f (args... )
492+ val = @noinline f (args... )
488493 diff = Base. GC_Diff (Base. gc_num (), stats)
494+ gc_bytes (b1)
495+ donotdelete (val)
489496 return Base. gc_alloc_count (diff)
490497end
491498only (methods (allocations)). called = 0xff
@@ -501,12 +508,56 @@ function is_simply_call(@nospecialize ex)
501508 return true
502509end
503510
511+ function _gen_allocation_measurer (ex, fname:: Symbol )
512+ if isexpr (ex, :call )
513+ if ! is_simply_call (ex)
514+ ex = :((() -> $ ex)())
515+ end
516+ pushfirst! (ex. args, GlobalRef (Base, fname))
517+ return esc (ex)
518+ elseif fname === :allocated
519+ # v1.11-compatible implementation
520+ return quote
521+ Experimental. @force_compile
522+ local b0 = Ref {Int64} (0 )
523+ local b1 = Ref {Int64} (0 )
524+ gc_bytes (b0)
525+ local val = $ (esc (ex))
526+ gc_bytes (b1)
527+ donotdelete (val)
528+ b1[] - b0[]
529+ end
530+ else
531+ @assert fname === :allocations
532+ return quote
533+ Experimental. @force_compile
534+ local b1 = Ref {Int64} (0 ) # See note above in `allocations`
535+ local stats = Base. gc_num ()
536+ local val = $ (esc (ex))
537+ local diff = Base. GC_Diff (Base. gc_num (), stats)
538+ gc_bytes (b1)
539+ donotdelete (val)
540+ Base. gc_alloc_count (diff)
541+ end
542+ end
543+ end
544+
504545"""
505546 @allocated
506547
507548A macro to evaluate an expression, discarding the resulting value, instead returning the
508549total number of bytes allocated during evaluation of the expression.
509550
551+ If the expression is a function call, an effort is made to measure only allocations
552+ during the function, excluding any overhead from calling it and not performing
553+ constant propagation with the provided argument values. This mode of use is recommended.
554+ If you want to include those effects, i.e. measuring the call site as well,
555+ use the syntax `@allocated (()->f(1))()`.
556+
557+ For more complex expressions, the code is simply run in place and therefore may see
558+ allocations due to the surrounding context. For example it is possible for
559+ `@allocated f(1)` and `@allocated x = f(1)` to give different results.
560+
510561See also [`@allocations`](@ref), [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref),
511562and [`@elapsed`](@ref).
512563
@@ -516,11 +567,37 @@ julia> @allocated rand(10^6)
516567```
517568"""
518569macro allocated (ex)
519- if ! is_simply_call (ex)
520- ex = :((() -> $ ex)())
570+ _gen_allocation_measurer (ex, :allocated )
571+ end
572+
573+ """
574+ @allocated inside f(...)
575+
576+ Return the number of bytes allocated during the execution of a given function,
577+ excluding any allocations in the argument expressions or due to the call itself.
578+
579+ ```julia-repl
580+ julia> @allocated identity([])
581+ 32
582+
583+ julia> @allocated inside identity([])
584+ 0
585+ ```
586+
587+ !!! compat "Julia 1.12"
588+ This macro was added in Julia 1.12.
589+ """
590+ macro allocated (how, ex)
591+ if how === :inside
592+ if isexpr (ex, :call )
593+ pushfirst! (ex. args, GlobalRef (Base, :allocated ))
594+ return esc (ex)
595+ else
596+ error (" `@allocated inside` requires a call expression" )
597+ end
598+ else
599+ error (" Unrecognized symbol argument to `@allocated`; must be `inside`" )
521600 end
522- pushfirst! (ex. args, GlobalRef (Base, :allocated ))
523- return esc (ex)
524601end
525602
526603"""
@@ -541,11 +618,37 @@ julia> @allocations rand(10^6)
541618 This macro was added in Julia 1.9.
542619"""
543620macro allocations (ex)
544- if ! is_simply_call (ex)
545- ex = :((() -> $ ex)())
621+ _gen_allocation_measurer (ex, :allocations )
622+ end
623+
624+ """
625+ @allocations inside f(...)
626+
627+ Return the number of allocations during the execution of a given function,
628+ excluding any allocations in the argument expressions or due to the call itself.
629+
630+ ```julia-repl
631+ julia> @allocations identity([])
632+ 1
633+
634+ julia> @allocations inside identity([])
635+ 0
636+ ```
637+
638+ !!! compat "Julia 1.12"
639+ This macro was added in Julia 1.12.
640+ """
641+ macro allocations (how, ex)
642+ if how === :inside
643+ if isexpr (ex, :call )
644+ pushfirst! (ex. args, GlobalRef (Base, :allocations ))
645+ return esc (ex)
646+ else
647+ error (" `@allocations inside` requires a call expression" )
648+ end
649+ else
650+ error (" Unrecognized symbol argument to `@allocations`; must be `inside`" )
546651 end
547- pushfirst! (ex. args, GlobalRef (Base, :allocations ))
548- return esc (ex)
549652end
550653
551654
0 commit comments