-
Notifications
You must be signed in to change notification settings - Fork 33
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
WIP: Better copyto #172
WIP: Better copyto #172
Conversation
4cb7fef
to
ff5d841
Compare
ff5d841
to
ff08d35
Compare
720b24d
to
b4e8089
Compare
@nalimilan this is WIP, so let me work on it a bit (I have to dig through all the tests as they require significant changes because if ordered is The implementation I proposed for |
I am going through the tests (hopefully I will update all today and push relevant changes) and here are two additional comments:
|
Thanks! That inconsistency between But maybe we could change that? It would make sense for |
end | ||
else | ||
if seen[s] | ||
drefs[dstart+i] = remap[s] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't you just use remap[s] == -1
as a sentinel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I have done first, but allocating seen
should be cheap, and the problem is that remap
, in order to be fast, should have the same eltype as drefs
and they are unsigned.
At least this is what I thought. Now I see that we can use remap[s] == 0
as sentinel, as we use 0
for missing which is handled in other way anyway. I will change this
end | ||
end | ||
end | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure we need to duplicate all of this code for performance? Branch prediction is usually quite efficient, so even if you have an if
inside the loop I suspect it won't make a big difference. And often LLVM will do the hoisting automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have to do isordered
check in the innermost part of the loop. I can check if compiler can optimize this out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there's already a branch I don't think it will matter, even if the compiler doesn't hoist it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but we have to do isordered
test in every step of the loop. If it is not optimized out then it will be much slower. I will check.
@static if VERSION >= v"0.7.0-DEV.3208" | ||
using Future | ||
Future.copy!(dest::CatArrOrSub, src::AbstractArray) = | ||
copyto!(dest, 1, src, 1, length(src)) | ||
Future.copy!(dest::CatArrOrSub, src::CatArrOrSub) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method isn't needed now AFAICT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you want to happen if we want to copy!
a normal array to a categorical array? Throw an error?
for i = 1:len2 | ||
A[len + i] = B[i] | ||
end | ||
# As in Base, A will be left modified if it is not possible to copy B to A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder why Base doesn't call resize!
in case of failure. That sounds trivial, undoing the operation is not as hard as in copyto!
. But better stick to what Base does, and maybe file an issue in Julia.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the reason is that you would have to do try-catch which will slow down the function? I will open an issue with Base.
x = categorical([1:255;1:255], true) | ||
y = categorical([1, 1000], true) | ||
@test copyto!(x, 1, y, 1, 1)[1] == y[1] | ||
@test_throws ErrorException copyto!(x, 1, y, 1, 1)[2] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better check the length? Actually, better check the whole contents of the result (same for the line above).
@test_throws ErrorException copyto!(x, 1, y, 1, 1)[2] | ||
|
||
x = categorical([1,2,3]) | ||
y = categorical([5,6,7]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better use unsorted values to really test that the order is preserved.
copyto!(x,y) | ||
@test levels(x) = [1,2,3,5,6,7] | ||
|
||
@test copy!(x, [1,1,1]) == [1,1,1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not also check levels? Also with different unsorted values? Same below.
Co-Authored-By: bkamins <bkamins@sgh.waw.pl>
Co-Authored-By: bkamins <bkamins@sgh.waw.pl>
This is what I see in the code. And that is why I am asking, because the issue is complex (in general - working on those things taught me that things that I initially see as simple are not). So we have two options:
I can implement both of them with equal speed in Option 2 is very tempting conceptually. The problem with it is that what we merge might have conflicting orderings (but I guess we should treat it as a corner case and just go with your implementation)? |
If you have the time and motivation, it would be interesting to investigate whether it would be possible to have |
This is what I intended. I really like to have things clean. |
@nalimilan - sorry to bother you again. I have started working on the order-preserving Can you please explain me the logic behind the following output?
I do not understand why you put |
No idea. I admit I would have expected |
I will design such an algorithm, but this is a longer process to make it efficient and consistent for: Therefore, because of the complexity we face, I will close this PR and close #170 and open issues/PRs that are split into smaller chunks of changed functionality. |
I am thinking about this issue for some time. The first conclusion is that we have to have a list of methods we should hande. This is what I have collected:
The key issue is that in general it is impossible to implement The general logic for all methods should be:
There are then multiple corner cases but the main is:
Currently with The difference in the approach manifests itself e.g. when doing The thing is that if we do not have common guiding principles then e.g. To understand what I mean have a look at this example (this is one of many possible examples):
So in short - I think we first need a principle we want to follow and then reimplement all the methods from grounds-up to follow it. Unfortunately it seems to be a large task, but - if we want to provide a solid base for the future - I would try to do it. |
Unfortunately I don't think we can fix Regarding whether we should (try to) add all levels or only those actually used, I think we should do the former, because it's not always possible to efficiently check which levels are used (you basically need to make an additional pass over the data). That's one of the reasons why we keep unused levels in the first place (the other being that levels are supposed to have a meaning by themselves, independent from the data). |
Then I would leave
I will go back to this when I am more or less finished with DataFrames.jl stuff ( |
TODO (from Slack):
which currently throws an error |
I will test it after #211 is merged and tagged. |
There is still a problem that:
throws an error. Also before we close this please confirm that this is intended:
|
I don't think CategoricalArrays handle
The Nothing to change in |
If this:
is intended then I think we are OK and this can be closed. Can I leave opening of the other specific issues you have indicated to you as I feel you understand better what is the core problem with them? |
just for a reference continuing the last example, we have:
so we are consistent. |
Actually I've realized it's covered by #170. |
OK - so this can be closed then. Right? |
I guess. I've filed #213 to fix the |
PR following the discussion in #170.
What does it change:
append!
usesresize!
andcopyto!
which will make it faster, but when it fails it does not clean upcopy!
will allowsrc
to be non-categorical;dest
is array or a view; in this implementation levels are retained and also ifdest
is orderedcopy!
might failcopyto!
:dest
is ordered do not allow adding new levelsdest
is not ordered only add levels that are present insrc
in copied range (not all levels insrc
- this makes sure that ifsrc
is categorical and not categorical holding the same values assrc
we get the same result)EDIT
The general idea of the design is that if
x
is categorical andy
is a non-categorical then:should be exactly the same as