Change isnull field of Nullable into hasvalue#18510
Conversation
2618e99 to
8da3c10
Compare
| end | ||
|
|
||
| Nullable{T}(value::T, isnull::Bool=false) = Nullable{T}(value, isnull) | ||
| Nullable{T}(value::T, hasvalue::Bool=true) = Nullable{T}(value, hasvalue) |
There was a problem hiding this comment.
is the second argument meant to be used?
@tkelman Indeed the two-argument form is mostly useful for tests, where using === requires controlling the contents of value (cf. #16923). Maybe it can also help for performance, instead of writing ifelse(hasvalue, Nullable(value), Nullable()), I would have to check. I guess @johnmyleswhite can tell.
There was a problem hiding this comment.
We make use of this for null-safe operations where we can generate the value and hasvalue fields without branching.
There was a problem hiding this comment.
okay, then it should be documented?
There was a problem hiding this comment.
Yes. We should document it in such a way that it's clear that you have more freedom than you usually need and that its use is a performance optimization.
| @inline function get{S,T}(x::Nullable{S}, y::T) | ||
| if isbits(S) | ||
| ifelse(x.isnull, y, x.value) | ||
| ifelse(isnull(x), y, x.value) |
There was a problem hiding this comment.
Should we maybe run benchmarks to make sure any ! operations are successfully eliminated? Or perhaps this is nothing compared to the cost of the branch, so even if they aren't removed, it doesn't matter.
There was a problem hiding this comment.
Not a bad idea in general, but I don't think we have any benchmarks for Nullable yet. We really need to add some. We could also check the generated code manually in some cases.
There was a problem hiding this comment.
I've opened a PR to add benchmarks: JuliaCI/BaseBenchmarks.jl#24. Please suggest cases to add measurements for.
There was a problem hiding this comment.
I see no cause for concern in the generated code. It has not changed at all with this patch.
julia> @code_llvm get(Nullable{Int}(10), 100)
define i64 @julia_get_64687(%Nullable.7*, i64) #0 {
top:
%2 = getelementptr inbounds %Nullable.7, %Nullable.7* %0, i64 0, i32 0
%3 = load i8, i8* %2, align 1
%4 = and i8 %3, 1
%5 = icmp eq i8 %4, 0
%6 = getelementptr inbounds %Nullable.7, %Nullable.7* %0, i64 0, i32 1
%7 = load i64, i64* %6, align 8
%8 = select i1 %5, i64 %7, i64 %1
ret i64 %8
}
2ba180a to
fcd065d
Compare
fcd065d to
672c01c
Compare
|
The first commit only changes the field but keeps the constructor as it was, while the second also change the constructor. I think we want both for consistency, but separating them helps measuring the breakage it creates. Overall, very little changes are needed, which is promising as regards the broader ecosystem. The two most disruptive cases are:
Luckily, these two cases appear to be relatively rare. |
TotalVerb
left a comment
There was a problem hiding this comment.
Looks great. Since generated code for get has not changed, I suspect there will be no performance regressions (except possibly isnull, but that's unavoidable).
This is consistent with most other languages and formats.
This method is not currently documented, and the new form is more consistent with the new structure of the type.
672c01c to
32a08c7
Compare
|
I've run the benchmarks locally. As expected, they are slower with this PR if we keep the So now it remains to find out whether many packages would be broken by the second commit. I've made a PR against NullableArrays, and it wasn't too painful. I think most packages should work fine. @tkelman Could you please run PkgEval on this PR? Thanks! |
|
Sure. I messed something up in a rebase of my first attempt, running again now. |
|
Shouldn't be too surprised by this: CategoricalArrays, CSV, and SQLite are new failures. Git was a timeout, DynamicDiscreteModels is flaky. https://gist.github.com/7d1882b630429019667fece0cc82172f |
|
Great! These are all packages which were supposed to fail, and whose maintainers are in the loop. Will have a look. Cc: @quinnj |
|
Noted. Will this be backported? Or just on 0.6? |
|
No, just on 0.6. |
|
Great. Merge away. Should be a pretty easy compat. |
|
Should we merge this then? @johnmyleswhite? @davidagold? |
| end | ||
|
|
||
| Nullable{T}(value::T, isnull::Bool=false) = Nullable{T}(value, isnull) | ||
| Nullable{T}(value::T, hasvalue::Bool=true) = Nullable{T}(value, hasvalue) |
There was a problem hiding this comment.
We make use of this for null-safe operations where we can generate the value and hasvalue fields without branching.
This is consistent with most other languages and formats.
Fixes #18507.