Почему метод list.Add быстрее чем точная копия, и таких классов уже много замечал, если там есть минимум 1 лишняя строчка c _version++?
То есть дизассемблер показывает больше кода, а он быстрее, как так, загадка Жака Фреско .
Может что-то не учитываю в бенчмарке, хотя ничто не могло пойти не так. Получается есть скрытые оптимизации, а где про них узнать, вроде List не partical класс, значит точная копия должна так же работать.
public class ListMy
{
public int[] _items;
public int _size;
public int Count => _size;
public ListMy(int capacity = 4)
{
_items = new int[0];
}
public int capacity => _items.Length;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int item)
{
int[] array = _items;
int size = _size;
if ((uint)size < (uint)array.Length)
{
_size = size + 1;
array[size] = item;
}
else
{
AddWithResize(item);
}
}
// Non-inline from List.Add to improve its code quality as uncommon path
[MethodImpl(MethodImplOptions.NoInlining)]
private void AddWithResize(int item)
{
Debug.Assert(_size == _items.Length);
int size = _size;
grow( );
_size = size + 1;
_items[size] = item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void grow()
{
int lastCount = _size;
var t = new int[int.Max(lastCount * 2,4)];
Array.Copy(_items, t, _items.Length);
_items = t;
}
/*
public void RemoveByIndex(int index)
{
// для коллекции не важен порядок элементов. удаляю в конец
_items[index] = _items[--_size];
if ((_size) <= (capacity >> 2))
{
int newLen = capacity >> 1;
var t = new int[newLen];
Array.Copy(_items, t, _size);
_items = t;
}
}
*/
/*
var span = CollectionsMarshal.AsSpan(list_1);
span[index] = span[span.Length - 1];
if (list_1.Count <= (list_1.Capacity >> 2))
{
list_1.Capacity = list_1.Capacity >> 1;
}
CollectionsMarshal.SetCount(list_1, list_1.Count - 1);
*/
}
но например метод RemoveByIndex ожидаемо быстрее чем так. Хотя если скалировать сколько там кода, то он должен был быть в раз 10 быстрее, а не в 2.
public class BenchmarkList
{
ListMy listMy;
List<int> listNative;
private int[] array;
[IterationSetup]
public void Setup() {
listMy = new ListMy();
listNative = new List<int>();
}
public IEnumerable<object[]> args()
{
yield return new object[] { 1000 };
yield return new object[] { 10_000 };
yield return new object[] { 100_000 };
}
[Benchmark]
[ArgumentsSource(nameof(args))]
public void Test_ListMy(int a)
{
for (int i = 0; i < a; i+=4)
{
listMy.Add(i);
listMy.Add(i-1);
listMy.Add(i+1);
listMy.Add(2*i);
}
}
[Benchmark]
[ArgumentsSource(nameof(args))]
public void Test_ListNative(int a)
{
for (int i = 0; i < a; i += 4)
{
listNative.Add(i);
listNative.Add(i - 1);
listNative.Add(i + 1);
listNative.Add(2 * i);
}
}
Она часто быстрее, но иногда ровно такая же скорост, но хоть на 1% быстрее всегда, хотя там одну переменную итерируют.
Если там есть какие скрытые микро оптимизации по зарегистрированному имени типа, то можно ли как-то туда и свою встроить.
Так же проверял, для вставки случайных элементов по случайным адресам в пределах 10 индексов. что бы там на всякий случай simd оптимизации не случились, то же всегда быстрее на 10% и более процентов.