1

I have made an extension method for initializing arrays of ValueTuples, and realized that it can be done in two different ways. I can either replace the array elements with new tuples, or change the properties of the existing elements (because ValueTuple<T1, T2> structs are mutable). I wonder if there is any practical difference between using the one method or the other. Here are the candidates:

public static void Initialize1<T1, T2>(this ValueTuple<T1, T2>[] array,
    T1 item1, T2 item2)
{
    for (int i = 0; i < array.Length; i++)
    {
        array[i] = (item1, item2);
    }
}

public static void Initialize2<T1, T2>(this ValueTuple<T1, T2>[] array,
    T1 item1, T2 item2)
{
    for (int i = 0; i < array.Length; i++)
    {
        array[i].Item1 = item1;
        array[i].Item2 = item2;
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Did you profile this code? That would give the most useful answers I guess. – Patrick Hofman Jun 28 '19 at 13:37
  • 3
    So, lets get this clear: you create an array of tuples, and pico-seconds later you are doing that all over again? Why not set the correct values from the start? – Patrick Hofman Jun 28 '19 at 13:38
  • There is no practical difference. One or the other is undoubtedly faster, depending on how the JIT is feeling and which one you're using, but whether that will be significant is another matter altogether. You could benchmark that, if you have reason to care. – Jeroen Mostert Jun 28 '19 at 13:40
  • 1
    @PatrickHofman: it's worth noting that you can't really "set the correct values from the start". I mean, you can, but that will just mean allocating an array of tuples that are zero-initialized, and then setting all the values -- just as is done here. (An alternate that would not involve that is initializing two separate arrays of primitive types -- that can be optimized. But then you don't have tuples.) – Jeroen Mostert Jun 28 '19 at 13:42
  • Depending on how you use the elements from the array, there might be a difference, if you have external references to the elements. If you change the elements values, the external references see the new value. If you replace the elements, external references still see the old values. – derpirscher Jun 28 '19 at 13:43
  • @PatrickHofman I made a test with an array of 10,000,000 elements, and I didn't see any noticeable difference in performance. Regarding your second question, I am initializing the same array multiple times during the lifetime of my application, with different initial values each time. – Theodor Zoulias Jun 28 '19 at 13:45
  • 2
    @derpirscher That would only be true if it's a reference type. – Servy Jun 28 '19 at 13:46
  • @derpirscher: as we're talking an array of value types, any external references to the elements (regardless of whether done through a pointer or an index) would also see any updates. Unless they boxed the value before, in which case they wouldn't see updates no matter how the value was changed. – Jeroen Mostert Jun 28 '19 at 13:47
  • @JeroenMostert We also have `ref` types now, so managed references to a field in a struct in an array are a thing these days. – Servy Jun 28 '19 at 13:49
  • Or you can create and initialize the array using: `var result = Enumerable.Repeat(("a","b"), 100).ToArray();` – Magnus Jun 28 '19 at 13:49
  • @Servy: which doesn't change anything about this scenario, right? In that effectively that's still an offset that won't change regardless of whether the field or the whole struct is getting overwritten. – Jeroen Mostert Jun 28 '19 at 13:50
  • @JeroenMostert The only real difference over the other options you listed is that it's more likely to be a possibility these days than it used to be. Of course the effect of referencing one of the fields is exactly as you said. – Servy Jun 28 '19 at 13:52
  • @Servy: Oh, I see -- you meant I should have amended to "whether done through a pointer, an index *or a managed reference*". Which is fair enough; in my mind that's just another fancy pointer and I didn't intend the list to be exclusive. – Jeroen Mostert Jun 28 '19 at 13:55
  • @JeroenMostert Yes. And I only mentioned it because I suspect it is (or will soon become) the much more likely possibility of the three. Pointers are both uncommon, and usually only used by people that understand value/reference semantics reasonably well already. – Servy Jun 28 '19 at 13:56
  • @PatrickHofman I just made the observation that changing one property of the `ValueTuple` is as performant as changing both properties. Strange. – Theodor Zoulias Jun 28 '19 at 14:24
  • 2
    @TheodorZoulias Performance testing things that are extremely fast is *very* hard. The difference between assigning 64 vs 128 bits of memory is going to be *tiny*, and testing very tiny things means that your testing overhead tends to completely drown out the code being tested. Getting meaningful data therefore tends to be extremely hard. On the flip side, most anything that's hard to test is almost always too small to care about optimizing. – Servy Jun 28 '19 at 14:36
  • There's [BenchmarkDotNet](https://benchmarkdotnet.org/) for all your microbenchmarking needs. Take any manually written benchmark with a pinch of salt. (But even "good" microbenchmarks need to be taken with salt, since it's so easy for timing to change when methods are called in "real" code.) – Jeroen Mostert Jun 28 '19 at 14:51
  • @Servy I agree. The initialization is so damn fast (30 msec for 10,000,000 elements in a single thread of a slow machine) that I don't really care about making it any faster. I made this question mainly to avoid any hidden gotchas that a more experienced C# developer may know about these things. – Theodor Zoulias Jun 28 '19 at 15:05

0 Answers0