Bitwise cleanup: Fix opSlice index error. Add unittests and changelog entry#5002
Bitwise cleanup: Fix opSlice index error. Add unittests and changelog entry#5002andralex merged 2 commits intodlang:masterfrom
Conversation
|
@wilzbach could you please have a look and make sure I did not miss any of your previous points? |
There was a problem hiding this comment.
Endianness
Ehm apparently no one mentioned that e.g. Intel's CPUs are Little-endian, so on my machine this means that core.bitop and bitwise don't work well together:
import core.bitop;
import std.range : bitwise;
auto arr = [3UL];
bt(arr.ptr, 1).writeln; // 1
bts(arr.ptr, 2);
arr[0].writeln; // 7
arr = [3UL]; // reset
auto r = arr.bitwise;
r[1].writeln; // false
r[2] = true;
arr[0].writeln; // 2305843009213693955Shouldn't the bitwise stream behave similarly to the BitRange or at least allow this behavior?
Performance
I have thrown a small benchmark together to show my previous point that recalculating the mask on every access is slow - the test is a simple count of set bits:
- the first example (
filter) is just to show the cost of the range abstraction as it usesfilterandsum - all other examples use a
foreachfor better comparability bitwise.foreachis thebitwisetaken from this PR (and thus Phobos)bitwise2is a simplerbitwisewith shifts the mask directly (in Big-endian and Little-endian variants)bitwise3usesBitRangeto calculate the positionsBitRangejust returns the positions of all set bits
> ldc -O5 -release -boundscheck=off bitarray.d bitwise.d && ./bitarray
bitwise.filter = 29 secs, 259 ms, 840 μs, and 9 hnsecs
bitwise.foreach = 24 secs, 668 ms, 420 μs, and 2 hnsecs
bitwise2Big.foreach = 16 secs, 778 ms, 923 μs, and 5 hnsecs
bitwise2Little.foreach = 16 secs, 849 ms, 760 μs, and 2 hnsecs
bitwise3.foreach = 21 secs, 905 ms, 458 μs, and 8 hnsecs
bitrange.foreach = 3 secs, 580 ms, and 864 μs
@wilzbach could you please have a look and make sure I did not miss any of your previous points?
Thanks a lot! Looks good, but I think you still have to think about some special cases in opSlice, have a look at this small snippet:
auto arr = only(4, 1);
writeln(arr[1..$]); // [1]
auto r = arr.bitwise;
r[29].writeln; // error
r[29..33].writeln; // error
auto r = 4.repeat.bitwise;
r[0..8].writeln; // error
std/range/package.d
Outdated
| import std.algorithm.comparison : equal; | ||
|
|
||
| auto rb = rndGen.bitwise; | ||
| assert(isInfinite!(typeof(rb))); |
| assert(isInfinite!(typeof(rb))); | ||
|
|
||
| auto rb2 = rndGen.bitwise; | ||
| // Don't forget that structs are passed by value |
There was a problem hiding this comment.
OT: which is why random ranges should never be allowed to be passed by value (it can lead easily to randomness errors), you may see e.g. the reference shell that Mir uses
See also this Dconf 2015 talk: http://dconf.org/2015/talks/wakeling.html
std/range/package.d
Outdated
| else static if (isBidirectionalRange!R) | ||
| { | ||
| bool isOverlapping = parent.empty; | ||
| if (!parent.empty) |
There was a problem hiding this comment.
how about:
if (parent.empty)
return true;
else
return parent.save.dropOne.empty && maskPos < backMaskPos;
It should. |
The 2.073 release is coming up soon (today is the scheduled feature freeze), so what do we with |
|
The endianess must definitely be fixed. |
|
This commit addresses only the endianess issue. I am now consuming the bits from lsb to msb. |
|
Yes, bit "zero" is traditionally LSB and bit 31 or 63 etc. is LSB. So it's expected we consume from lsb regardless of endianness. |
|
@wilzbach you lost me with the perf measurements. What is the tl;dr? I'm mostly confused because the code should be identical for the little and big endian machines - in all cases, consume from LSB through MSB. Performance is not a criteria for acceptance so it shouldn't stop this lest we spend the rest of the year getting the perfect shade. Perf improvements can always come later. And I'm not sure caching the mask is a good idea anyway. Generally: all other things being approximately equal, a little computation is to be preferred to a little extra state to maintain. |
std/range/package.d
Outdated
| else static if (isBidirectionalRange!R) | ||
| { | ||
| bool isOverlapping = parent.empty; | ||
| if (!parent.empty) |
std/range/package.d
Outdated
| static if (isBidirectionalRange!R) | ||
| { | ||
| len -= (backMaskPos - 1); | ||
| len -= (bitsNum - backMaskPos); |
Recalculating the mask isn't the most performant way:
I just included it because
Yes, but
edit: It would be interesting to use |
| as a user-friendly way to query for function attributes.)) | ||
| $(LI $(RELATIVE_LINK2 bitwise, Added `std.range.bitwise` to create | ||
| a bitwise adapter over an integral type range, consuming the range elements | ||
| bit by bit.)) |
There was a problem hiding this comment.
The changelog.dd wasn't removed yet, but it will be soon.
Could you please create a file in the new changelog folder? This will also end merge conflicts with the changelog file.
|
Cool, @wilzbach you may want to now add the bitmask caching. Thx! |
|
Another issue: |
This PR brings the following:
changelog.dd. Now there are only spaces.