Thread overview | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
December 27, 2020 opIndexMember | ||||
---|---|---|---|---|
| ||||
In some cases a structure of arrays is more desirable than an array of structs, usually for performance reasons, but maybe you want tight packing or something else, so I have been thinking a while about proposing the following... auto opIndexMember!(string what)(size_t idx); So basically if opIndexMember is defined for Foo then... foo[i].x Is rewritten to.. foo.opIndexMember!("x")(i); So you can do stuff like this.... struct Vector { float x,y,z; } struct VecArray { float[] _x; float[] _y; float[] _z; auto opIndexMember(string what)(size_t idx) { static if (what = "x") return _x[idx]; else static if (what = "y") return _y[idx]; else static if (what = "z") return _z[idx]; else static if (what = "magitude") return sqrt(sqr(_x[idx])+sqr(_y[idx])+sqr(_z[idx])); else static if (what = "") return Vector(_x{idx], _y[idx], _z[idx]); static assert(0); } } So you can use that either as a structure of arrays, or an array of structs. And with introspection and UDAs you could probably have an array class that automatically enumerates members of the struct, stores as SOA internally, but provides a AOS API. Im thinking maybe disallow having both opIndex and opIndexMember defined, if you use array op but with no trailing member it just passes an empty string. Might reduce ambiguity? |
December 27, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to claptrap | On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote:
> So you can do stuff like this....
What's wrong with this?
```
struct VecArray
{
float[] _x;
float[] _y;
float[] _z;
Vector opIndex(size_t idx) {
return Vector(_x[idx], _y[idx], _z[idx]);
}
}
```
Performance in debug builds?
|
December 27, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dennis | On Sunday, 27 December 2020 at 15:02:16 UTC, Dennis wrote: > On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote: >> So you can do stuff like this.... > > What's wrong with this? > ``` > struct VecArray > { > float[] _x; > float[] _y; > float[] _z; > > Vector opIndex(size_t idx) { > return Vector(_x[idx], _y[idx], _z[idx]); > } > } > ``` > > Performance in debug builds? It wont work with ref returns, and performance in release builds. I had some instances where the return type was being fully constructed even though only one member was accessed, but I dont remember exactly what code caused that. godbolt with LDC -03... =============================================== import std; struct Vector { float x,y,z; } struct VecArray { float[] _x; float[] _y; float[] _z; Vector opIndex(size_t idx) { return Vector(_x[idx], _y[idx], _z[idx]); } } float doSomething1(ref VecArray array) { return array[0].x * 2.0; } float doSomething2(ref VecArray array) { return array._x[0] * 2.0; } ============================================== doSomthing1: float example.doSomething1(ref example.VecArray): push rax cmp qword ptr [rdi], 0 je .LBB6_4 cmp qword ptr [rdi + 16], 0 je .LBB6_4 cmp qword ptr [rdi + 32], 0 je .LBB6_4 mov rax, qword ptr [rdi + 8] movss xmm0, dword ptr [rax] addss xmm0, xmm0 pop rax ret .LBB6_4: lea rsi, [rip + .L.str.1] mov edi, 11 mov edx, 12 call _d_arraybounds@PLT =============================================== doSomthing2: float example.doSomething2(ref example.VecArray): push rax cmp qword ptr [rdi], 0 je .LBB7_2 mov rax, qword ptr [rdi + 8] movss xmm0, dword ptr [rax] addss xmm0, xmm0 pop rax ret .LBB7_2: lea rsi, [rip + .L.str.1] mov edi, 11 mov edx, 23 call _d_arraybounds@PLT |
December 27, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to claptrap | On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote: > In some cases a structure of arrays is more desirable than an array of structs, usually for performance reasons, but maybe you want tight packing or something else, so I have been thinking a while about proposing the following... What about this: ``` import std.math; struct Struct { float[] x; float[] y; float[] z; this(float[] x, float[] y, float[] z) { this.x=x; this.y=y; this.z=z; } float[3] opIndex(int index) { return [x[index],y[index],z[index]]; } } float x(float[3] array) { return array[0]; } float y(float[3] array) { return array[1]; } float z(float[3] array) { return array[2]; } float magnitude(float[3] array) { return sqrt(sqr(array[0])+sqr(array[1])+sqr(array[2])); } float sqr(float x) { return x*x; } int main() { import std.stdio; Struct s = Struct([1,2],[3,4],[5,6]); writeln(s[0].x); writeln(s[0].y); writeln(s[0].z); writeln(s[0].magnitude()); return 0; } ``` |
December 27, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to sighoya | On Sunday, 27 December 2020 at 17:34:44 UTC, sighoya wrote:
> On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote:
>> In some cases a structure of arrays is more desirable than an array of structs, usually for performance reasons, but maybe you want tight packing or something else, so I have been thinking a while about proposing the following...
>
>
> What about this:
Codegen is better, but doesnt handle ref parameters, wont handle different types, maybe it's not just 3 floats, maybe a bool, or an enum too.
|
December 27, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to claptrap | On Sunday, 27 December 2020 at 17:01:47 UTC, claptrap wrote: > It wont work with ref returns, and performance in release builds. What if you used pointers instead? Maybe it can be optimized when you make opIndex return a structure like this: > struct VecArrayIndex { > VecArray* vecArray; > size_t index; > ref float x() {return vecArray.x[index];} > ref float y() {return vecArray.y[index];} > } I'm not trying to annoy you with awkward workarounds, I see what you're going for. I just really don't want operator overloading to become more complex than it already is. It's already confusing having opIndex, opSlice, opSliceUnary, opAssign, opIndexAssign, opIndexOpAssign, opIndexUnary etc. And then there's still some cases missing that the compiler uses for internal types that can't be expressed in library types: - multiple array concatenation without multiple re-allocations, e.g. `a ~ b ~ c` gets rewritten to a single call. - for associative arrays, opIndex followed by opIndexAssign, e.g. for an `int[string][string]` doing `aa["newKey"]["anotherNewKey"] = 1` does not give a range violation for `aa["newKey"]`. So there's good rationale for adding solutions to that so library solutions can replace compiler magic. Now you propose, opIndexMember, but that's just the beginning of even more possible combinations: next you'll need opIndexMemberAssign, opIndexMemberOpAssign, and who knows what. So that's why I want to fix / improve operator overloading without adding any more cases if it can be helped. |
December 28, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dennis | On Sunday, 27 December 2020 at 23:06:47 UTC, Dennis wrote: > On Sunday, 27 December 2020 at 17:01:47 UTC, claptrap wrote: >> It wont work with ref returns, and performance in release builds. > > What if you used pointers instead? Maybe it can be optimized when you make opIndex return a structure like this: > >> struct VecArrayIndex { >> VecArray* vecArray; >> size_t index; >> ref float x() {return vecArray.x[index];} >> ref float y() {return vecArray.y[index];} >> } That gives identical codegen to just accessing the arrays directly, so codegen is as good as it can be! > I'm not trying to annoy you with awkward workarounds, I see what you're going for. I just really don't want operator overloading to become more complex than it already is. It's already confusing having opIndex, opSlice, opSliceUnary, opAssign, opIndexAssign, opIndexOpAssign, opIndexUnary etc. And then there's still some cases missing that the compiler uses for internal types that can't be expressed in library types: To be honest I expected a bit more enthusiasm for the idea but Im not going to be upset or lose any sleep over it. I already had a work around, just that workarounds are not solutions, they avoid the problem rather than solving them. So I though it would be a nice addition to make SOA vs AOS easier to implement and easier for the compiler to produce optimal code. I hadnt thought of the consequences of it spawning other op overloads. |
December 28, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to claptrap | On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote: > So basically if opIndexMember is defined for Foo then... > > foo[i].x > > Is rewritten to.. > > foo.opIndexMember!("x")(i); For an alternative solution see: https://github.com/nordlow/phobos-next/blob/master/src/nxt/soa.d No benchmarks yet, tough, but you're very welcome to add them. |
December 29, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to Per Nordlöw | On Monday, 28 December 2020 at 20:15:27 UTC, Per Nordlöw wrote:
> On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote:
>> So basically if opIndexMember is defined for Foo then...
>>
>> foo[i].x
>>
>> Is rewritten to..
>>
>> foo.opIndexMember!("x")(i);
>
> For an alternative solution see:
> https://github.com/nordlow/phobos-next/blob/master/src/nxt/soa.d
>
> No benchmarks yet, tough, but you're very welcome to add them.
Hi, that works perfectly, and in godbolt, LDC -O3, identical codegen to just accessing the array directly.
|
December 29, 2020 Re: opIndexMember | ||||
---|---|---|---|---|
| ||||
Posted in reply to claptrap | On Tuesday, 29 December 2020 at 10:41:44 UTC, claptrap wrote:
> Hi, that works perfectly, and in godbolt, LDC -O3, identical codegen to just accessing the array directly.
Nice. Feel free to propose enhancements.
The dependency
import nxt.pure_mallocator : PureMallocator;
should probably be turned into a template parameter.
|
Copyright © 1999-2021 by the D Language Foundation