Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке? Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого. Но тем не менее.
Семантика: отказаться от владения низлежащим буфером и передать его наружу. Используется как array builder для интеропа с низкоуровневыми API, которые получают на вход сегмент массива.
Пример использования:
var localList = new List<string>();
// Populating, filtering, sorting, grouping...
localList.Add("Camille");
localList.Add("Annie");
localList.Add("Sara");
localList.Add("Katrin");
localList.Add("Kari");
string[] array = localList.ToArray(); // Want to move, not to copy.string grouping = string.Join(", ", array, 1, 3);
Console.WriteLine(grouping); // Annie, Sara, Katrin
Q>Если это кажется полезным, присоединяйтесь к обсуждению: https://github.com/dotnet/corefx/issues/33169 Пока что собираются предложение завернуть. (Может, и правильно.)
По мне так полезная фича, ибо куча методов которая требует масиив и приходится копировать.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>По мне так полезная фича, ибо куча методов которая требует масиив и приходится копировать.
Можешь повлиять :) Достаточно откомментироваться в обсуждении (опционально со своими примерами), чтобы потихоньку склонить decision maker'ов к востребованности фичи. А там я запилю PR, и десятка лет не пройдёт, как замерджат и внедрят :)
Здравствуйте, Qbit86, Вы писали:
Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке? Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого. Но тем не менее. Q>Семантика: отказаться от владения низлежащим буфером и передать его наружу. Используется как array builder для интеропа с низкоуровневыми API, которые получают на вход сегмент массива.
Я большого смысла в таком API не вижу.
Если бы чего и хотелось, то это вариант Array.Resize(T[], Int32), который при новом размере меньше текущего не копировал бы массив в новый, а каким-то образом возвращал неиспользуемую память системе.
Тогда можно было бы использовать вместо List<T> ArrayBuilder<T> и затем просто обрезать массив до нужного размера.
А зачем оно в стандарте? Своя обёртка над массивом и пинпоинтеры для передачи в unmanaged без маршалинга — пара десятков строк кода. К тому же для тех, кто заботится о количестве и объёме аллокаций, стандартный List всё равно не лучший выбор.
Здравствуйте, pugv, Вы писали:
P>А зачем оно в стандарте?
Потому что а почему бы и нет? Это не новый функционал, это просто раскрытие существующего штатного, но скрытого функционала.
P>Своя обёртка над массивом и пинпоинтеры для передачи в unmanaged без маршалинга — пара десятков строк кода.
В существующих кодобазах уже полно всяких временных листов, начинающихся от `.ToList()` с последующими модификациями. Создание своего handmade ArrayBuilder'а не отменит того факта, что в дефолтной коллекции List<T> нет базового функционала.
Здравствуйте, Qbit86, Вы писали:
Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке?
Показалось, что совершенно бесполезная вещь: если число элементов в коллекции изначально неизвестно (а такой вывод можно сделать по приведённому примеру), то при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу в чём профит?
Когда беспокоит количество и объём аллокаций (в смысле сведения их к абсолютному минимуму), имеет смысл подойти к проблеме иначе — или всё-таки быть осведомлёммым о числе (возможно, максимальном) элементов и тогда можно сразу массив и использовать или использовать другие структуры данных.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Qbit86, Вы писали:
Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке? Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого. Но тем не менее.
Те, кто заботятся о количестве аллокаций, напишут себе сами свой List, а скорее всего, напишут себе что-то другое, более интегированное. Так что нет, не пригодился бы.
Q>Семантика: отказаться от владения низлежащим буфером и передать его наружу. Используется как array builder для интеропа с низкоуровневыми API, которые получают на вход сегмент массива. Q>Пример использования: Q>
Q>string[] array = localList.ToArray(); // Want to move, not to copy.
Q>string grouping = string.Join(", ", array, 1, 3);
Q>Console.WriteLine(grouping); // Annie, Sara, Katrin
Q>
Ты про все эти новые Span<T>, Memory<T>, Sequence<T> и Ranges которые или частично сделаны или в процессе, читал? Имхо все подобные сценарии давно закрыты целиком и полностью.
Здравствуйте, Sharowarsheg, Вы писали:
S>Те, кто заботятся о количестве аллокаций, напишут себе сами свой List
Я себе свой List написал. Но хочется иметь дешёвый и естественный способ избежать заведомо ненужной пессимизации в существующих кодобазах, без привлечения лишних зависимостей.
Здравствуйте, hi_octane, Вы писали:
_>Ты про все эти новые Span<T>, Memory<T>, Sequence<T> и Ranges которые или частично сделаны или в процессе, читал?
Да.
_>Имхо все подобные сценарии давно закрыты целиком и полностью.
Так «давно закрыты целиком и полностью» или «частично сделаны или в процессе»?
1) Куча существующих стандартных API по-прежнему получают на вход массивы и их сегменты, а не Span<T>. И все они переделаны в ближайшее время не будут, не говоря уже о сторонних библиотеках.
2) Если в существующей кодобазе где-то есть подобный временный список, то Span<T> ты из него не получишь, чтобы передать в подобный гипотетический API.
3) Memory<T> — это абстракция, со своими издержками. А MoveToArray() — это механика конкретной структуры данных, для неё естественная и практичная.
Здравствуйте, _FRED_, Вы писали:
_FR>при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу :xz: в чём профит?
А почему бы и не уменьшить на единицу? Причём, это уменьшение в районе правой границы удвоений. То есть одна аллокация в этом диапазоне длин по объёму будет порядка суммы всех предыдущих аллокаций.
_FR>или всё-таки быть осведомлёммым о числе (возможно, максимальном) элементов и тогда можно сразу массив и использовать или использовать другие структуры данных.
Да, часто есть какая-то предварительная estimatedCapacity, так что может быть даже всего одна аллокация. И не хочется городить ещё одну чуть меньшего размера под массив, когда почти подходящий массив уже есть внутри, только руку протяни.
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, Sharowarsheg, Вы писали:
S>>Те, кто заботятся о количестве аллокаций, напишут себе сами свой List
Q>Я себе свой List написал. Но хочется иметь дешёвый и естественный способ избежать заведомо ненужной пессимизации в существующих кодобазах, без привлечения лишних зависимостей.
У меня гораздо более двоичный подход к оптимизации — или нужно оптимизировать, или нет. Если нет, то пойдет стандартный лист. Если да, то ничего из готового, скорее всего, не подойдет — а если подошло, то, скорее всего, ещё есть куда оптимизировать.
1. Нижележащие managed-библиотеки по идее должны принимать ровно то, что им нужно. Т.е. где-то IEnumerable достаточен, где-то ICollection и т.д.
Если хочется "прикинутся" неизменяемым массивом — это одно, и тут средств пока не особо много. Но передача владения да еще нижележащего буфера — это уже совсем другое. Опять же — те кто принимают просто массив — могут не принимать длину и тут будет облом.
Я банально встречал ошибки с MemoryStream где вместо ToArray брали нижележащий буффер и получали лишний хвост (что приводило к неверным данным).
Боюсь себе представить "типичное" использование подобного метода.
2. Ну, а для unmanaged — это и вовсе непроблема, т.к. тот же список строк придется все равно как-то развернуть в соотв. с API.
Здравствуйте, Mystic Artifact, Вы писали:
MA>Я банально встречал ошибки с MemoryStream где вместо ToArray брали нижележащий буффер и получали лишний хвост (что приводило к неверным данным). MA>Боюсь себе представить "типичное" использование подобного метода. :)
Пример с MemoryStream хороший. В отличие от MemoryStream.GetBuffer(), предложенный метод List<T>.ReleaseBuffer()'а не раскрывает внутренности экземпляра List'а, так как эти внутренности немедленно перестают ему принадлежать сразу после вызова. То есть «опасность» относиться к другим типам — самому массиву и тем API, которые его потребляют.
MA>Опять же — те кто принимают просто массив — могут не принимать длину и тут будет облом. :)
Для тех API, которые зависят от array.Length, а не от переданной длины, такой подход не будет работать. К нему transfer ownership неприменим, а приходится делать вынужденную копию. Но в остальных-то случаях (а их, полагаю, большинство) API принимают пару (array, count) или тройку (array, offset, count).
Здравствуйте, Qbit86, Вы писали:
_FR>>при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу в чём профит?
Q>А почему бы и не уменьшить на единицу?
Потому что можно добиться лучших результатов оптимизации применив другие подходы, например, изначально используя массив (но, в общем, в каждом случае по-разному, в зависимости от конкретной ситуации).
Предлагаемый же метод меняет семантику такого широко используемого типа, как List<> за ради сильно небольшой выгоды. Лучше обратите внимание на то, сколько в BCL типов-аналогов List<>-а (обёрток над массивом, переаллоцируемым при необходимости), например тут. Для каждого конкретного случая самым улучшим оказывается что-то со своими маленькими особенностями и это кажется более правильным, чем нечто общее, но в большинстве случаев не самое лучшее.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Предлагаемый же метод меняет семантику такого широко используемого типа, как List<>
Не меняет вообще никак, совсем. Просто возвращает в набор публичных API ожидаемый там естественный метод, который по какому-то упущению не добавили ещё в .NET 2.0.
_FR>Для каждого конкретного случая самым улучшим оказывается что-то со своими маленькими особенностями и это кажется более правильным, чем нечто общее, но в большинстве случаев не самое лучшее.
List<T>.ReleaseBuffer() — это и есть не «нечто общее», а то самое «что-то со своими маленькими особенностями» «для каждого конкретного случая». В данном случае для конкретного случая List<T>'а. Вот есть в кодобазе куча конкретных листов, и для этих конкретных случаев конкретных листов самым лучшим оказывается конкретная механика List<T>.ReleaseBuffer(), а не рихтование кода под ArrayBuilder{T}, который вернёт Buffer<T>, который кастится к Span<T>, и так далее.
там что, правда предлагается костыль, который будет ради "интеропа с низкоуровневыми API" высовывать наружу "низлежащий буффер", меняя состояние до полной невалидности своего контейнера?
Здравствуйте, keenn, Вы писали:
K>там что, правда предлагается костыль, который будет ради "интеропа с низкоуровневыми API" высовывать наружу "низлежащий буффер", меняя состояние до полной невалидности своего контейнера?
Ну разжую тогда, раз "нет". Зашел по ссылке, там реализация предлагается такая (дальше опять читать не стал, наверное уже достаточно):
public T[] MoveToArray()
{
T[] result = _items;
_size = 0;
_items = s_emptyArray;
_version++;
return result;
}
— предполагается, что клиенты List будут с абсолютной уверенностью ожидать, что под List точно должен быть именно array, на сто процентов. Вообще, клиент хорошо так погружается во внутренности List (в которые он и до этого немного излишне погружался). Можно и далее пойти "изящные" хаки делать — например, создавать List(capacity n) и далее в зависимости от ситуации делать либо MoveToArray (получая массив), либо дальше работать с листом. А, что? Красиво! capacity может не значить, что массив будет обязательно создан? (а должен значить! мы то знаем!) Нельзя на это полагаться? Или можно? Да что вы говорите.
— всегда могут положиться на этот костыль, думая, что "так они не потеряют в ресурсах" (быстродействии/памяти), когда хотят получить массив там, где им нужно. Ведь этот костыль в основном на "мамкину оптимизацию". "efficient “array builder”, епта (рукалицо). А нужно им это там, где они просто плохо продумали свой код изначально. И теперь хотят, чтобы им костыли дали на уровне стандарта. Ну офигеть теперь.
— название — просто сказка. Которая отлично характеризует эту инициативу. "MoveToArray". Можно там развить инициативу, ведь нам нужен эффисиент эррэй билдер! И поэтому добавляем в стандарт:
public T[] MoveToArray(int start, int length) — вырезает из массива частичку. А чего. Эффективно! Для нужд получения части массива, когда нужна только часть.
public T[] MoveToArray(int start, int length, bool noDelete) — возвращает часть массива, а "вырезанную" часть не удаляет, а контейнер путем хитрых махинацй указания отрезков потом переиспользует. А по индексам использует информацию по смещениям. Еще эффективнее!
Про то, что "предлагатель" забыл про capacity — я вообще молчу. Это ожидаемо чисто из уровня этого пропозала.