in reply to Re^3: About List::Util's pure Perl shuffle()
in thread About List::Util's pure Perl shuffle()
sub bukNew ($) { my( $ref ) = @_; my @x = 0 .. $#$ref; @{ $ref }[ map splice( @x, rand @x, 1 ), @x ]; }
More and more interesting. But then, since all the other subs are (@), why not simply the following?
sub bukNew (@) { my @x = 0 .. $#_; @_[ map splice( @x, rand @x, 1 ), @x ]; }
However some random tests with various string and list lengths seem to show that it could be considerably slower than buk(), by even as much as 40% or so. Thus... are you sure your benchmark is not flawed?
|
---|
Replies are listed 'Best First'. | |
---|---|
Re^5: About List::Util's pure Perl shuffle()
by BrowserUk (Patriarch) on Jul 12, 2007 at 16:08 UTC | |
More and more interesting. But then, since all the other subs are (@), why not simply the following? blazar++. Just checking if anyone is still paying attention :) Since bukNew() doesn't need to copy the input list--because it shuffles, and therefore mutates, a list of indices generated internally, rather than mutating a copy of the input--it seemes silly to pass arrays in by value forcing their duplication. Most uses of shuffle() are applied to pre-existing arrays. That's why we make a copy of them, or create a list of aliases to them in most versions--simply to avoid the shuffle from modifying the external arrays. So, instead of
we can avoid some copying by using
And when we want to shuffle a list, instead of
we use
Inspired by blokhead's version, I went back to basics and tried benchmarking a simple version that took a reference rather thn a list:bukNew() with quite surprising results. Of course, the same lesson can now be retrofitted to those other routines that don't need to replicate the input list for their operation. In particular, blokhead's version! And here are the headline results of doing that (as blokhead_ref): Notice that the length of the strings is not a factor for blokhead(_ref) or bukNew!
The significant thing is that blokhead_ref moves ahead of blokhead very quickly as the number of elements increases. Avoiding copying the array pays dividends very quickly. However some random tests with various string and list lengths seem to show that it could be considerably slower than buk(), by even as much as 40% or so. Thus... are you sure your benchmark is not flawed? If you look back over the thread, you'll see that there have been various instances where different people have seen different results from benchmarking apparently the same code. Some of the differences are explained by whether the data being shuffled consists of all integer data (as in your original benchmark (0..1000 etc.), or string data as used by most people after ikegami noted the difference it makes. When an SV points to an IV, copying that SV is a faster operation than when it points to a PV. With the latter, a second memory allocation and memcpy operation have to be performed to copy the string data pointed at by the PV. If the SV contains integer (and probably float?), and has never been used in a string context, then the number will never have been ascii-ized and the PV will be null. That makes for significantly less work to copy it. That doesn't explain all the anomolies seen above though I think? Anyway, it's quite possible my benchmark is flawed--it certainly wouldn't be the first time :)--so here is the code. Tell me what you think? The benchmark code: Read more... (2 kB)
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
by blazar (Canon) on Jul 13, 2007 at 13:29 UTC | |
blazar++. Just checking if anyone is still paying attention :) Still paying attention and also advertising in clpmisc (link @ GG). So BrowserUk++ for the additional insight and the many details provided in this reply of yours. Since bukNew() doesn't need to copy the input list--because it shuffles, and therefore mutates, a list of indices generated internally, rather than mutating a copy of the input--it seemes silly to pass arrays in by value forcing their duplication. Most uses of shuffle() are applied to pre-existing arrays. That's why we make a copy of them, or create a list of aliases to them in most versions--simply to avoid the shuffle from modifying the external arrays. Well, but then that is somewhat obvious, and even the naive implementation would would be better apt at shuffling an array in place, and would thus better be rewritten like this:
(Not that this would make it really faster - I checked and it's still considerably slower than the alternatives presented here.) But then I think that in any case you would be comparing apples and bananas: for it was somewhat clear that the interface was in terms of "accept a list, return a (shuffled) list"... unless the interface itself was also part of the choice to take. OTOH your tests themselves show that blokhead's solution performs best on large datasets, which is exactly were speed is more likely to matter (well, not strictly, since one may have 10^6 shuffles of 10 elements long lists to do...) - and it has a more intuitive interface, so I would go for it. More precisely I have "decided" that my favourite version is the following modification of blokhead() that can be considered a "hybrid" with another version of yours:
I must say that I've included it in the benchmarks too and while it doesn't perform well compared to blokhead(), buk() and bukNew() in the short list tests, it is comparable with the best on the 1000 strings one, and in two of them it even performed best of all, although I'd rather consider this to be a fluctuation. Why this is so, anyway, is beyond my comprehension. In the meanwhile, I received a followup from Peter J. Holzer in clpmisc, which addresses my own post (a copy of the root node here) and I'm also reporting herafter in its entirey for completeness. Peter J. Holzer's replyRead more... (1348 Bytes) | [reply] [d/l] [select] |