-
-
Notifications
You must be signed in to change notification settings - Fork 739
Fix issue 11598 - uniform could be faster for integrals #1717
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
Conversation
These changes retain the uniform quality of this function while, in the common case, cutting the number of division operations in half. Additionally, extra unittests were added to assure that all of the bounds were tested properly.
Please write up some test code (not unittests, it's too heavy; just test code for the sake of this pull request) to simulate a few different cases of rolling uniform(0, n) a sufficient number of times (say, n * 10 million or n * 100 million) to confirm that each rollable value has no systematic bias. Do it for n = 2, 3, ..., 10, 20, 50, 100, and post the results here and in the bug report. Sorry if it seems like I'm being fussy or doubting you, but there have been bugs in the past in std.random that would never have got there if the developer responsible had done a simple test like this before submitting a pull request. Obviously serious statistical tests-of-randomness are a bit too heavy for now, but this I think is reasonable to ask. |
It's a fair request to have documented rigor for such an important function in std.random. I'll document my entire process here. For tests doing what you describe, refer to this gist: https://gist.github.com/Zshazz/1bffebf9cb7cb97cfabc One thing to note, knowing the implementation for this change (a bit of information on the idea behind the implementation is at the end of this post), is that this wouldn't be sufficient for detecting all of the potential problems. For instance, if you used a bad uniform implementation with just In that case, there would be So, let's define Now, let's define Here is the code and results from running the code for these two tests using the new uniform and a bad uniform: https://gist.github.com/Zshazz/450f57a5374bf0b80fb5 So all that said, these tests aren't necessarily going to cover every possible question about statistical issues that this new uniform could bring up, either. Hence a general argument for correctness is probably a better approach. For a complete description, I refer to my document on my dropbox: https://dl.dropboxusercontent.com/u/2206555/uniformUpgrade.pdf Because it's possible (and likely eventually) that this document will no longer appear on my dropbox, I'll reproduce the most important information here (not exactly what is in the document, but close enough): The modulus operator maps an integer to a small, finite space. For instance, (Non-negative is important in this case because some definitions of modulus, namely the one used in computers generally, map negative numbers differently to (-3 .. 0]. The issue with computers is that integers have a finite space they must fit in, and our uniformly chosen random number is picked in that finite space. So, that method is not sufficient. You can look at it as the integer space being divided into "buckets" and every bucket after the first bucket maps directly into that first bucket. So, the answer is to simply "reroll" if you're in that last bucket, since it's the only unfair one. Eventually you'll roll into a fair bucket. Simply, instead of the meaning of the last bucket being "maps to To generalize, We'll first try to do the mapping into the first bucket by doing If we start at
If the bucket starts any later, then it must have lost at least one number and
Hence, our condition to reroll is OK, I think that about covers everything I've done on this. I hope that wasn't too much, but I did want to document the work put into developing this. |
w/in the comment history of a github pull is a horrible place for documentation. It belongs as ddoc comments w/in the source itself so that it becomes part of the generated documentation, or as a separate article referenced by the source documentation. |
Should that really be part of the ddoc? It's more of implementation information, not necessarily something you need to know in order to use it. I could add it as internal documentation. That probably wouldn't be a bad idea since for the original uniform I wasn't entirely sure what the mental model was. |
I think internal documentation is fine. |
} | ||
|
||
assert(upperDist != 0); | ||
if (upperDist == 1) return lower; |
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 this test really necessary? Seems like a slowdown for the general case?
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 really. Without the check, the math still works. I think the only reason I had that line is because it was in the previous uniform
(but that's not a good reason to keep it). I'll remove it in the next push. That gives a consistent ~3% boost in performance to the benchmark mentioned in the introductory post of the PR with Xorshift
, which is certainly nice.
@WebDrake : LGTM, mostly, but I'm no PRNG expert. Does this also look good to you? |
A special case was checked that only occurs when calling uniform with a range of only one element. This was not necessary to keep the math correct and represented a slight slowdown in the common case for no apparent benefit.
@monarchdodra @Zshazz Can I have 24 hours to really go over it in detail? @Zshazz any reference works you can point me to that are relevant to your implementation here? I'm sure the code is fine, I just want to make sure that I really, really understand it first. |
I have no problem with you taking some time on it (I recognize that this needs very careful consideration). I don't have any reference information for it, but I don't think I'm particularly clever, so it probably exists out there. I'll look around and see if I can find anything that looks similar. The best I can do now is recommend you read uniformUpgrade.pdf/the documentation above, but I'm certainly not the best writer. If I find anything similar, I'll post it here. |
Well, here's something interesting: Java uses this same technique. The difference is that it overflows as opposed to the method here which doesn't rely on overflow behavior. It doesn't help with understanding, but it provides some precedence for this approach. It also special-cases powers of two for a bit of a performance boost in that circumstance. |
@WebDrake : LGTM. Anything to add? |
I didn't manage to make the time to go over it in depth as I'd wanted, but I have no reason to hold it up. So let's go, if later I come up with an objection (unlikely!) we can always revert or revise. |
Fix issue 11598 - uniform could be faster for integrals
Thankyou for this commit. |
Just to note, this patch is responsible for an observable change in the sequences of integers generated by calls to
With the earlier With (admittedly fairly basic and ad-hoc) tests I don't see any sign of bias in the new |
More seriously, I discovered this
I'm writing up a bug report on this now. |
Update: it looks like it's a general problem of how lower bounds have been dealt with, not specific to this particular implementation of |
Issue filed: https://issues.dlang.org/show_bug.cgi?id=12835 |
This corresponds to the changes made in phobos pull request dlang/phobos#1717
See:
https://d.puremagic.com/issues/show_bug.cgi?id=11598
http://forum.dlang.org/thread/jnu1an$rjr$1@digitalmars.com
These changes retain the uniform quality of this function while, in the common case, cutting the number of division operations in half.
Additionally, extra unittests were added to assure that all of the bounds were tested properly.
Example benchmark:
Results on my machine:
Old
uniform
(usingXorshift
): 686msNew
uniform
(usingXorshift
): 451msNew
uniform
(usingMt19937
): 597msSo, as you can see, this new
uniform
is significantly faster than the old. In code that uses the integraluniform
heavily (for instance,randomShuffle
) it's possible to use the higher qualityMt19937
and still be faster than the olduniform
using the much fasterXorShift
.