-3

I have two big arrays of two readonly structs: A and B. I need to enumerate over them but I need to do it in the most optimized way.

readonly struct A {
   long a;
   long b;
   long c;
   long d;
   long e;
   long g;
}

readonly struct B {
   byte a;
   ushort b;
}

As you can see Type A is very large struct to copy it over and over again so I made my custom enumerator that returns this struct by reference (ref return). Type B doesn't need it because its' size is only 4 bytes. Now I need to make two enumerators move simultenously using only single foreach cycle because it doesn't look much native calling Enumerator's methods directly (i.e. MoveNext, Current) and so on.

My problem is that when I try to save two structs in one single aggregate struct A is copied and I lose my perfomance gain.

public readonly struct Aggregate
{
    public readonly A ItemA;
    public readonly B ItemB;

    public Aggregate(A itemA, in B itemB)
    {
        ItemA = itemA;
        ItemB = itemB;
    }
}

The total size of this aggregate will have size of 56 bytes. It sounds logical that C# copies the struct when I assign it to the property of another struct. But what can I do with it? I need only the reference to the element of an array. Using unsafe code to get a pointer is not a right way, I think, because GC can move my array (if it's small enough and not located in LOH area).

So what solutions could you propose to me?

Dobby007
  • 1,229
  • 1
  • 12
  • 22
  • 1
    I fail to see the reason not to use IEnumerable – Steve Dec 18 '18 at 19:30
  • @Steve, it's not very good to copy the struct over and over again for me. I wrote it in the question – Dobby007 Dec 18 '18 at 19:31
  • 1
    you havent show us where you are copying them. If you only need to enumerate two arrays you are copying nothing. Though you should be using class not struct if you want to reference by ref addr – Steve Dec 18 '18 at 19:33
  • 3
    Make `A` a class, not a struct. That's how you get reference semantics everywhere it's used, which seems to be what you want for it. – Servy Dec 18 '18 at 19:34
  • @Servy, then my program will suffer because GC will care about all that objects in the heap – Dobby007 Dec 18 '18 at 19:35
  • If you use proper `IEnumerable` you can use `Enumerable.Zip`. Otherwise, you'll need to not use `foreach`. – Cory Nelson Dec 18 '18 at 19:37
  • @Steve, the point is that structs are copied every time you want to pass it or return from the method – Dobby007 Dec 18 '18 at 19:37
  • @Dobby007 Are the objects extremely long lived, and are there so many of them that collections actually take a material amount of time? And of course you're using around the same amount of heap memory by having arrays of large value types, so if you have so many of these, and they're so long lived, that it's a problem, it'll likely be a problem either way. Value types are a win when they represent *small* object, in which copying them around isn't a meaningful cost. When that stops being true, they tend to cost more than the garbage collections. – Servy Dec 18 '18 at 19:39
  • @Servy, that's why they invented readonly and ref structs in C# 7.2 - 7.3 – Dobby007 Dec 18 '18 at 19:40
  • @Servy, around 500 million of them – Dobby007 Dec 18 '18 at 19:40
  • https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/struct https://techiesimon.com/2017/02/18/mutable-structs-are-evil-yes-really/ – Steve Dec 18 '18 at 19:42
  • @Steve, they are not mutable as long they are readonly https://learn.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code – Dobby007 Dec 18 '18 at 19:44
  • I still fail to see the reason to use struct over class – Steve Dec 18 '18 at 19:48
  • @Steve, too many objects in the heap. When using array it's only one big object filled with struct. GC doesn't need to do much work. – Dobby007 Dec 18 '18 at 19:50
  • @Dobby007 an array of class is one big object in the memory too. Unless you are throwing the array away GC won't do much work after you enumerated it. – Steve Dec 18 '18 at 19:55

2 Answers2

1

I don't know whether I understand your question correct or not, but your talking about arrays.

Assumption: Both are arrays of same size

You can do that

for (int i = 0; i < length; i++)
{
    ref var aItem = ref arrayA[i];
    ref var bItem = ref arrayB[i];
    //do your stuff
}
nosale
  • 808
  • 6
  • 14
0

You can use the enumerators explicitly, without a foreach-loop:

var aEnumerator = listA.GetEnumerator();
var bEnumerator = listB.GetEnumerator();
while (aEnumerator.MoveNext() && bEnumerator.MoveNext()) {
    var aItem = aEnumerator.Current;
    var bItem = bEnumerator.Current;
    //TODO: do some work
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Yes, I know that I can do it but I wrote: "Now I need to make two enumerators move simultenously using only single foreach cycle because it doesn't look much native calling Enumerator's methods directly (i.e. MoveNext, Current) and so on." – Dobby007 Dec 18 '18 at 19:45
  • This is the only ways I know, how you can enumerate simultaneously. But I am not sure how you are going the use a ref return. Have you added another property like `public ref int RefCurrent { get { return ref _current; } }` ? Because `IEnumerator` doesn't allow ref return on its regular `Current` property. – Olivier Jacot-Descombes Dec 18 '18 at 19:53
  • Yes, exactly. I added private property _current and return it by reference using ref return. Duck typing helped me. – Dobby007 Dec 18 '18 at 19:55