Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set @nospecialize for something #46115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rikhuijzer
Copy link
Contributor

@rikhuijzer rikhuijzer commented Jul 20, 2022

This reduces the time to first something and shouldn't have a negative effect on performance. Benchmarked with Julia 1.8-rc3:

_something() = throw(ArgumentError("No value arguments present"))
_something(x::Nothing, @nospecialize(y...)) = something(y...)
_something(x::Some, @nospecialize(y...)) = x.value
_something(@nospecialize(x::Any), @nospecialize(y...)) = x

println("\nWarmup")
warmup(x) = x
@time @eval warmup(1)

data(n) = Tuple(rand([1, 2, nothing], n))
data(10)

println("\n`@time @eval` with `data(10)`")
@time @eval _something(data(10))
@time @eval something(data(10))

println("\n`@time @eval` with `data(10)`")
@time @eval _something(data(10))
@time @eval something(data(10))

println("\n`@time @eval` with `data(100)`")
@time @eval _something(data(100))
@time @eval something(data(100))

using BenchmarkTools: @btime

println("\n`@btime` with `data(10)`")
@btime _something(data(10))
@btime something(data(10))

println("\n`@btime` with `data(100)`")
@btime _something(data(100))
@btime something(data(100))
Warmup
  0.008335 seconds (715 allocations: 46.219 KiB, 26.85% compilation time)

`@time @eval` with `data(10)`
  0.002522 seconds (475 allocations: 31.594 KiB, 90.09% compilation time) # _something
  0.003608 seconds (437 allocations: 29.297 KiB, 92.91% compilation time) # something

`@time @eval` with `data(10)`
  0.000231 seconds (58 allocations: 2.984 KiB) # _something
  0.003566 seconds (436 allocations: 29.234 KiB, 93.21% compilation time) # something

`@time @eval` with `data(100)`
  0.000229 seconds (58 allocations: 4.969 KiB) # _something
  0.003454 seconds (436 allocations: 31.250 KiB, 92.85% compilation time) # something

`@btime` with `data(10)`
  7.765 μs (6 allocations: 406 bytes) # _something
  6.893 μs (6 allocations: 403 bytes) # something

`@btime` with `data(100)`
  52.839 μs (8 allocations: 2.53 KiB) # _something
  54.913 μs (8 allocations: 2.48 KiB) # something

The benchmark shows that _something has no compilation time for arbitrary tuples after the first invocation. The original method something does have compilation time. Furthermore, the running time does not seem to be affected and I cannot think of a reason where it would since f(@nospecialize(x)) = x correctly infers the return type even with the @nospecialize annotation.

@rikhuijzer
Copy link
Contributor Author

The test failures appear unrelated.

something(x::Nothing, y...) = something(y...)
something(x::Some, y...) = x.value
something(x::Any, y...) = x
something(x::Nothing, @nospecialize(y...)) = something(y...)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one seems to be sketchy, as specialization may produce better code for the y... part. We should double check the performance for splat cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted. The nospecialize version appears to be slightly slower on both Julia 1.8-rc3 and 1.9.0 (5e081d6). I cannot spot a difference in the @code_warntype though which I don't understand.

julia> _c(x::Nothing, @nospecialize(y...)) = something(y...)
_c (generic function with 1 method)

julia> _d(x::Nothing, y...) = something(y...)
_d (generic function with 1 method)

julia> @btime _c(Tuple([nothing; rand([1, 2, nothing], 100)])...);
  96.258 μs (9 allocations: 3.50 KiB)

julia> @btime _d(Tuple([nothing; rand([1, 2, nothing], 100)])...);
  93.493 μs (9 allocations: 3.50 KiB)

julia> @btime _c(Tuple([nothing; rand([1, 2, nothing], 100)])...);
  95.241 μs (9 allocations: 3.50 KiB)

julia> @btime _d(Tuple([nothing; rand([1, 2, nothing], 100)])...);
  94.229 μs (9 allocations: 3.55 KiB)

Even more strange, the nospecialize version for something(x::Any, y...) = x is slower than the original version too?

julia> _a(@nospecialize(x::Any), @nospecialize(y...)) = x
_a (generic function with 1 method)

julia> _b(x::Any, y...) = x
_b (generic function with 1 method)

julia> @btime _b(Tuple([1; rand([1, 2, nothing], 100)])...);
  61.069 μs (6 allocations: 3.45 KiB)

julia> @btime _a(Tuple([1; rand([1, 2, nothing], 100)])...);
  62.778 μs (6 allocations: 3.45 KiB)

julia> @btime _b(Tuple([1; rand([1, 2, nothing], 100)])...);
  61.295 μs (6 allocations: 3.41 KiB)

julia> @btime _a(Tuple([1; rand([1, 2, nothing], 100)])...);
  63.034 μs (6 allocations: 3.41 KiB)

I don't get it.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You way first understand what @nospecialize really does. Read #41931 if interested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants