-
Notifications
You must be signed in to change notification settings - Fork 27
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
added string radix sort and sortperm + tests #27
Conversation
src/string_radix_sort.jl
Outdated
The default is `UInt`, so on a 64 bit machine it loads 64 bits (8 bytes) at a time. | ||
If the `String` is shorter than 8 bytes then it's padded with 0. | ||
|
||
Assumes machine is little-endian |
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.
Is that true even with the ntoh
calls?
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.
It's not true. Have removed
src/string_radix_sort.jl
Outdated
return ntoh(unsafe_load(Ptr{T}(pointer(s, skipbytes+1)))) | ||
else | ||
ns = (sizeof(T) - min(sizeof(T), n - skipbytes))*8 | ||
h = unsafe_load(Ptr{T}(pointer(s, skipbytes+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.
Couldn't this segfault if you read past the end of the string? e.g. if the string ends on a page boundary and the next page is inaccessible?
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.
made a segfault safe version
src/string_radix_sort.jl
Outdated
h = unsafe_load(Ptr{T}(pointer(s, skipbytes+1))) | ||
h = h << ns | ||
h = h >> ns | ||
return ntoh(h) |
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.
Wouldn't it be better to do return (ntoh(unsafe_load(...)) >> ns) << ns
, i.e. do ntoh
first and flip the order of the shifts, so that it still works on bigendian machines?
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.
yep. but supplanted by the segfault safe version now
src/string_radix_sort.jl
Outdated
if lo >= hi; return svec; end | ||
|
||
# find the maximum string length | ||
lens = reduce((x,y) -> max(x,sizeof(y)),0, svec) |
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.
Just use maximum(sizeof, svec)
src/string_radix_sort.jl
Outdated
- `s`: a `String` | ||
- `skipbytes`: how many bytes to skip e.g. load_bits("abc", 1) will load "bc" as bits | ||
""" | ||
load_bits(s::String, skipbytes = 0) = load_bits(UInt, s, skipbytes) |
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.
Not clear why this method is needed, since you always pass a type to load_bits
below
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.
have removed
src/string_radix_sort.jl
Outdated
# are all values the same at this radix? | ||
if bin[idx,j] == len; continue; end | ||
|
||
cbin = cumsum(bin[:,j]) |
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 use cumsum!
with a preallocated array. Note that bin[:,j]
also allocates a copy. You might want to just write a loop to do the sum with no copies.
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.
done
src/string_radix_sort.jl
Outdated
Currently radixsot | ||
""" | ||
function sortperm_radixsort(svec::AbstractVector{String}; rev::Union{Bool,Void}=nothing, order::Ordering=Forward) | ||
siv = StringIndexVector(copy(svec), collect(1:length(svec))) |
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.
Usually you would define a sortperm_radixsort!
function that doesn't make a copy of svec
, and then define sortperm_radixsort(svec, ...) = sortperm_radixsort!(copy(svec), ...)
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.
done
The speed advantage of radixsort vs Base.sort seems to have vanished after implementing the segfault safe version. The performance is so bad that it's better to wait till I come up with a performant version. |
src/string_radix_sort.jl
Outdated
|
||
function load_bits_with_padding(::Type{UInt128}, s::String, skipbytes = 0) | ||
n = sizeof(s) | ||
T = UInt128 |
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'm not sure the compiler is smart enough to constant-fold a type that you assign to a local variable like this. Does it improve performance if you just paste UInt128
directly in the code?
(I think it should be possible to write this function in a way that works for all T
simultaneously with no penalty by making sure the branches get optimized out, but easier to optimize the case for a single T
first.)
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.
Ok, I will just replace wiht
Actually I think a macro will work better here; just generate the code for all types. It's basically the same anyway. Might look into that.
I actually initial I have one function for all types. But it keeps on crashing. I think it has to do with the Base.zext_int(UInt64, ::UInt128)
which doesn't make sense; even that branch will never be reached, the compiler still complained and crashed the program every time.
src/string_radix_sort.jl
Outdated
if o == Reverse | ||
sorttwo!(.~load_bits.(UInt128, svec, skipbytes), svec) | ||
else | ||
sorttwo!(load_bits.(UInt128, svec, skipbytes), svec) |
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.
Note that this allocates arrays over and over; would be better to allocate a bits
array once and overwrite it in-place with bits .= load_bits.(...)
more comprehensive test cases to come to test all branches |
minor = removed lexicographic ordering as it's not longer in 0.7
Bump! String radix sort is 3x faster than base sort and passes all tests. using SortingAlgorithms
x = rand([randstring(8) for i=1:1_000_000], 100_000_00);
@time sort(x) |> issorted #11s
@time sort(x, alg=StringRadixSort) |> issorted # 3.7s |
Any reason why this cannot be just the |
@nalimilan I tried to use But the real issue is |
Sorry, I'm not familiar with that machinery, but these implementation issues should definitely be fixed in some way. It would be too bad to have |
I think a way to solve it is some kind of method-piracy. Can overload the |
Hmm, that wouldn't be great. Maybe we need to change something in Base so that it's possible to dispatch on the algorithm? |
I can't find it now but I was involved in a discussion, basically, either base adds in a |
Maybe JuliaLang/julia#24770? Anyway, I'd say it would be fine to make a PR against Base to add |
I recall Chris R saying that the real fix is to dispatch on named arguments... can't find that discussion now. |
You mean on keyword arguments? That won't happen in 1.0, and maybe never, so we'd better find a solution until then... :-) |
Could you file an issue in Base to discuss the problem with |
Is this something we want to try and get merged? |
""" | ||
sorttwo!(vs, index) | ||
|
||
Sort both the `vs` and reorder `index` at the same. This allows for faster sortperm |
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 sounds similar to what I experimented at #33.
Why close this? Can we try to find a way forward by discussing the needed changes in Base? |
I have added StringRadixSortAlg so that (LSD) radix sort can be applied to strings