digitalmars.D - opIndexMember
- claptrap (35/35) Dec 27 2020 In some cases a structure of arrays is more desirable than an
- Dennis (14/15) Dec 27 2020 What's wrong with this?
- claptrap (62/77) Dec 27 2020 It wont work with ref returns, and performance in release builds.
- sighoya (51/55) Dec 27 2020 What about this:
- claptrap (4/10) Dec 27 2020 Codegen is better, but doesnt handle ref parameters, wont handle
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (4/8) Dec 28 2020 For an alternative solution see:
- claptrap (3/14) Dec 29 2020 Hi, that works perfectly, and in godbolt, LDC -O3, identical
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (5/7) Dec 29 2020 Nice. Feel free to propose enhancements.
- Petar Kirov [ZombineDev] (4/15) Dec 29 2020 Here's my own implementation which I dug out from an old project
- Petar Kirov [ZombineDev] (6/23) Dec 29 2020 Comparing it to your implementation, the main advantage of mine
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (3/7) Dec 30 2020 Lots of goodies there. Thanks
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?
Dec 27 2020
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?
Dec 27 2020
On Sunday, 27 December 2020 at 15:02:16 UTC, Dennis wrote:On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote: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 PLTSo 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?
Dec 27 2020
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.
Dec 27 2020
On Sunday, 27 December 2020 at 23:06:47 UTC, Dennis wrote:On Sunday, 27 December 2020 at 17:01:47 UTC, claptrap wrote:That gives identical codegen to just accessing the arrays directly, so codegen is as good as it can be!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: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.
Dec 28 2020
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; } ```
Dec 27 2020
On Sunday, 27 December 2020 at 17:34:44 UTC, sighoya wrote:On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote: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.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:
Dec 27 2020
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.
Dec 28 2020
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:Hi, that works perfectly, and in godbolt, LDC -O3, identical codegen to just accessing the array directly.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.
Dec 29 2020
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.
Dec 29 2020
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:Here's my own implementation which I dug out from an old project of mine from a few years back: https://gist.github.com/PetarKirov/a074073a12482e761a5e88eec559e5a8So 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.
Dec 29 2020
On Tuesday, 29 December 2020 at 22:33:01 UTC, Petar Kirov [ZombineDev] wrote:On Monday, 28 December 2020 at 20:15:27 UTC, Per Nordlöw wrote:Comparing it to your implementation, the main advantage of mine is that it always takes (size_t.sizeof * 2) bytes, regardless of how many members the struct has since it uses a heap allocation for all of the arrays.On Sunday, 27 December 2020 at 14:46:43 UTC, claptrap wrote:Here's my own implementation which I dug out from an old project of mine from a few years back: https://gist.github.com/PetarKirov/a074073a12482e761a5e88eec559e5a8So 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.
Dec 29 2020
On Tuesday, 29 December 2020 at 22:39:48 UTC, Petar Kirov [ZombineDev] wrote:Comparing it to your implementation, the main advantage of mine is that it always takes (size_t.sizeof * 2) bytes, regardless of how many members the struct has since it uses a heap allocation for all of the arrays.Lots of goodies there. Thanks
Dec 30 2020