Fix Issue 18223: use memset in uninitializedFillDefault(T[])#6024
Fix Issue 18223: use memset in uninitializedFillDefault(T[])#6024dlang-bot merged 1 commit intodlang:masterfrom
Conversation
|
Thanks for your pull request and interest in making D better, @n8sh! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla references
Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + phobos#6024" |
| zero bits? Padding between a struct's fields is not considered. | ||
| +/ | ||
| private template isAllZeroBits(T, T value) | ||
| { |
There was a problem hiding this comment.
Is there a more elegant way than this to check if T.init is entirely zero bits? typeid(T).initializer() is null isn't available.
std/experimental/allocator/common.d
Outdated
| foreach (i; 1 .. N) | ||
| buffer ~= " && isAllZeroBits!(E,value["~to!string(i)~"])"; | ||
| return buffer; | ||
| }()); |
There was a problem hiding this comment.
Is there a more elegant way to check all the elements of a static array?
std/experimental/allocator/common.d
Outdated
| ~"), value."~fieldName~")"; | ||
| } | ||
| return buffer; | ||
| }()); |
There was a problem hiding this comment.
Is there a more elegant way to check all the fields of a struct?
554a6f2 to
bbf8757
Compare
std/experimental/allocator/common.d
Outdated
| buffer ~= " && isAllZeroBits!(E,value["~to!string(i)~"])"; | ||
| return buffer; | ||
| }()); | ||
| } |
There was a problem hiding this comment.
Is there a more elegant way to check all the elements of a static array?
There was a problem hiding this comment.
Your version in "elegant":
enum isAllZeroBits = ()
{
int b;
static foreach (e; value)
b += !isAllZeroBits!(typeof(e), e);
return b == 0;
}();There was a problem hiding this comment.
And I think we can do even better here:
enum isAllZeroBits = isAllZeroBits!(typeof(value[0]), value[0]);Static arrays need to have the same type, don't they?
There was a problem hiding this comment.
That works only when the static array is the outermost type and not a field of another struct . That optimization could go in isInitAllZeroBits though.
7f6c48c to
53c3865
Compare
std/experimental/allocator/common.d
Outdated
| enum isAllZeroBits = mixin(() | ||
| { | ||
| enum N = typeof(value).length; | ||
| char[] buffer = new char[32 * value.length]; // Pre-size for value.length <= 999. |
There was a problem hiding this comment.
This doesn't reserve the size. On append the slice is not the tail so it wil reallocate.
There was a problem hiding this comment.
Thanks for the correction! reserve isn't available at compile time so I'll just remove this.
ed54668 to
4cfb52e
Compare
|
EDIT: Found the way using tupleof. |
bdfe933 to
5a0ef22
Compare
| import core.stdc.string : memset; | ||
| if (array !is null) | ||
| memset(array.ptr, cast(ubyte) T.init, T.sizeof * array.length); | ||
| return array; |
| }()); | ||
| } | ||
| else | ||
| enum isAllZeroBits = false; |
There was a problem hiding this comment.
The style guide demands that once braces are used for an if statement that they are used for all cases.
std/experimental/allocator/common.d
Outdated
| char[] buffer; | ||
| buffer ~= "isAllZeroBits!(E,value[0])"; | ||
| import std.conv : to; | ||
| foreach (i; 1 .. N) |
There was a problem hiding this comment.
Because it could have been either I avoided it due to seeing this advice on the forums:
When you can use the old style do so. Since it puts less stress on the compiler in the general case.
There was a problem hiding this comment.
That's bad advice. There isn't any measurable performance penalty - I tried a large-scale test: #5989
std/experimental/allocator/common.d
Outdated
| enum isAllZeroBits = mixin(() | ||
| { | ||
| enum N = typeof(value).length; | ||
| char[] buffer; |
There was a problem hiding this comment.
could be string - you don't modify individual chars.
std/experimental/allocator/common.d
Outdated
| buffer ~= " && isAllZeroBits!(E,value["~to!string(i)~"])"; | ||
| return buffer; | ||
| }()); | ||
| } |
There was a problem hiding this comment.
Your version in "elegant":
enum isAllZeroBits = ()
{
int b;
static foreach (e; value)
b += !isAllZeroBits!(typeof(e), e);
return b == 0;
}();
std/experimental/allocator/common.d
Outdated
| if (buffer.length == 0) | ||
| buffer ~= "true"; | ||
| return buffer; | ||
| }()); |
There was a problem hiding this comment.
How about:
static if (value.tupleof.length == 0)
{
enum isAllZeroBits = true;
}
else
{
enum isAllZeroBits = ()
{
int b;
static foreach (e; value.tupleof)
b += !isAllZeroBits!(typeof(e), e);
return b == 0;
}();
}
std/experimental/allocator/common.d
Outdated
| buffer ~= " && isAllZeroBits!(E,value["~to!string(i)~"])"; | ||
| return buffer; | ||
| }()); | ||
| } |
There was a problem hiding this comment.
And I think we can do even better here:
enum isAllZeroBits = isAllZeroBits!(typeof(value[0]), value[0]);Static arrays need to have the same type, don't they?
8038986 to
779cf53
Compare
wilzbach
left a comment
There was a problem hiding this comment.
const(char)[] (aka string) is a typical use case, so I would add tests for arrays with const elements.
std/experimental/allocator/package.d
Outdated
| memset(array.ptr, 0, T.sizeof * array.length); | ||
| return array; | ||
| } | ||
| else static if (is(T : char) || (is(T : wchar) && T.init == 0xffff)) |
There was a problem hiding this comment.
-
What's the reason for excluding
dcharhere? -
Shouldn't the type classifiers be removed? E.g.
- [
Unqual]((https://dlang.org/phobos/std_traits.html#Unqual) - isSomeChar - doesn't allow
alias this
- Also the
T.init == 0xffffcheck is to support user-extended versions of (char) that don't overwrite theinitvalue, right? This should probably get a test too.
There was a problem hiding this comment.
-
dchar.sizeof == 4 && dchar.init == 0x0000_ffff. Can't use memset. -
static assert(is(immutable(char) : char))compiles so I don't know that Unqual is necessary. I hadn't given much though toalias this. What do you suggest? -
Since
char.sizeof == 1memset will work regardless of what char.init is. Sincewchar.sizeof == 2memset will work if the high 8 bits are the same as the low 8 bits; the main purpose of theT.init == 0xffffcheck was reminding the casual reader that is is true for wchar. Maybe0 == (T.init & 0xff) ^ (T.init >>> 8)would be better?
| // Has non-zero private fields. | ||
| import std.random : Mt19937; | ||
| static assert(!isInitAllZeroBits!Mt19937); | ||
| } |
There was a problem hiding this comment.
It might be worthwhile to check whether this works with const.
ConstOf might be helpful.
| static assert(isInitAllZeroBits!P); | ||
| P[] a = [P(10, 11), P(20, 21), P(40, 41)]; | ||
| uninitializedFillDefault(a); | ||
| assert(a == [P.init, P.init, P.init]); |
There was a problem hiding this comment.
Needs tests for const, e.g
static struct S { float x = 0; float y = 0; }
static foreach (e; ["mutable", "non-mutable"])
{{
static if (e == "mutable")
alias P = S;
else
alias P = ConstOf!S;
static assert(isInitAllZeroBits!P);
P[] a = [P(10, 11), P(20, 21), P(40, 41)];
uninitializedFillDefault(a);
assert(a == [P.init, P.init, P.init]);
}}fails with:
std/experimental/allocator/package.d(929): Error: function core.stdc.string.memset(return scope void* s, int c, ulong n) is not callable using argument types (const(S)*, int, ulong)
std/experimental/allocator/package.d(994): Error: template instance std.experimental.allocator.uninitializedFillDefault!(const(S)) error instantiating
There was a problem hiding this comment.
Shouldn't trying to write to const without casting const-ness away result in compilation failure?
EDIT (x2): When this function is used in makeArray const-ness is cast away by the caller. So I think not removing const-ness inside uninitializedFillDefault is consistent with the current design.
phobos/std/experimental/allocator/package.d
Lines 1453 to 1454 in c7c83e6
std/experimental/allocator/common.d
Outdated
| package template isInitAllZeroBits(T) | ||
| { | ||
| import std.traits : isStaticArray; | ||
| static if (isStaticArray!T) |
There was a problem hiding this comment.
I would use a more generic trait: static if (T.sizeof == 0).
5f9a7c6 to
6fb050c
Compare
a072bf8 to
fc69535
Compare
|
Improvement codes, loop should not be continue if isAllZeroBits return false.
|
|
|
|
@apz28 I've fixed the issue you raised. |
fc69535 to
192220f
Compare
6296021 to
b8c2b01
Compare
|
Putting this on the merge queue (if no objections are raised in the next three days, this can be merged). |
Applies to code that was removed.
|
BTW @n8sh we have this semi-official Slack channel that we often for some quick discussion. I couldn't find your email address, so if you want to join, just send me an email ;-) |
| static assert(isAllOneBits!(char, 0xff)); | ||
| static assert(isAllOneBits!(wchar, 0xffff)); | ||
| static assert(isAllOneBits!(byte, cast(byte) 0xff)); | ||
| static assert(isAllOneBits!(int, 0xffff_ffff)); |
There was a problem hiding this comment.
I would prefer to see tests for all integer types here just to be sure.
d52186b to
aa8f45a
Compare
When we can statically determine that the representation of T.init consists of nothing but zeroes or nothing but ones we use memset. (The second case occurs for char and wchar.)
aa8f45a to
8777cbf
Compare
Current function in std.experimental.allocator.package:
When we can statically determine that the representation of T.init consists of nothing but zeroes we can instead use memset. char and wchar can also be special-cased.