Подтвердить что ты не робот

Список <T> to T [] без копирования

У меня есть большой список типов значений, которые нужно предоставить OpenGL. Было бы здорово, если бы это произошло как можно быстрее. Теперь я делаю следующее:

List<Vertex> VList = new List<Vertex>();
... //Add vertices
Vertex[] VArray;
VList.CopyTo(VArray, VList.Length);
GL.SetData(..., VArray);

Этот список легко 10 МБ большой, поэтому копирование происходит медленно. Могу ли я сделать это без копирования, как-то получить указатель на массив, используемый внутри List?

Или мне нужно реализовать свой собственный класс List.

EDIT: я забыл упомянуть, что я не знаю количество элементов, которые будут добавлены в список.

4b9b3361

Ответ 2

Я бы не рекомендовал то, что вы хотите сделать. Почему вы используете List<T> в первую очередь? Если вы можете точно сказать, какие характеристики должна иметь структура данных, которую вы хотите создать, и как она должна взаимодействовать с потребительским API, мы могли бы дать вам правильное решение вашей проблемы.

Но я постараюсь ответить на заданный вопрос.

Могу ли я сделать это без копирования, например как-то получить указатель на массив используется внутри List?

Да, хотя вы будете полагаться на недокументированную деталь реализации. Начиная с NET 4.0, поле базового массива называется _items.

Vertex[] vertices = (Vertex[]) typeof(List<Vertex>)
                   .GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)
                   .GetValue(VList);

Обратите внимание, что этот массив почти наверняка имеет слабень в конце (что вся точка List<T>), поэтому array.Length в этом массиве будет не так полезен. API, который потребляет массив, должен быть уведомлен о "реальной" длине массива с помощью других средств (сообщая ему, что такое реальный список Count).

Ответ 3

Если вам нужно многократно обращаться к внутреннему массиву, рекомендуется сохранить его как делегата.

В этом примере он делегирует динамический метод. Первый вызов может быть не быстрым, но последующие вызовы (в списке того же типа) будут намного быстрее.

public static class ListExtensions
{
    static class ArrayAccessor<T>
    {
        public static Func<List<T>, T[]> Getter;

        static ArrayAccessor()
        {
            var dm = new DynamicMethod("get", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(T[]), new Type[] { typeof(List<T>) }, typeof(ArrayAccessor<T>), true);
            var il = dm.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0); // Load List<T> argument
            il.Emit(OpCodes.Ldfld, typeof(List<T>).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)); // Replace argument by field
            il.Emit(OpCodes.Ret); // Return field
            Getter = (Func<List<T>, T[]>)dm.CreateDelegate(typeof(Func<List<T>, T[]>));
        }
    }

    public static T[] GetInternalArray<T>(this List<T> list)
    {
        return ArrayAccessor<T>.Getter(list);
    }
}

Ответ 4

Вместо того, чтобы использовать рефлексию для доступа к внутреннему массиву в List<T>, если вам нужна только возможность добавления, тогда я бы рекомендовал реализовать свой собственный изменяемый размер массива (gasp!). Это не так сложно.

Что-то вроде:

class ResizableArray<T>
{
    T[] m_array;
    int m_count;

    public ResizableArray(int? initialCapacity = null)
    {
        m_array = new T[initialCapacity ?? 4]; // or whatever
    }

    internal T[] InternalArray { get { return m_array; } }

    public int Count { get { return m_count; } }

    public void Add(T element)
    {
        if (m_count == m_array.Length)
        {
            Array.Resize(ref m_array, m_array.Length * 2);
        }

        m_array[m_count++] = element;
    }
}

Затем вы можете получить во внутреннем массиве InternalArray и узнать, сколько элементов находится в массиве, используя Count.

Ответ 5

Вы можете сделать это с отражением:

public static T[] GetUnderlyingArray<T>(this List<T> list)
{
    var field = list.GetType().GetField("_items",
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.NonPublic);
    return (T[])field.GetValue(list);
}

edit: а кто-то уже сказал это, пока я тестировал это.

Ответ 6

Возможно, вам стоит подумать, не подходит ли ваш подход к этому. Если вы обнаружите, что используете рефлексию, вы уже проиграли.

Я могу придумать несколько способов приблизиться к этому, хотя тот, который идеален, во многом зависит от того, является ли это многопоточной частью кода или нет.

Предположим, что это не...

Подумайте о характеристиках массива. Каждый раз, когда этот метод называется массивом длины N, создается. Ваша цель - повысить производительность (это означает, что вы хотите минимизировать распределение и копии данных).

Можете ли вы намекнуть на компиляцию или время выполнения, какой идеальный начальный размер для массива? Я имею в виду - если 95% времени N-длина составляет 100 тыс. Или меньше... начните с массива элементов 100 тыс. Единиц. Продолжайте использовать его, пока не нажмете случай, когда массив слишком мал.

Когда вы попадаете в этот случай, вы можете решить, что вы делаете, основываясь на своем понимании программы. Должен ли массив расти на 10%? Должно ли оно вырасти до буквальной необходимой длины? Можете ли вы использовать то, что у вас есть, и продолжить процесс для остальных данных?

Со временем будет найден идеальный размер. Вы можете даже настроить свою программу на конечный размер каждый раз, когда она запускается, и использовать ее как подсказку для выделения при следующем запуске (возможно, длина этого массива зависит от факторов окружающей среды, таких как разрешение и т.д.).

Другими словами, я предлагаю, чтобы вы не использовали метод List-to-Array, и что вы предварительно выделяете массив, держите его навсегда и вырабатываете его по мере необходимости.

Если ваша программа имеет проблемы с потоками, вам, очевидно, необходимо будет их решить.

Ответ 7

Вы могли бы получить указатель из общего списка, но я бы не рекомендовал его, и он, вероятно, не будет работать так, как вы ожидали бы (если вообще). В основном это означает получение указателя на объект, а не структуру памяти, такую ​​как массив.

Я думаю, вы должны пойти об этом по-другому, и если вам нужна скорость, тогда работайте непосредственно в массиве байтов, используя указатель на структурный массив в небезопасном контексте.

Фоновая информация:
" Даже при использовании с ключевым словом unsafe, адрес адреса управляемого объекта, получение размера управляемого объекта или объявление указателя на управляемый тип не допускается ". - Из С#: преобразовать общий указатель в массив

MSDN небезопасно

Ответ 8

Поскольку вы используете GL, я предполагаю, что вы знаете, что делаете, и пропустите все оговорки. Попробуйте это или посмотрите fooobar.com/info/233561/...

  [StructLayout(LayoutKind.Explicit)]
  public struct ConvertHelper<TFrom, TTo>
      where TFrom : class
      where TTo : class {
    [FieldOffset( 0)] public long before;
    [FieldOffset( 8)] public TFrom input;
    [FieldOffset(16)] public TTo output;

    static public TTo Convert(TFrom thing) {
      var helper = new ConvertHelper<TFrom, TTo> { input = thing };
      unsafe {
        long* dangerous = &helper.before;
        dangerous[2] = dangerous[1];  // ie, output = input
      }
      var ret = helper.output;
      helper.input = null;
      helper.output = null;
      return ret;
    }
  }

  class PublicList<T> {
    public T[] _items;
  }

  public static T[] GetBackingArray<T>(this List<T> list) {
    return ConvertHelper<List<T>, PublicList<T>>.Convert(list)._items;
  }