Преобразование списка ИЛИ массива в диапазон

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

В настоящее время в параметрах используется IList<T>, поскольку нам требуется доступ к библиотеке на основе индекса и чтобы избежать перезаписи для обоих типов.

public int Foo<T>(IList<T> values);

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

public int Foo<T>(T[] values)
    => Foo(values.AsSpan());

public int Foo<T>(List<T> values)
    => Foo(CollectionsMarshal.AsSpan(values));

private int Foo<T>(Span<T> values)

Есть ли какой-либо интерфейс/маршал, который можно использовать как для списка, так и для массива, который преобразует их в диапазон?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
130
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Невозможно использовать один и тот же метод AsSpan() для списков и массивов, кроме как создать собственное расширение AsSpan(). Вместо этого вы всегда можете использовать IList<T>:

public int Foo<T>(IList<T> list)
{   
    Span<T> span;

    if (list is T[] array)
    {
        span = array.AsSpan();
    }
    else if (list is List<T> concreteList)
    {
        span = CollectionsMarshal.AsSpan(concreteList);
    }
    else 
    {
        throw new NotSupportedException("not supported!");
    }

    return Foo(span);
}

private int Foo<T>(Span<T> values)
{
    // ...
}

Да, это то, на чем мы остановились, но особой радости это не вызывает. Спасибо!

JBrown521 02.07.2024 10:52

Довольно бесплатное улучшение производительности

Я бы не сказал, что это совсем бесплатно. Возможно, вам стоит прочитать мелким шрифтом на CollectionsMarshal.AsSpan<T>(List<T>)

Получает Span<T> представление данных в списке. Предметы не должны быть добавлен или удален из List<T>, пока используется Span.

Это связано с реализацией (которая на самом деле является лучшим, что можно сделать из-за того, как реализован List<T>):

T[] items = list._items; // this is the backing store of the list which could change if not enough capacity
...
span = new Span<T>(ref MemoryMarshal.GetArrayDataReference(items), size);

что действительно нарушает определение того, что IList<T> должен уметь делать:

void Insert(int index, T item);
void RemoveAt(int index);

На этом этапе мы можем рассматривать IReadOnlyList<T> как обходной путь, но это не меняет того факта, что пока мы преобразуем экземпляр List<T> в Span<T> внутри метода (путем получения ссылки на резервное хранилище списка), кто-то другой может изменить оригинал List<T> до такой степени, что возникнет необходимость в новом хранилище резервных копий из-за ограничений емкости. Таким образом, наш метод будет работать со старыми/недействительными/противоречивыми данными.

Итак, я бы просто добавил перегрузку, принимающую Span<T> для повышения производительности, и не делал никаких изменений в существующем методе. Или, если вы действительно хотите облегчить жизнь людям, использующим библиотеку, добавьте:

/// <summary>
/// Some good warning...
/// </summary>
public int FooDangerous<T>(IList<T> values) {
    if (values is List<T> valuesList) {
        var span = CollectionsMarshal.AsSpan((List<T>)valuesList);
        return Foo(span);
    } else if (values is T[] valuesArray) {
        var span = valuesArray.AsSpan();
        return Foo(span);
    }
    // logic

}

Это имеет смысл, спасибо. Мне было интересно, почему у List<T> другой механизм преобразования в диапазон, чем тот, который используется для массива. В этом контексте мы всегда ожидаем, что коллекции будут иметь все свои данные (правда, они, вероятно, должны быть IReadOnlyList<T>), поэтому мне не приходило в голову, что произойдет, если список будет изменен в другом месте. Спасибо!

JBrown521 02.07.2024 10:45

@ JBrown521, да, я думаю, что это одна из (если не главная) причина, по которой нет удобного интерфейса с добавлением GetSpan() к основной коллекции/списку интерфейсов BCL, хотя Span<T> у нас уже давно есть.

Ivan Petrov 02.07.2024 11:08

Другие вопросы по теме

Почему массив % 1 работает на 150 % медленнее для меньших чисел, чем для больших?
Левая часть выражения LIKE должна иметь значение varchar (фактически: varbinary). Какая альтернатива преобразованию varbinary в varchar?
Есть ли какая-либо польза от использования словарного понимания, если возможен эквивалентный dict(zip(a, b))?
Узнайте, какой поток/процесс потребляет процессор
Улучшение производительности простого триггера onEdit
Numba njit делает вызов функции, включающей длинные выражения, очень медленно
«Предварительная обработка» функции Python, чтобы избежать избыточной оценки условной логики
Numpy `np.prod`, `np.all` работают медленнее, чем явные алгебраические операции
Почему копия for-loop не достигает максимальной пропускной способности CPU-RAM на одном ядре?
Как выбрать отдельные элементы из таблицы по переменной, а затем присоединиться к другой таблице в LINQ