List<T>.MoveToArray()
От: Qbit86 Кипр
Дата: 02.11.18 07:54
Оценка:
Скажите, вам бы пригодился метод 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


Если это кажется полезным, присоединяйтесь к обсуждению: https://github.com/dotnet/corefx/issues/33169 Пока что собираются предложение завернуть. (Может, и правильно.)
Глаза у меня добрые, но рубашка — смирительная!
Re: List<T>.MoveToArray()
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 02.11.18 08:23
Оценка: 1 (1)
Здравствуйте, Qbit86, Вы писали:


Q>Если это кажется полезным, присоединяйтесь к обсуждению: https://github.com/dotnet/corefx/issues/33169 Пока что собираются предложение завернуть. (Может, и правильно.)

По мне так полезная фича, ибо куча методов которая требует масиив и приходится копировать.
и солнце б утром не вставало, когда бы не было меня
Re[2]: Куча методов
От: Qbit86 Кипр
Дата: 02.11.18 08:28
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>По мне так полезная фича, ибо куча методов которая требует масиив и приходится копировать.


Можешь повлиять :) Достаточно откомментироваться в обсуждении (опционально со своими примерами), чтобы потихоньку склонить decision maker'ов к востребованности фичи. А там я запилю PR, и десятка лет не пройдёт, как замерджат и внедрят :)
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 02.11.2018 8:30 Qbit86 . Предыдущая версия .
Re: List<T>.MoveToArray()
От: romangr Россия  
Дата: 02.11.18 08:41
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке? Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого. Но тем не менее.

Q>Семантика: отказаться от владения низлежащим буфером и передать его наружу. Используется как array builder для интеропа с низкоуровневыми API, которые получают на вход сегмент массива.

Я большого смысла в таком API не вижу.
Если бы чего и хотелось, то это вариант Array.Resize(T[], Int32), который при новом размере меньше текущего не копировал бы массив в новый, а каким-то образом возвращал неиспользуемую память системе.
Тогда можно было бы использовать вместо List<T> ArrayBuilder<T> и затем просто обрезать массив до нужного размера.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re: List<T>.MoveToArray()
От: pugv Россия  
Дата: 02.11.18 09:52
Оценка: +1
А зачем оно в стандарте? Своя обёртка над массивом и пинпоинтеры для передачи в unmanaged без маршалинга — пара десятков строк кода. К тому же для тех, кто заботится о количестве и объёме аллокаций, стандартный List всё равно не лучший выбор.
Re[2]: Зачем оно в стандарте
От: Qbit86 Кипр
Дата: 02.11.18 09:57
Оценка:
Здравствуйте, pugv, Вы писали:

P>А зачем оно в стандарте?


Потому что а почему бы и нет? Это не новый функционал, это просто раскрытие существующего штатного, но скрытого функционала.

P>Своя обёртка над массивом и пинпоинтеры для передачи в unmanaged без маршалинга — пара десятков строк кода.


В существующих кодобазах уже полно всяких временных листов, начинающихся от `.ToList()` с последующими модификациями. Создание своего handmade ArrayBuilder'а не отменит того факта, что в дефолтной коллекции List<T> нет базового функционала.
Глаза у меня добрые, но рубашка — смирительная!
Re: List<T>.MoveToArray()
От: _FRED_ Черногория
Дата: 02.11.18 18:55
Оценка: +2
Здравствуйте, Qbit86, Вы писали:

Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке?


Показалось, что совершенно бесполезная вещь: если число элементов в коллекции изначально неизвестно (а такой вывод можно сделать по приведённому примеру), то при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу в чём профит?

Когда беспокоит количество и объём аллокаций (в смысле сведения их к абсолютному минимуму), имеет смысл подойти к проблеме иначе — или всё-таки быть осведомлёммым о числе (возможно, максимальном) элементов и тогда можно сразу массив и использовать или использовать другие структуры данных.
Help will always be given at Hogwarts to those who ask for it.
Re: List<T>.MoveToArray()
От: Sharowarsheg  
Дата: 02.11.18 19:01
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке? Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого. Но тем не менее.


Те, кто заботятся о количестве аллокаций, напишут себе сами свой List, а скорее всего, напишут себе что-то другое, более интегированное. Так что нет, не пригодился бы.
Re: List<T>.MoveToArray()
От: hi_octane Беларусь  
Дата: 02.11.18 19:08
Оценка:
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 которые или частично сделаны или в процессе, читал? Имхо все подобные сценарии давно закрыты целиком и полностью.
Re[2]: Свой List
От: Qbit86 Кипр
Дата: 02.11.18 19:12
Оценка:
Здравствуйте, Sharowarsheg, Вы писали:

S>Те, кто заботятся о количестве аллокаций, напишут себе сами свой List


Я себе свой List написал. Но хочется иметь дешёвый и естественный способ избежать заведомо ненужной пессимизации в существующих кодобазах, без привлечения лишних зависимостей.
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: Сценарии давно закрыты
От: Qbit86 Кипр
Дата: 02.11.18 19:24
Оценка: +1
Здравствуйте, hi_octane, Вы писали:

_>Ты про все эти новые Span<T>, Memory<T>, Sequence<T> и Ranges которые или частично сделаны или в процессе, читал?


Да.

_>Имхо все подобные сценарии давно закрыты целиком и полностью.


Так «давно закрыты целиком и полностью» или «частично сделаны или в процессе»?

1) Куча существующих стандартных API по-прежнему получают на вход массивы и их сегменты, а не Span<T>. И все они переделаны в ближайшее время не будут, не говоря уже о сторонних библиотеках.
2) Если в существующей кодобазе где-то есть подобный временный список, то Span<T> ты из него не получишь, чтобы передать в подобный гипотетический API.
3) Memory<T> — это абстракция, со своими издержками. А MoveToArray() — это механика конкретной структуры данных, для неё естественная и практичная.
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: Уменьшить на единицу
От: Qbit86 Кипр
Дата: 02.11.18 19:32
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу :xz: в чём профит?


А почему бы и не уменьшить на единицу? Причём, это уменьшение в районе правой границы удвоений. То есть одна аллокация в этом диапазоне длин по объёму будет порядка суммы всех предыдущих аллокаций.

_FR>или всё-таки быть осведомлёммым о числе (возможно, максимальном) элементов и тогда можно сразу массив и использовать или использовать другие структуры данных.


Да, часто есть какая-то предварительная estimatedCapacity, так что может быть даже всего одна аллокация. И не хочется городить ещё одну чуть меньшего размера под массив, когда почти подходящий массив уже есть внутри, только руку протяни.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Свой List
От: Sharowarsheg  
Дата: 02.11.18 19:34
Оценка: 3 (1)
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Sharowarsheg, Вы писали:


S>>Те, кто заботятся о количестве аллокаций, напишут себе сами свой List


Q>Я себе свой List написал. Но хочется иметь дешёвый и естественный способ избежать заведомо ненужной пессимизации в существующих кодобазах, без привлечения лишних зависимостей.


У меня гораздо более двоичный подход к оптимизации — или нужно оптимизировать, или нет. Если нет, то пойдет стандартный лист. Если да, то ничего из готового, скорее всего, не подойдет — а если подошло, то, скорее всего, ещё есть куда оптимизировать.
Re: List<T>.MoveToArray()
От: Mystic Artifact  
Дата: 03.11.18 08:32
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Мне не кажется это сильно полезным:

1. Нижележащие managed-библиотеки по идее должны принимать ровно то, что им нужно. Т.е. где-то IEnumerable достаточен, где-то ICollection и т.д.

Если хочется "прикинутся" неизменяемым массивом — это одно, и тут средств пока не особо много. Но передача владения да еще нижележащего буфера — это уже совсем другое. Опять же — те кто принимают просто массив — могут не принимать длину и тут будет облом.

Я банально встречал ошибки с MemoryStream где вместо ToArray брали нижележащий буффер и получали лишний хвост (что приводило к неверным данным).

Боюсь себе представить "типичное" использование подобного метода.


2. Ну, а для unmanaged — это и вовсе непроблема, т.к. тот же список строк придется все равно как-то развернуть в соотв. с API.
Re[2]: MemoryStream
От: Qbit86 Кипр
Дата: 03.11.18 08:59
Оценка:
Здравствуйте, 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).
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Уменьшить на единицу
От: _FRED_ Черногория
Дата: 03.11.18 11:24
Оценка:
Здравствуйте, Qbit86, Вы писали:

_FR>>при добавлении элементов в список уже будут случаться переаллокации и копирования и предлагаемый метод лишь уменьшит их число на единицу в чём профит?


Q>А почему бы и не уменьшить на единицу?


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

Предлагаемый же метод меняет семантику такого широко используемого типа, как List<> за ради сильно небольшой выгоды. Лучше обратите внимание на то, сколько в BCL типов-аналогов List<>-а (обёрток над массивом, переаллоцируемым при необходимости), например тут. Для каждого конкретного случая самым улучшим оказывается что-то со своими маленькими особенностями и это кажется более правильным, чем нечто общее, но в большинстве случаев не самое лучшее.
Help will always be given at Hogwarts to those who ask for it.
Конкретный случай
От: Qbit86 Кипр
Дата: 03.11.18 11:44
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Предлагаемый же метод меняет семантику такого широко используемого типа, как List<>


Не меняет вообще никак, совсем. Просто возвращает в набор публичных API ожидаемый там естественный метод, который по какому-то упущению не добавили ещё в .NET 2.0.

_FR>Для каждого конкретного случая самым улучшим оказывается что-то со своими маленькими особенностями и это кажется более правильным, чем нечто общее, но в большинстве случаев не самое лучшее.


List<T>.ReleaseBuffer() — это и есть не «нечто общее», а то самое «что-то со своими маленькими особенностями» «для каждого конкретного случая». В данном случае для конкретного случая List<T>'а. Вот есть в кодобазе куча конкретных листов, и для этих конкретных случаев конкретных листов самым лучшим оказывается конкретная механика List<T>.ReleaseBuffer(), а не рихтование кода под ArrayBuilder{T}, который вернёт Buffer<T>, который кастится к Span<T>, и так далее.
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 03.11.2018 11:45 Qbit86 . Предыдущая версия .
Re: List<T>.MoveToArray()
От: keenn  
Дата: 04.11.18 04:52
Оценка:
ссылку не читал.

там что, правда предлагается костыль, который будет ради "интеропа с низкоуровневыми API" высовывать наружу "низлежащий буффер", меняя состояние до полной невалидности своего контейнера?

жесть какая-то, чего только не предложат
Re[2]: Нет
От: Qbit86 Кипр
Дата: 04.11.18 08:05
Оценка:
Здравствуйте, keenn, Вы писали:

K>там что, правда предлагается костыль, который будет ради "интеропа с низкоуровневыми API" высовывать наружу "низлежащий буффер", меняя состояние до полной невалидности своего контейнера?


Нет.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Нет
От: keenn  
Дата: 04.11.18 08:45
Оценка:
Q>Нет.

Ну разжую тогда, раз "нет". Зашел по ссылке, там реализация предлагается такая (дальше опять читать не стал, наверное уже достаточно):

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 — я вообще молчу. Это ожидаемо чисто из уровня этого пропозала.
Re[4]: Capacity
От: Qbit86 Кипр
Дата: 04.11.18 09:00
Оценка:
Здравствуйте, keenn, Вы писали:

K>"предлагатель" забыл про capacity


Нет.
Глаза у меня добрые, но рубашка — смирительная!
Re[5]: Capacity
От: keenn  
Дата: 04.11.18 09:13
Оценка: +2
Q>Нет.

Ахаха, спасибо! Посмотрел, а ведь действительно, в ремарках такое(!):

/// <remarks>
/// This array is <see cref="Capacity"/> in length,
/// so the caller should save actual <see cref="Count"/> before calling this method.
/// </remarks>


Вот эти ремарки — "caller should save actual Count" — в предложении добавить в стандарт по List-у — реально финиш.

Я вообще не понимаю, зачем контрибуторы тратят время обсуждая подобные пропозалы, когда у них реальных проблем немеряно. Может политики какие-то клиентоориентированности. kpi там зависит или еще что.
Re[6]: Тратят время
От: Qbit86 Кипр
Дата: 04.11.18 09:20
Оценка:
Здравствуйте, keenn, Вы писали:

K>Вот эти ремарки — "caller should save actual Count" — в предложении добавить в стандарт по List-у — реально финиш.


В стандарте уже есть подобные методы.

K>Я вообще не понимаю, зачем контрибуторы тратят время обсуждая подобные пропозалы


Я в свою очередь не понимаю, зачем я трачу время на твои комментарии. Хотя почти и не трачу, просто два раза написал «нет» — частично помогло даже.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: Тратят время
От: keenn  
Дата: 04.11.18 09:27
Оценка:
Q>В стандарте уже есть подобные методы.

И что с того?

Q>Я в свою очередь не понимаю, зачем я трачу время на твои комментарии. Хотя почти и не трачу, просто два раза написал «нет» — частично помогло даже.


А, так ты еще и тот самый предлагатель. Ну извини.

Объясняю: тратишь потому, что с одной стороны по сути ответить нечего, а с другой — обидно. Ну извини, не хотел резко. Твое предложение — мягко говоря, "не очень", что тебе там (да и здесь) и объясняют в гораздо более мягких выражениях. Не обращай внимания.
Re[8]: Ответить нечего
От: Qbit86 Кипр
Дата: 04.11.18 09:49
Оценка:
Здравствуйте, keenn, Вы писали:

K>Объясняю: тратишь потому, что с одной стороны по сути ответить нечего


На что ответить? На предложение добавить «MoveToArray(int start, int length, bool noDelete)» про «вырезает из массива частичку»? :) Или это: «а должен значить! мы то знаем!»? :) Да, мне на это ответить нечего, это вообще поток рандомных слов, сделанный исходя из неверных предположений.

K>а с другой — обидно... тебе там (да и здесь) и объясняют в гораздо более мягких выражениях


Чего тут обидного? :) Все пункты несогласия в этом треде — пока ты не пришёл — были от шарящих людей, которые контекст поняли. Конечно, с ними интереснее, и ответы на их комментарии я за трату времени не считаю.

От твоих комментариев польза одна. Название MoveToArray() (по аналогии с MoveToImmutable()) дейстивтельно теперь представляется слишком сложным. Надо было ReleaseBuffer() по аналогии с GetBuffer().

K>Ну извини, не хотел резко.


Извини, не хотел резко. Действительно не хотел, именно поэтому в начале треда ограничивался только формулировкой «Нет».
Глаза у меня добрые, но рубашка — смирительная!
Re[9]: Ответить нечего
От: keenn  
Дата: 04.11.18 10:00
Оценка:
Q>На что ответить? На предложение добавить «MoveToArray(int start, int length, bool noDelete)» про «вырезает из массива частичку»? Или это: «а должен значить! мы то знаем!»? Да, мне на это ответить нечего, это вообще поток рандомных слов, сделанный исходя из неверных предположений.

Я тебе хотел объяснить этим развитием твоей же мысли, что ты предлагаешь и почему это неправильно. Очевидную, примитивную мысль, что буффер внутри листа — деталь реализации, которую ты хочешь вытащить (совершенно неоправданно) наружу. Я даже знаю, что ты сейчас напишешь — про MemoryStream?

Q>От твоих комментариев польза одна. Название MoveToArray() (по аналогии с MoveToImmutable()) дейстивтельно теперь представляется слишком сложным. Надо было ReleaseBuffer() по аналогии с GetBuffer().


Ага, начинаешь понемногу понимать, что предлагаешь. Уже хорошо. Это все равно точно так же неправильно, только теперь чуть более явно.
Re[10]: Инкапсуляция
От: Qbit86 Кипр
Дата: 04.11.18 10:12
Оценка:
Здравствуйте, keenn, Вы писали:

K>Очевидную, примитивную мысль, что буффер внутри листа — деталь реализации, которую ты хочешь вытащить (совершенно неоправданно) наружу. Я даже знаю, что ты сейчас напишешь — про MemoryStream?


Про MemoryStream не я, а Mystic Artifact выше. MemoryStream гораздо более опасный случай (и всё равно его пользователи умудряются чудом выживать). Потому что вытаскивает наружу не только внутренности класса, а внутренности экземпляра класса. ReleaseBuffer() подобного не делает, и инкапсуляцию и инварианты экземпляра не нарушает. С точки зрения наблюдаемого поведения экземпляра листа это неотличимо от Clear()TrimExcess(), если волнует ненаблюдаемое поведение). Возвращённый массив к бывшему владельцу больше никакого отношения не имеет.

K>Ага, начинаешь понемногу понимать, что предлагаешь.


Ты начинаешь понемногу понимать, что я предлагаю. (Но это не точно.)
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 04.11.2018 10:13 Qbit86 . Предыдущая версия .
Re[11]: Инкапсуляция
От: keenn  
Дата: 04.11.18 10:22
Оценка: :)
Q> ReleaseBuffer() подобного не делает, и инкапсуляцию и инварианты экземпляра не нарушает. С точки зрения наблюдаемого поведения экземпляра листа это неотличимо от Clear()TrimExcess(), если волнует ненаблюдаемое поведение). Возвращённый массив к бывшему владельцу больше никакого отношения не имеет.

Да что ж такое. Давай с другой стороны попробую тебе объяснить. Начну с наводящего вопроса — зачем понадобился этот новый метод для класса List? Какая у него мотивация? То, что его нужно переименовать в ReleaseBuffer, ты уже понял. Сейчас, надеюсь, произойдет еще один шаг в понимании. Хотя я уже разжевал до кашицы во втором же сообщении.

Q>Ты начинаешь понемногу понимать, что я предлагаю. (Но это не точно.)


В том то и проблема, что я прекрасно понимаю что ты предлагаешь. А ты — нет.
Re[12]: Наводящие вопросы
От: Qbit86 Кипр
Дата: 04.11.18 10:47
Оценка: 6 (1)
Здравствуйте, keenn, Вы писали:

Q>> ReleaseBuffer() подобного не делает, и инкапсуляцию и инварианты экземпляра не нарушает. С точки зрения наблюдаемого поведения экземпляра листа это неотличимо от Clear()TrimExcess(), если волнует ненаблюдаемое поведение). Возвращённый массив к бывшему владельцу больше никакого отношения не имеет.


K>Начну с наводящего вопроса — зачем понадобился этот новый метод для класса List?


Ты с таким апломбом ворвался в этот тред, распахнув с ноги дверь, и начал
Автор: keenn
Дата: 04.11.18
как раз с фразы: «ссылку не читал». А теперь позволяешь себе свысока подобные «наводящие вопросы».

K>Какая у него мотивация?


Но так и быть. Моя мотивация — большая легаси кодобаза, где есть куча листов (что естественно, потому что это «дефолтная» коллекция). И у этих листов есть много вызовов `.ToArray()` по тем или иным причинам (например, для передачи в API последующего уровня). И вместо большинства этих `.ToArray()` с гораздо более сложным и дорогостоящим функционалом (аллокация и копирование) достаточно простой передачи владения буфером (гораздо более простого и базового функционала). Речь не об оптимизации, речь об избегании пессимизации на ровном месте.

K>То, что его нужно переименовать в ReleaseBuffer, ты уже понял.


Исходное название апеллировало к «move semantics» — термину стандартному, но похоже, ширнармассам незнакомому.

K>Сейчас, надеюсь, произойдет еще один шаг в понимании.


Масштаб твоей снисходительности граничит только с твоим самомнением.

K>Хотя я уже разжевал до кашицы во втором же сообщении.


Напомню, во втором сообщении ты написал «кашицу» про «MoveToArray(int start, int length, bool noDelete)» и «мамкину оптимизацию». Собственно, на этом общение с тобой можно было и закончить.
Глаза у меня добрые, но рубашка — смирительная!
Re[13]: Наводящие вопросы
От: keenn  
Дата: 04.11.18 11:07
Оценка:
Q>Ты с таким апломбом ворвался в этот тред, распахнув с ноги дверь, и начал
Автор: keenn
Дата: 04.11.18
как раз с фразы: «ссылку не читал». А теперь позволяешь себе свысока подобные «наводящие вопросы».


Я назвал его "наводящим" не просто так. Я прекрасно видел твою мотивацию. Наводящим он был для того, чтобы натолкнуть тебя на нужный ход мыслей. Ну за "апломб" я извинился уже. И это Мне нравится, когда дискуссия проходит немного, скажем так, энергично ) Не парься на этот счет особо. Сорри еще раз.

K>>Какая у него мотивация?


Q>Но так и быть. Моя мотивация — большая легаси кодобаза, где есть куча листов (что естественно, потому что это «дефолтная» коллекция). И у этих листов есть много вызовов `.ToArray()` по тем или иным причинам (например, для передачи в API последующего уровня). И вместо большинства этих `.ToArray()` с гораздо более сложным и дорогостоящим функционалом (аллокация и копирование) достаточно простой передачи владения буфером (гораздо более простого и базового функционала). Речь не об оптимизации, речь об избегании пессимизации на ровном месте.


Ты не поверишь — есть на свете много легаси кодебаз, где есть куча много чего. И там много костылей бы пригодилось. То, что ты описываешь не решится банальным добавлением описанного тобой метода, т.к. изменится (для большой кодебазы — должна) логика, и нужен будет частичный пересмотр кодебазы. Для того чтобы внедрить этот метод.

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

И это. Ответ твой — метод нужен для оптимизации. Или еще задавать тебе так раздражающие тебя наводящие вопросы? Мне всегда казалось, что люди быстрее понимают с ними, но ты как-то умудряешься обижаться и продолжать не понимать о чем идет речь.

Q>Исходное название апеллировало к «move semantics» — термину стандартному, но похоже, ширнармассам незнакомому.


Ага-ага, кто бы тут про апломб. Ты — элита, в отличии от ширнармасс (к которым принадлежу я), я правильно понимаю? Из снисхождения ко мне (и таким как я) ты снисходишь до того, что готов рассмотреть изменение по наименованию, "слишком сложно" для ширнармасс и прочих мелких умов. Это восхитительно просто, я считаю. Не говоря уж о том, что эти аллюзии на move semantics в твоем изначальном пропозале к классу List выглядят так, что есть риск, что ты тоже относишься к "ширнармассам".

Q>Масштаб твоей снисходительности граничит только с твоим самомнением.


Не парься Мне не лень еще раз извинится. Поверь, у меня нет никакого самомнения.

Q>Напомню, во втором сообщении ты написал «кашицу» про «MoveToArray(int start, int length, bool noDelete)» и «мамкину оптимизацию». Собственно, на этом общение с тобой можно было и закончить.


Я просто прыгнул на большее число шагов в объяснении, чем нужно было.
Re[14]: Костыли
От: Qbit86 Кипр
Дата: 04.11.18 11:49
Оценка: 3 (1)
Здравствуйте, keenn, Вы писали:

K>Ну про то, что легаси кодебазы где вместо массивов используются листы


Массивы не могут использоваться в сыром виде вместо листов. Массивы — это примитивные структуры данных с фиксированным количеством элементов. А лист — навешенный поверх слой, который поддерживает динамический размер, удаление из середины со сдвигом, etc. Он уже есть. Других стандартных коллекций с аналогичным функционалом и перформанс-характеристиками, чтоб использовать вместо листа — нет.

K>не являются основанием для добавления чего либо в класс List — это ведь понятно?


Не является. Но это то, что немедленно улучшит quality of life, и моё дело предложить — а их дело принимать решение. В листе и так полно костылей. Тот же ToArray() куда менее полезен. Его отсутствие делало бы извлечение массива из листа более осознанным: был бы явно виден new и CopyTo().

Если уж надо сохранить количество методов, то я бы предпочёл жить во вселенной, где есть ReleaseBuffer() вместо ToArray(). Функционал последнего легко достижим без загрязнения API класса, а функционал первого — нет. По прежнему считаю, что ReleaseBuffer() — не добавление нового, а восстановление недостающего, но коренного функционала. Это не просто какой-то рандомный метод типа SwapOddAndEvenItems() для нужд лично моего проекта.

K>И это. Ответ твой — метод нужен для оптимизации.


Написал же: «речь не об оптимизации, речь об избегании пессимизации на ровном месте.» Но пусть будет для оптимизации, не суть важно.

K>Ты — элита, в отличии от ширнармасс (к которым принадлежу я)


Ширнармассы программистов на C# в среднем не сталкиваются с move semantics как в C++ или transferring ownership как в Rust — и это нормально. Мне при использовании семантики название по большому счёту неважно, главное, чтоб людям было понятнее. Понятнее ReleaseBuffer(), так ради бога. Вероятно, и в своём листе переименую.
Глаза у меня добрые, но рубашка — смирительная!
Re[15]: Костыли
От: keenn  
Дата: 04.11.18 13:38
Оценка:
Q>Массивы не могут использоваться в сыром виде вместо листов. Массивы — это примитивные структуры данных с фиксированным количеством элементов. А лист — навешенный поверх слой, который поддерживает динамический размер, удаление из середины со сдвигом, etc. Он уже есть. Других стандартных коллекций с аналогичным функционалом и перформанс-характеристиками, чтоб использовать вместо листа — нет.

Ну, раз так, отвечу в тон: существует также группа хелперов из System.Array. Не говоря уж о том, что как-то странно делегировать эффективную работу с массивом по требованиям динамического размера или удаления из середины List-у, если сильно волнуешься о количестве и объёме аллокаций

Q>Не является. Но это то, что немедленно улучшит quality of life, и моё дело предложить — а их дело принимать решение. В листе и так полно костылей. Тот же ToArray() куда менее полезен. Его отсутствие делало бы извлечение массива из листа более осознанным: был бы явно виден new и CopyTo().


Ну этот костыль, который предлагается тобой — он такой, костыль новый идеологически. Затрагивающий требования к дизайну List. О чем я и пишу. И ничего он жизнь не улучшит (а вот усложнить — легко). Может кому-то в легаси когда-то и да, но это извините. Ну и "ничего страшного, будет еще один костыль, они же уже есть" — насчет этого ведь понятно же, что это не подход?

Q>Написал же: «речь не об оптимизации, речь об избегании пессимизации на ровном месте.» Но пусть будет для оптимизации, не суть важно.


Речь идет вот о чем: ты хочешь использовать по сути внутренние фичи List-а ("динамический размер, удаление из середины со сдвигом, etc") и при этом сделать такое явное требование к дизайну List-а, чтобы он был построен поверх массива и только массива. Это требование, явно проистекающее из нового метода ReleaseBuffer. Сейчас такого требования таки нет. Ну и все, что далее этого касается — например, значит, нужно вводить явные требования к тем неявным фичам листа, которые тебя заинтересовали, типа динамического размера (т.к. иначе смысл ReleaseBuffer полностью теряется).

Например, чтобы в порядке вещей было обернуть in place массив в List, пофигачить серию операций вроде добавления/удаления, а потом сделать ReleaseBuffer. И при этом по производительности/памяти должны быть обеспечены явно описанные требования. Ведь это как раз основной сценарий и то, для чего это всё это и делается — не так ли? Не кажется тебе это мутноватым?

Есть существующее требование-костыль в листе (также относящееся к оптимизации), близкое, но не тождественное — capacity. Его нужно дружить с предлагаемым тобой — отсюда рождается, для начала, конфликты вида "caller should save actual Count". Т.е. тут еще и на каллера перекладывается некая ответственность, которая проистекает из этого конфликта. И тут еще нужно это женить ("save actual Count" — это имхо несерьезно)...
Re: List<T>.MoveToArray()
От: _NN_ www.nemerleweb.com
Дата: 04.11.18 16:40
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Насколько я понимаю в CoreFX есть частное решения проблемы для Enumerable.ToArray через ArrayBuilder (LargeArrayBuilder).
Как можно видеть подразумевается, что ToArray вызовется у него лишь однажды и таким образом не нужно лишний раз копировать.

К сожалению это не решает общей проблемы, когда формируем List, а нужно передать массив.

  код
https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L317
            public TResult[] ToArray()
            {
                var builder = new LargeArrayBuilder<TResult>(initialize: true);
                
                foreach (TSource item in _source)
                {
                    builder.Add(_selector(item));
                }

                return builder.ToArray();
}


https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Collections/Generic/LargeArrayBuilder.SizeOpt.cs#L41
    // LargeArrayBuilder.netcoreapp.cs provides a "LargeArrayBuilder" that's meant to help
    // avoid allocations and copying while building up an array.  But in doing so, it utilizes
    // T[][] to store T[]s, which results in significant size increases for AOT builds. To
    // address that, this minimal wrapper for ArrayBuilder<T> may be used instead; it's a simple
    // passthrough to ArrayBuilder<T>, and thus doesn't incur the size increase due to the T[][]s.



internal struct LargeArrayBuilder<T>
{
 private ArrayBuilder<T> _builder; // mutable struct; do not make this readonly

 public T[] ToArray() => _builder.ToArray();
}



https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Collections/Generic/ArrayBuilder.cs#L99
        /// <summary>
        /// Creates an array from the contents of this builder.
        /// </summary>
        /// <remarks>
        /// Do not call this method twice on the same builder.
        /// </remarks>
        public T[] ToArray()
        {
            if (_count == 0)
            {
                return Array.Empty<T>();
            }

            Debug.Assert(_array != null); // Nonzero _count should imply this

            T[] result = _array;
            if (_count < result.Length)
            {
                // Avoid a bit of overhead (method call, some branches, extra codegen)
                // which would be incurred by using Array.Resize
                result = new T[_count];
                Array.Copy(_array, 0, result, 0, _count);
            }

#if DEBUG
            // Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0.
            _count = -1;
            _array = null;
#endif

            return result;
        }
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[16]: ArrayPrefix<T>
От: Qbit86 Кипр
Дата: 04.11.18 20:15
Оценка:
Здравствуйте, keenn, Вы писали:

K>Не говоря уж о том, что как-то странно делегировать эффективную работу с массивом по требованиям динамического размера или удаления из середины List-у, если сильно волнуешься о количестве и объёме аллокаций :)


Почему «не говоря», что странного и к чему этот загадочный смайлик? Я читал исходники Листа и знаю, что там происходит. Хочешь сказать, List неэффективно реализован? Сам эффективнее не напишешь хотя бы потому, что List<T> использует недоступные остальным интринсики типа `RuntimeHelpers.IsReferenceOrContainsReferences<T>()`.

K>явное требование к дизайну List-а, чтобы он был построен поверх массива и только массива. Это требование, явно проистекающее из нового метода ReleaseBuffer. Сейчас такого требования таки нет.


В документации такое предположение есть. Правда, Стивен Тоуб говорит, что это не считается; тут ему виднее, конечно.

K>Ну и все, что далее этого касается — например, значит, нужно вводить явные требования к тем неявным фичам листа, которые тебя заинтересовали, типа динамического размера (т.к. иначе смысл ReleaseBuffer полностью теряется).


Кроме того, что Лист представлен как префикс массива, длина которого не меньше size — никаких предположений нет.

K>Например, чтобы в порядке вещей было обернуть in place массив в List, пофигачить серию операций вроде добавления/удаления, а потом сделать ReleaseBuffer.


Это нельзя сделать, потому что нарушает инкапсуляцию экземпляра Листа. Это я не предлагаю, это мне не нужно.

K>Есть существующее требование-костыль в листе (также относящееся к оптимизации), близкое, но не тождественное — capacity. Его нужно дружить с предлагаемым тобой — отсюда рождается, для начала, конфликты вида "caller should save actual Count". Т.е. тут еще и на каллера перекладывается некая ответственность, которая проистекает из этого конфликта. И тут еще нужно это женить ("save actual Count" — это имхо несерьезно)...


Это ответственность по использованию уже массива, не самого Листа. Если вдруг decision makers решат вместе с массивом таки возвращать ещё и Count в структуре ArrayPrefix<T> (как ArraySegment<T>, но без offset), я могу это реализовать. Хотя считаю это излишним усложнением, проблемой другого уровня. Это уже можно реализовать методом расширения, в стандарте необязательно.
Глаза у меня добрые, но рубашка — смирительная!
Re: List<T>.MoveToArray()
От: Sinatr Германия  
Дата: 05.11.18 08:38
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Семантика: отказаться от владения низлежащим буфером и передать его наружу.


А что должно произойти с List<T> после этой операции? Он как-то задиспозится и начнет ругаться при вызове своих методов?

Если вы пишете в среде, где аллокации нежелательны (xbox?), то вам придется пол фреймворка переписать. Подозреваю, что кто-то уже это сделал.

Q>Используется как array builder


А почему просто не сделать ArrayBuilder, вместо того, чтобы добавлять какой-то странный метод в повсеместно используемый тип? Засуньте его в namespaces подальше...
---
ПроГLамеры объединяйтесь..
Re[2]: Clear()
От: Qbit86 Кипр
Дата: 05.11.18 08:43
Оценка:
Здравствуйте, Sinatr, Вы писали:

S>А что должно произойти с List<T> после этой операции? Он как-то задиспозится и начнет ругаться при вызове своих методов?


Нет, он просто перейдёт в своё исходное состояние со ссылкой на пустой массив. Наблюдаемое поведение как у `Clear()`.

S>xbox?


Unity.

S>А почему просто не сделать ArrayBuilder


Просто сделать ArrayBuilder, и просто добавить в него остальные методы Листа, чтобы получить функциональность Листа. Получится тот же Лист, только переименованный.
Глаза у меня добрые, но рубашка — смирительная!
Re[17]: ArrayPrefix<T>
От: keenn  
Дата: 05.11.18 08:51
Оценка:
Q>Почему «не говоря», что странного и к чему этот загадочный смайлик? Я читал исходники Листа и знаю, что там происходит. Хочешь сказать, List неэффективно реализован? Сам эффективнее не напишешь хотя бы потому, что List<T> использует недоступные остальным интринсики типа `RuntimeHelpers.IsReferenceOrContainsReferences<T>()`.

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

Q>Это нельзя сделать, потому что нарушает инкапсуляцию экземпляра Листа.


"Инкапсуляция" нарушается в тот момент, когда мы точно знаем, что внутри массив по определению. И он там не для чего-то, а для наших нужд по оптимизации, тоже по официальному определению. И что мы можем эффективно работать с этим массивом с помощью внутренностей листа, а потом получить его через ReleaseBuffer. И этот ReleaseBuffer должен вернуть конкретно тот массив, с которым работал низведенный до обертки лист. Иначе оптимизация не прокатит.

Q>Это я не предлагаю, это мне не нужно.


А что тебе нужно? Разве не работать "эффективно" с массивом через лист, имея возможность получить его впоследствии, не копируя? И нужно это только для легаси? (т.к. если нужна эффективная работа с массивами, используя неповторимый внутренний функционал листа — то разумнее не костыли к листу городить, а определять новый функционал с новыми интерфейсами).

Q>Это ответственность по использованию уже массива, не самого Листа. Если вдруг decision makers решат вместе с массивом таки возвращать ещё и Count в структуре ArrayPrefix<T> (как ArraySegment<T>, но без offset), я могу это реализовать. Хотя считаю это излишним усложнением, проблемой другого уровня. Это уже можно реализовать методом расширения, в стандарте необязательно.


Да нет тут никакого другого уровня. Это проблема дизайна, напрямую связанная с твоим предложением. Которая грозит вполне конкретным гемороем пользователям класса. Которую нужно продумать и предлагать в комплекте с тем, что ты предложил (может в процессе продумывания и предложение бы отвалилось само собой). А не ссылаться на "ну это уже другое, потом думать будем, давайте сначала MoveToArray введем... а! лучше не MoveToArray, а ReleaseBuffer!".
Re[18]: ArrayPrefix<T>
От: Qbit86 Кипр
Дата: 05.11.18 09:19
Оценка:
Здравствуйте, keenn, Вы писали:

K>"Инкапсуляция" нарушается в тот момент, когда мы точно знаем, что внутри массив по определению.


Нет. Шаринг ссылки на живой буфер нарушает инкапсуляцию экземпляра Листа. Отказ от владения буфером — нет.

K>А что тебе нужно? Разве не работать "эффективно" с массивом через лист, имея возможность получить его впоследствии, не копируя?


Для этого не нужно иметь доступ к «живому» буферу.

K>И нужно это только для легаси?


Это нужно для тех случаев, для которых вообще нужен Лист. Или ты хочешь сказать: забудьте про Лист, он не нужен, используйте везде новый ArrayBuilder?

K>т.к. если нужна эффективная работа с массивами, используя неповторимый внутренний функционал листа — то разумнее не костыли к листу городить, а определять новый функционал с новыми интерфейсами.


Т.е. продублировать тот же функционал Листа плюс один метод?

K>Которая грозит вполне конкретным гемороем пользователям класса.


А `Clear()` им грозит геморроем?

K>Которую нужно продумать и предлагать в комплекте с тем, что ты предложил (может в процессе продумывания и предложение бы отвалилось само собой).


Обсуждение это и есть часть продумывания. Оно идёт в комплекте с предложением. Таков процесс фичереквестов в corefx и их ревью. Туда нельзя просто зафигачить пулл-реквест, который сам по себе «до конца» в одиночку «продумал».
Глаза у меня добрые, но рубашка — смирительная!
Re[19]: ArrayPrefix<T>
От: keenn  
Дата: 05.11.18 09:36
Оценка:
Q>Нет. Шаринг ссылки на живой буфер нарушает инкапсуляцию экземпляра Листа. Отказ от владения буфером — нет.

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

Q>Для этого не нужно иметь доступ к «живому» буферу.


И что?

Q>Это нужно для тех случаев, для которых вообще нужен Лист. Или ты хочешь сказать: забудьте про Лист, он не нужен, используйте везде новый ArrayBuilder?


Ну да, теперь наклевывается новое решение — создаем синглтон листа на всё приложение, и используем его для работы с массивами по всему приложению. Его инстанс же можно переиспользовать, не так ли? /s

Лист нужен там, где нужен лист (может быть и нигде, ага). Массив нужен там, где нужен массив.

Q>Т.е. продублировать тот же функционал Листа плюс один метод?


Нет.

Q>А `Clear()` им грозит геморроем?


Нет.
Re[20]: Соломенное чучело
От: Qbit86 Кипр
Дата: 05.11.18 09:53
Оценка:
Здравствуйте, keenn, Вы писали:

K>Явное специфицирование самого наличия буффера как массива с дальнейшим использованием этого факта внешними клиентами


Это уже специфицировано в документации, и на это уже закладывается куча стороннего кода.

Q>>Для этого не нужно иметь доступ к «живому» буферу.

K>И что?

А то, что это не нарушает согласованности внутреннего представления или каких-то инвариантов. В отличие от твоего предложения про «in-place».

K>Ну да, теперь наклевывается новое решение — создаем синглтон листа на всё приложение, и используем его для работы с массивами по всему приложению.


Сначала ты предлагал «MoveToArray(int start, int length, bool noDelete)», потом «обернуть in place массив в List», теперь «синглтон листа на всё приложение». Раз за разом придумываешь себе соломенные чучела, и отважно с ними борешься. Я-то в этой схеме зачем? Зачем ты мне пишешь эти свои пропозалы? Что мне с ними делать? Мне не интересно это обсуждать.
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: List<T>.MoveToArray()
От: _NN_ www.nemerleweb.com
Дата: 05.11.18 10:57
Оценка: +1
Здравствуйте, keenn, Вы писали:

Попробую перевести в более конструктивное русло.
Имеем метод:

var l = new List<int>();

l.Add(1);
l.Add(2);

if (x) l.Add(3);

Stream someStream = GetSomeStream();
someStream.Write(l.ToArray(), 0, l.Length);


В вызове l.ToArray() мы получаем лишнюю копию, от которой и хочет и обойтись Qbit86

На данный момент единственным вариантом является написание своей обёртки над массивом, что и сделано в Rist или ждать пока ArrayBuffer станет доступным всем.

Или полагаться на оптимизацию Enumerable.ToArray
Автор: _NN_
Дата: 04.11.18
:

static class E
{
public static IEnumerable<TResult> ToEnumerable<TResult>(this TResult value)
{
    yield return value;
}
}

// ..

var l = Enumerable.Empty<int>();

l.Concat(1.ToEnumerable());
l.Concat(2.ToEnumerable());

if (x) l.Concat(3.ToEnumerable());

Stream someStream = GetSomeStream();
someStream.Write(l.ToArray(), 0, l.Length);
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: ArrayBuilder<T>.Buffer
От: Qbit86 Кипр
Дата: 05.11.18 11:13
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>...или ждать пока ArrayBuffer станет доступным всем.


С ArrayBuilder<T> свои проблемы, его по всей видимости нельзя сделать одновременно (изменяемой) структурой и безопасным в смысле владения. Мне-то норм, но у людей вызывает сомнение даже передача буфера. Что уж говорить про шаринг буфера.
Глаза у меня добрые, но рубашка — смирительная!
Re: List<T>.MoveToArray()
От: Hardballer  
Дата: 05.11.18 12:17
Оценка: 3 (1) +2
Здравствуйте, Qbit86, Вы писали:

Q>Если это кажется полезным, присоединяйтесь к обсуждению: https://github.com/dotnet/corefx/issues/33169 Пока что собираются предложение завернуть. (Может, и правильно.)


Вредная инновация. По одной простой причине, те, кому действительно критичен перформанс и аллокация, не пользуется стандартной библиотекой коллекций. А устраивать протекание абстракции в стандартной библиотеке, ИМХО, не стоит.
Отредактировано 05.11.2018 12:18 Hardballer . Предыдущая версия .
Re[5]: Capacity
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.11.18 12:51
Оценка:
Здравствуйте, Qbit86, Вы писали:

Можно вспомнить StringBuilder и его ToString https://habr.com/post/172689/
Он прекрасно работал в .Net 2.0
и солнце б утром не вставало, когда бы не было меня
Re: List<T>.MoveToArray()
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.11.18 05:24
Оценка: 3 (1) +1
Здравствуйте, Qbit86, Вы писали:

Q>Скажите, вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке?

Нет, не пригодился бы.
Мат в два хода:
1. Введение этого метода никак не помогает существующей code base, т.к. в ней нет его вызовов.
2. А если нам нужно переписывать существующий codebase, то есть 100500 альтернативных способов, которые работают без расширения сигнатуры основополагающего типа.

Предположение, что "во многих случаях .ToArray() является финалом жизненного цикла списка" нужно обрабатывать по-другому:
1. Обосновать его — например, проведя анализ FCL и публично доступных проектов, благо байт-код легко восстанавливается
2. Оптимизировать существующий ToArray, чтобы он не копировал, а возвращал свой буфер.
3. Возможно, договорившись с командой JIT, чтобы получить операцию усечения существующего массива без переаллокаций.

Вот это было бы полезное предложение, потому что в результате выиграла бы вся та legacy codebase, которая писалась без оглядки на производительность.
А если п.1 у нас не удаётся, то и говорить не о чем: редкие случаи, заслуживающие оптимизации, можно оптимизировать, не ломая основы публичной библиотеки.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: ToArray()
От: Qbit86 Кипр
Дата: 06.11.18 07:29
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>2. Оптимизировать существующий ToArray, чтобы он не копировал, а возвращал свой буфер.


Это не эквивалентная оптимизация, это изменение семантики. Я против шаринга внутреннего буфера, это нарушение инкапсуляции. Мне нужна безопасная семантика передачи буфера. Семантику существующего `ToArray()` точно менять нельзя.

S>3. Возможно, договорившись с командой JIT, чтобы получить операцию усечения существующего массива без переаллокаций.


Это ортогонально обсуждаемому вопросу. Само по себе, может быть, было бы неплохо. Скажем, после или во время передачи буфера его эффективно усечь, чтобы не тащить actualCount. Но сейчас-то доступа к этому буферу нет совсем никакого.

Тем более, что «договариваться» с командой JIT на мой взгляд нереалистично, и вообще у них там всё сложно, BotR нужно читать. А в FCL всё проще, даже я могу дописать нужный код.

S>редкие случаи, заслуживающие оптимизации, можно оптимизировать, не ломая основы публичной библиотеки.


Так и я не предлагаю основы ломать. Я просто хочу добавить тривиальный, но полезный вариант Clear()/Dispose().
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Clear()
От: Sinatr Германия  
Дата: 06.11.18 08:47
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

S>>А почему просто не сделать ArrayBuilder

Q>Просто сделать ArrayBuilder, и просто добавить в него остальные методы Листа, чтобы получить функциональность Листа. Получится тот же Лист, только переименованный.

Нет ничего зазорного в своем типе, реализующем интерфейс, к примеру IList, почему вы до List<T> "докапались"?

List<T> изначально ничего общего с массивами не имеет. То, что он их использует — это implementation details. ToList() — это внешний (extension) метод. Вы же предлагаете засунуть что-то, что нужно в весьма специфическом сценарии, имеющем побочный эффект, прямо в List<T>. Был бы я вашей мамой — отшлепал бы за саму мысль.

Делайте ArrayBuilder, как нормальные люди.
---
ПроГLамеры объединяйтесь..
Re[3]: ToArray()
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.11.18 09:00
Оценка: 3 (1)
Здравствуйте, Qbit86, Вы писали:
Q>Это не эквивалентная оптимизация, это изменение семантики. Я против шаринга внутреннего буфера, это нарушение инкапсуляции. Мне нужна безопасная семантика передачи буфера. Семантику существующего `ToArray()` точно менять нельзя.
То, что вы предлагаете — ещё худшее нарушение инкапсуляции.
S>>3. Возможно, договорившись с командой JIT, чтобы получить операцию усечения существующего массива без переаллокаций.
Q>Это ортогонально обсуждаемому вопросу. Само по себе, может быть, было бы неплохо. Скажем, после или во время передачи буфера его эффективно усечь, чтобы не тащить actualCount. Но сейчас-то доступа к этому буферу нет совсем никакого.
И слава богу. Лучше никакого доступа, чем давать токсичный доступ. Потому, что может не хватить всех ресурсов вселенной, чтобы этот доступ потом забрать.
Q>Тем более, что «договариваться» с командой JIT на мой взгляд нереалистично, и вообще у них там всё сложно, BotR нужно читать. А в FCL всё проще, даже я могу дописать нужный код.
Странно. Вот для Span<T> почему-то договориться удалось — там же устраняются избыточные проверки выхода за диапазон
S>>редкие случаи, заслуживающие оптимизации, можно оптимизировать, не ломая основы публичной библиотеки.
Q>Так и я не предлагаю основы ломать.
То, что вы предлагаете, хуже, чем "ломать". Расширение core функциональности — это разбазаривание ресурсов будущих поколений ради сиюминутной выгоды нынешнего поколения. Сломанный код можно починить; сломанную архитектуру можно только выбросить и написать заново.
Q>Я просто хочу добавить тривиальный, но полезный вариант Clear()/Dispose().
Сам по себе Clear()/Dispose() — очень плохой паттерн. Симптом анти-паттерна: использовать его правильно гораздо сложнее, чем неправильно.


Ещё раз попробую объяснить: по-хорошему, оптимизация таких вещей — забота платформы. Ескейп-анализ, выявление паттернов "передачи владения", устранение избыточных копирований в сценариях list.ToArray(), stringBuilder.ToString(), arrayBuilder.ToArray(), вот это всё.
Потому, что программист в этом плох: либо он не заметит возможности воспользоваться такой семантикой (и получит плохой перформанс), либо увидит её там, где её нет (и получит багу).
Такие решения должна принимать неустанная машина, а не слабонервный кожаный мешок.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Воображаемые абстракции
От: Qbit86 Кипр
Дата: 06.11.18 09:07
Оценка:
Здравствуйте, Sinatr, Вы писали:

S>List<T> изначально ничего общего с массивами не имеет.


Он изначально создавался как аналог non-generic ArrayList. Это конкретная структура данных, а не абстракция. Абстракцией здесь будет IList<T>. Но в связи с соглашением FDG об именовании реализаций библиотечных интерфейсов, этот конкретный тип ArrayList<T> лишился явного префикса «Array-».

«The class List<T> is analogous to the non-generic ArrayList.»
https://msdn.microsoft.com/en-us/library/ms379564.aspx

«It implements the List<T> generic interface by using an array whose size is dynamically increased as required.»
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netstandard-2.0#remarks

То есть всё дело в именовании. Значит, пропозал просто надо переделать: зареквестить новый стандартный тип NothingMoreThanFuckingPlainArrayList<T>, точь в точь клон текущего Листа плюс механика безопасной передачи буфера. Полезный тип был бы, не только мне.

А использование просто легаси Листа должно быть strongly disouraged, конечно, раз его поведение и перформанс-характеристики могут непредсказуемо меняться от версии к версии. Capacity в нём тоже запретить, что ещё за Capacity, утечка абстракций же.

S>Вы же предлагаете засунуть что-то, что нужно в весьма специфическом сценарии, имеющем побочный эффект


Тот же побочный эффект, что и у метода Clear(). Его тоже нужно тоже запретить?

S>Был бы я вашей мамой — отшлепал бы за саму мысль.


Мне нужны эффективные структуры данных, а не в воображаемые абстракции играться. (Лист, кстати, эффективная структура данных, хотя тут знатоки в комментах о «стандартных коллекциях» отзывались пренебрежительно.)

Вообще заход про «маму» и «отшлёпать» это сразу фейл, конечно.
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 06.11.2018 12:19 Qbit86 . Предыдущая версия .
Re[2]: List<T>.MoveToArray()
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 06.11.18 09:51
Оценка:
Здравствуйте, Sinclair, Вы писали:

Можно вспомнить StringBuilder https://habr.com/post/172689/
Там изначально был быстрый ToString

public override string ToString()
    {
      string str = this.m_StringValue;
      if (this.m_currentThread != Thread.InternalGetCurrentThread() || 2 * str.Length < str.ArrayLength) 
        return string.InternalCopy(str);//возвращаем копию строки
      str.ClearPostNullChar();
      this.m_currentThread = IntPtr.Zero;     
      return str; //возвращаем ссылку на текущую строку
    }
и солнце б утром не вставало, когда бы не было меня
Re[3]: StringBuilder
От: Qbit86 Кипр
Дата: 06.11.18 09:58
Оценка: +1
Здравствуйте, Serginio1, Вы писали:

S>Можно вспомнить StringBuilder https://habr.com/post/172689/

S>Там изначально был быстрый ToString

Он может позволить себе вернуть ссылку на шаренную строку, потому что строки в C# неизменны. (Ну, плюс-минус известные утечки абстракций с низкоуровневыми манипуляциями.) В случае Листа/массива нужен независимый массив. Либо копия как в ToArray(), либо передача как в MoveToArray(). Вариант шаринга а-ля GetBuffer() точно не нужен.
Глаза у меня добрые, но рубашка — смирительная!
Re[4]: StringBuilder
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 06.11.18 11:05
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Serginio1, Вы писали:


S>>Можно вспомнить StringBuilder https://habr.com/post/172689/

S>>Там изначально был быстрый ToString

Q>Он может позволить себе вернуть ссылку на шаренную строку, потому что строки в C# неизменны. (Ну, плюс-минус известные утечки абстракций с низкоуровневыми манипуляциями.) В случае Листа/массива нужен независимый массив. Либо копия как в ToArray(), либо передача как в MoveToArray(). Вариант шаринга а-ля GetBuffer() точно не нужен.


Согласенн, тем более нужны лишние проверки. MoveToArray лучше всего. Просто у StringBuilder есть 2 свойства Length и ArrayLength а у Array свойства ArrayLength вроде нет
и солнце б утром не вставало, когда бы не было меня
Re[4]: Ownership, lifetimes
От: Qbit86 Кипр
Дата: 06.11.18 13:15
Оценка:
Здравствуйте, Sinclair, Вы писали:

Q>>Я против шаринга внутреннего буфера, это нарушение инкапсуляции. Мне нужна безопасная семантика передачи буфера. Семантику существующего `ToArray()` точно менять нельзя.

S>То, что вы предлагаете — ещё худшее нарушение инкапсуляции.

Нет. Просто нет.

S>Лучше никакого доступа, чем давать токсичный доступ. Потому, что может не хватить всех ресурсов вселенной, чтобы этот доступ потом забрать.


Это про любой функционал можно сказать.

S>Странно. Вот для Span<T> почему-то договориться удалось — там же устраняются избыточные проверки выхода за диапазон ;)


Странно для простых программистских вещей типа exclusive ownership, lifetime management и move semantics лезть в кишки рантайма вместо библиотечных решений. Вот уж где «токсичный доступ» с необратимыми последствиями.

S>>>редкие случаи, заслуживающие оптимизации, можно оптимизировать, не ломая основы публичной библиотеки.


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

S>То, что вы предлагаете, хуже, чем "ломать". Расширение core функциональности — это разбазаривание ресурсов будущих поколений ради сиюминутной выгоды нынешнего поколения.


По мне так это экономия «ресурсов поколений». Расширение функциональности — это преимущественный путь эволюции фреймворка.

S>Сам по себе Clear()/Dispose() — очень плохой паттерн.


Приехали. `Clear()` не нужен? А `RemoveAt()`?

S>Ескейп-анализ, выявление паттернов "передачи владения", устранение избыточных копирований в сценариях list.ToArray()

S>Такие решения должна принимать неустанная машина, а не слабонервный кожаный мешок.

Это вопрос проектирования языка программирования (в Rust много чего на анализе лайфтаймов завязано), а не постфактум хаков в рантайме. Но в окружающей нас реальности у нас на руках уже существующий C#, и инженерные проблемы приходится решать в рамках него.
Глаза у меня добрые, но рубашка — смирительная!
Re[5]: Воображаемые абстракции
От: Sinatr Германия  
Дата: 06.11.18 13:24
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q> что ещё за Capacity, утечка абстракций же.


Сарказм засчитан. Решил глянуть на MSDN:

Gets or sets the total number of elements the internal data structure can hold without resizing.

и чуть ниже

.. the capacity is increased by automatically reallocating the internal array ..

Пффф.. майкрософт, что ж ты так. Внутренней структуры данных — вот абстракция, а то что это самый обычный массив — это уж как "имплементировалось".

Q>Он изначально создавался как аналог non-generic ArrayList.


Интересно. Тут слово "аналог" очень верное. Просто подобного раньше не было. Если б они (разработчики фрейворка) знали, насколько List<T> cтанет популярен в обиходе рядового программиста, то очень даже возможно наплодили бы кучу ListXXX (ListInt, ListDouble и т.д.) еще в первом фреймворке. Просто видимо не подозревали.

ArrayList был неплох, но постоянные касты и боксинг — увы. А вот List<T> взлетел.

Потому "аналога" не было, ArrayList — да, самый "близкий", но не более.

Не важно как его назвали, важно, как он используется. Можно придумать много маленьких методов, улучшающих List<T>, подобных предлагаемому вами. Получится некий супер клас. Зачем? Лучше пусть каждый создаст свою специализированную структуру данных, оптимизированную под вполне конкретную задачу. Бонус — простота.

Q>Тот же побочный эффект, что и у метода Clear(). Его тоже нужно тоже запретить?


Ну тогда называть надо GetBufferAndClearList(). Имхо, любой метод ToXXX подразумевает создания нового экземпляра чего-то. MoveToXXX — вполне себе неочевидно, что произойдет после вызова.

Q>Вообще заход про «маму» и «отшлёпать» это сразу фейл, конечно.


Когда у мамы кончаются/нет времени на аргументы, а ребенок устойчиво стоит на своем уперт — это просто рабочая схема (совпадения по возрасту, даже если они есть — случайны) Если предложение встречают в штыки, то это то же самое "шлепание". Если со штыками большинство уважаемых коллег — то (еще одна попытка) вашей маме было бы стыдно за вас! Напишите уже ArrayBuilder, не задевайте тонкие натуры рядовых пользователей List<T>!

Вы же согласны, что просто пытаетесь абюзить List<T>, чтобы в конечном итоге получить массив? Вам не нужен List<T>. Это XY проблема, когда вы обсуждаете один из возможных решений задачи, вместо обсуждения самой задачи.
---
ПроГLамеры объединяйтесь..
Re[6]: Standard implementation
От: Qbit86 Кипр
Дата: 06.11.18 13:53
Оценка:
Здравствуйте, Sinatr, Вы писали:

S>Если б они (разработчики фрейворка) знали, насколько List<T> cтанет популярен


Не-не-не, логика наоборот же. Класс как раз и назван List<T>, чтобы стать популярным. Чтобы он воспринимался как дефолтный среди всяких LinkedList<T> и что там ещё:
«✓ DO ensure that the names differ only by the "I" prefix on the interface name when you are defining a class–interface pair where the class is a standard implementation of the interface.»

Его не назвали ArrayList<T> только потому, чтобы подтолкнуть пользователя взять его по умолчанию, не оставлять наедине с кучай равнозначных вариантов типа SomeList<T>.
Его так назвали не для того, чтобы оставить детали реализации неопределёнными.

S>ArrayList был неплох, но постоянные касты и боксинг — увы. А вот List<T> взлетел.


Понятное дело, что с появлением дженериков стали нужны дженерик-коллекции взамен легаси.

S>Потому "аналога" не было, ArrayList — да, самый "близкий", но не более.


Как это не более. Это самый естественный вариант растущей коллекции — враппер простого массива в непрерывной области памяти, дружественный к кэшу, быстрая индексация, и т.п.

S>Не важно как его назвали, важно, как он используется.


Всё так. И используется он как обёртка гарантированно лежащего внутри массива, а не туманная «индексируемая коллекция». Эта его реализация без всяких сомнений навечно прибита гвоздями и попытка её «оптимизировать» неизбежно приведёт к величайшему шит-сторму в истории .NET — негативный наблюдаемый импакт будет обеспечен.

S>Ну тогда называть надо GetBufferAndClearList().


Это ради бога, всё обсуждаемо и на пользу понятности.

S>Имхо, любой метод ToXXX подразумевает создания нового экземпляра чего-то. MoveToXXX — вполне себе неочевидно, что произойдет после вызова.


Окей.

S>Вы же согласны, что просто пытаетесь абюзить List<T>, чтобы в конечном итоге получить массив?


На практике List<T> может использоваться по назначению (кстати, какое у него вообще назначение?), просто в конце цикла своей жизни жертвует свои органы отдаёт свой буфер тем, кому он нужнее.

В текущем проекте нашёл 172 вызова `List<T>.Array()`. Первый в списке выглядит так:
var queue = RequestQueue.ToArray();
RequestQueue.Clear();

(Кстати, не факт, что отказ тут от копирования уменьшит memory pressure; может, дальше список будет перезаполняться, и «догоняться» до прежней Capacity лишними аллокациями.)
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 06.11.2018 13:56 Qbit86 . Предыдущая версия .
Re[7]: Standard implementation
От: _NN_ www.nemerleweb.com
Дата: 06.11.18 14:02
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>В текущем проекте нашёл 172 вызова `List<T>.Array()`. Первый в списке выглядит так:

Q>
Q>var queue = RequestQueue.ToArray();
Q>RequestQueue.Clear();
Q>


Если этот код собственный, то почему не заменить List на MyList с нужным функционалом, где ToArray/GetArrayAndClear отдаст внутренний массив ?
Или использовать Enumerable и получить оптимизацию ToArray уже сейчас ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[8]: Enumerable
От: Qbit86 Кипр
Дата: 06.11.18 14:15
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Если этот код собственный


Это код какого-то стороннего говнокласса, просто дропнутый в Unity-проект виде исходников. Короче, не важно. Ни .netstandard-библиотеки, ни даже маловероятное включение ReleaseBufferAndClear() в ближайшее время вряд ли как-то повлияет на этот проект.

_NN>Или использовать Enumerable и получить оптимизацию ToArray уже сейчас ?


А что там? Когда я читал исходники Enumerable в последний раз, там всё так же была пресловутая «последняя копия» (для обрезания накопительного буфера до фактического размера перед возвратом массива пользователю). И ToList() в этом смысле был эффективнее ToArray() для получение индексируемой колекции, потому что «последней копии» не требовал.

Было бы неплохо, возможно, если бы в LINQ to Objects был ToArrayPrefix() или ToArraySegment(), чтобы избежать «последней копии», а просто вернуть буфер внутреннего ArrayBuilder'а. Но в любом случае использование Linq в общем случае порождает ненужные аллокации.
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 06.11.2018 14:24 Qbit86 . Предыдущая версия .
Re[5]: Ownership, lifetimes
От: Sinclair Россия https://github.com/evilguest/
Дата: 06.11.18 14:25
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Это про любой функционал можно сказать.

Конечно. Именно поэтому все фичи начинают с отрицательного score, и только путём упорной работы с требованиями заслуживают шанс на реализацию.
Странно, что вам это не очевидно.
Q>Странно для простых программистских вещей типа exclusive ownership, lifetime management и move semantics лезть в кишки рантайма вместо библиотечных решений. Вот уж где «токсичный доступ» с необратимыми последствиями.
У вас какое-то искажённое восприятие реальности. Если нам не понравилась реализация JIT, то мы просто меняем её и всё, без необходимости бегать по исходникам сотен проектов и переписывать их код.
Non-intrusive improvements, всё такое.
Что вы будете делать, когда вы поймёте, что этот метод — неудачный? Есть план по выводу его из обращения?

Q>Я изучил соответствующие гайдлайны corefx, это не входит в список ломающих изменений.

Думайте о будущем. Убирание вашего метода обратно будет ломающим изменением.

Q>По мне так это экономия «ресурсов поколений». Расширение функциональности — это преимущественный путь эволюции фреймворка.

Расширение должно быть оправданным, а не просто "я подумал, что это будет прикольно".

S>>Сам по себе Clear()/Dispose() — очень плохой паттерн.


Q>Приехали. `Clear()` не нужен? А `RemoveAt()`?

Не нужен Dispose(). Если вы не понимаете, почему — то вам пока рано сабмиттить пропозалы по улучшению фреймворка.

Q>Это вопрос проектирования языка программирования (в Rust много чего на анализе лайфтаймов завязано), а не постфактум хаков в рантайме.

Кто это вам сказал? Компилятор С++ и Java JIT с вами не согласны.

Q>Но в окружающей нас реальности у нас на руках уже существующий C#, и инженерные проблемы приходится решать в рамках него.

Для начала хотелось бы увидеть обоснование наличия инженерной проблемы — какие-то синтетические тесты, где видно, как офигенно бы выиграл идиоматический реальный код, если бы в нём был интересный вам метод.
Пока что это выглядит как экономия на спичках, ради которой добавляется ажно целый новый метод, с феерической семантикой, и в третий по частоте использования класс во всём фреймворке.
Ничуть не лучше, чем string.IsPalindrome().

Получив внятный список сценариев, которые типа выиграют от этого нового метода, можно рассуждать об альтернативных методах борьбы с этими сценариями.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Enumerable
От: _NN_ www.nemerleweb.com
Дата: 06.11.18 14:26
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, _NN_, Вы писали:


_NN>>Если этот код собственный


Q>Это код какого-то стороннего говнокласса, просто дропнутый в Unity-проект виде исходников. Короче, не важно. Ни .netstandard-библиотеки, ни даже маловероятное включение ReleaseBufferAndClear() в ближайшее время вряд ли как-то повлияет на этот проект.

Ну так в коде вставляем вначале свой List и радуемся.

using List = MyListWithToArrayOptimization; 

... // код


_NN>>Или использовать Enumerable и получить оптимизацию ToArray уже сейчас ?


Q>А что там? Когда я читал исходники Enumerable в последний раз, там всё так же была пресловутая «последняя копия» (для обрезания накопительного буфера до фактического размера перед возвратом массива пользователю). И ToList() в этом смысле был эффективнее ToArray() для получение индексируемой колекции, потому что «последней копии» не требовал.

Я уже писал в теме, что для Enumerable есть оптимизация и ToArray не должен делать копию, то бишь Enumerable.ToArray() != Enumerable.ToList().ToArray().
Возможно зависит от версии фреймворка, стоит уточнить тестами и просмотром кода.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[10]: Последняя копия
От: Qbit86 Кипр
Дата: 06.11.18 14:34
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Я уже писал в теме, что для Enumerable есть оптимизация и ToArray не должен делать копию, то бишь Enumerable.ToArray() != Enumerable.ToList().ToArray().


А как он работает? То, что я видел: (Large?)ArrayBuilder<T> со стратегией удвоения. В том пути выполнения, где заранее он не знает количества элементов: он останавливается на буфере какого-то размера, потом аллоцирует массив посчитанной длины, и копирует из большего буфера в подходящий. Если не аллоцировать вконце, то откуда брать буфер точной но заранее неизвестной длины?
Глаза у меня добрые, но рубашка — смирительная!
Re[11]: Последняя копия
От: _NN_ www.nemerleweb.com
Дата: 06.11.18 16:18
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, _NN_, Вы писали:


_NN>>Я уже писал в теме, что для Enumerable есть оптимизация и ToArray не должен делать копию, то бишь Enumerable.ToArray() != Enumerable.ToList().ToArray().


Q>А как он работает? То, что я видел: (Large?)ArrayBuilder<T> со стратегией удвоения. В том пути выполнения, где заранее он не знает количества элементов: он останавливается на буфере какого-то размера, потом аллоцирует массив посчитанной длины, и копирует из большего буфера в подходящий. Если не аллоцировать вконце, то откуда брать буфер точной но заранее неизвестной длины?


Да, последняя копия нужна, не подумал про это.
А как MoveToArray решает проблему последней копии ?
Разе что предполагается, что среда исполнения сможет взять массив одной длинны и уменьшить его длину.
Тогда тут надо в самом фрейморке иметь внутренний метод типа InternalArray.SetLength, использовать везде ArrayBuilder, а List по все видимости можно даже и не трогать.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[12]: Хвост
От: Qbit86 Кипр
Дата: 06.11.18 16:30
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>А как MoveToArray решает проблему последней копии ?


Никак, передаёт сырой буфер с неиспользуемым «хвостом». Возвращаемый буфер можно завернуть в кортеж (array, count) (типа ArraySegment<T> но без offset). В любом случае фактическое количество элементов в буфере можно получить до разрушения Листа.

В том и суть, что большинство API, получающие низкоуровневый array, не закладываются на его длину, а имеют перегрузки, принимающие count. Поэтому им чистый массив не нужен, достаточно грязного буфера.
Глаза у меня добрые, но рубашка — смирительная!
Re[13]: Хвост
От: _NN_ www.nemerleweb.com
Дата: 06.11.18 19:12
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Никак, передаёт сырой буфер с неиспользуемым «хвостом». Возвращаемый буфер можно завернуть в кортеж (array, count) (типа ArraySegment<T> но без offset). В любом случае фактическое количество элементов в буфере можно получить до разрушения Листа.


Q>В том и суть, что большинство API, получающие низкоуровневый array, не закладываются на его длину, а имеют перегрузки, принимающие count. Поэтому им чистый массив не нужен, достаточно грязного буфера.


Т.е. метод должен вернуть массив и длину, а не только массив или как получить эту длину ?
И что делать с методами, которые не получает длину ?

Контракт ToArray это вернуть массив правильной длины.
Вы предлагаете другой контракт, значит и должен быть другой метод.

Может вам нужен что-то вроде Enumerable.ToSpan ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[14]: Длина
От: Qbit86 Кипр
Дата: 06.11.18 21:01
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Т.е. метод должен вернуть массив и длину, а не только массив или как получить эту длину ?


int count = list.Count;
string[] array = list.MoveToArray();
Debug.Assert(count <= array.Length);
ReadOnlySpan<string> span = array.AsSpan(0, count);

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

_NN>И что делать с методами, которые не получает длину ?


Им передавать частично заполненный буфер нельзя. Но я таких методов в стандартной библиотеке не припомню; все, что встречал, также имеют и перегрузку (array, start, count) — именно для таких сценариев.

_NN>Контракт ToArray это вернуть массив правильной длины.

_NN>Вы предлагаете другой контракт, значит и должен быть другой метод.

Так я и предлагаю другой, отдельный метод. Я не говорю менять существующий копирующий ToArray().

_NN>Может вам нужен что-то вроде Enumerable.ToSpan ?


Я не уверен, что `ToSpan()` можно непротиворечиво прикрутить к Листу: https://github.com/dotnet/corefx/issues/19814
Глаза у меня добрые, но рубашка — смирительная!
Re[6]: Меняем JIT
От: Qbit86 Кипр
Дата: 07.11.18 08:42
Оценка:
Здравствуйте, Sinclair, Вы писали:

Q>>Это про любой функционал можно сказать.

S>Конечно. Именно поэтому все фичи начинают с отрицательного score, и только путём упорной работы с требованиями заслуживают шанс на реализацию.
S>Странно, что вам это не очевидно.

А, ну это ок. А то могло создаться впечатление, что пространные рассуждения про необратимость расширения API относятся именно к этому методу.

Q>>Странно для простых программистских вещей типа exclusive ownership, lifetime management и move semantics лезть в кишки рантайма вместо библиотечных решений. Вот уж где «токсичный доступ» с необратимыми последствиями.

S>У вас какое-то искажённое восприятие реальности. Если нам не понравилась реализация JIT, то мы просто меняем её и всё, без необходимости бегать по исходникам сотен проектов и переписывать их код.

Вы молодцы! Но некоторые пишут код для пользователей, которым нельзя навязать свою версию рантайма с кастомным JIT'ом. Непонимание такого простого факта и есть «искажённое восприятие реальности».

S>Non-intrusive improvements, всё такое.


Нифига себе, non-intrusive. Если на каждый чих протаскивать в стандарт изменения рантайма, это максимально intrusive.

S>Что вы будете делать, когда вы поймёте, что этот метод — неудачный? Есть план по выводу его из обращения?


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

S>Думайте о будущем. Убирание вашего метода обратно будет ломающим изменением.


Убирание любого публичного метода, а не только обсуждаемого, будет ломающим.

S>Расширение должно быть оправданным, а не просто "я подумал, что это будет прикольно".


Я считаю, что это оправдано. Овнеры репозитория corefx принимают решение, кажется ли им оно оправданным.

S>Не нужен Dispose(). Если вы не понимаете, почему — то вам пока рано сабмиттить пропозалы по улучшению фреймворка.


У тебя какие-то отфонарные критерии, для позволения мне сабмитить пропозалы или нет. Какие ещё пререквизиты, расскажи? Безоговорочное неприятие `Dispose()`, это раз. Что ещё? Чтоб сразу всем списком. И предложи овнерам репозитория corefx добавить это в их список требований к пропозалам, чтоб в одном месте. Скинь ссылку на discussion только.

Q>>Это вопрос проектирования языка программирования (в Rust много чего на анализе лайфтаймов завязано), а не постфактум хаков в рантайме.

S>Кто это вам сказал? Компилятор С++ и Java JIT с вами не согласны.

Про Java не знаю. А в C++ так, как я сказал. Например, с прошлого века там была RVO. И была она, конечно, ненадёжная и непереносимая кросскопиляторно. И неявно ломалась при стечении разных обстоятельств. И всю дорогу педалировалась рекомендация не возвращать из функций большие объекты в надежде на RVO. Чтоб регламентировать и оптимизировать сопутствующие сценарии, добавили явные языковые средства (rvalue-ссылки, move-конструкторы) и библиотечные (std::move, std::forward и друзья). А не прикручивали в стандарт требования к рантайму и кодогенерации с навязыванием escape-анализа и т.д.

S>Ничуть не лучше, чем string.IsPalindrome().


`string.IsPalindrome()` может быть реализован поверх существующего API Строки. `List<T>.ReleaseBuffer()` не может быть реализован поверх существующего API Листа.

Мне, например, нужен метод `SwapRemove()` как в стандартном векторе Rust. Но я могу его реализовать поверх API Листа, поэтому добавление во фреймворк не предлагаю. А сброс буфера не могу, поэтому предлагаю.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: Меняем JIT
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.11.18 08:52
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Вы молодцы! Но некоторые пишут код для пользователей, которым нельзя навязать свою версию рантайма с кастомным JIT'ом. Непонимание такого простого факта и есть «искажённое восприятие реальности».

То есть новую версию FCL вы навязать можете, а рантайм — нет?
По-моему, ваш сценарий становится всё более и более надуман.

S>>Non-intrusive improvements, всё такое.

Q>Нифига себе, non-intrusive. Если на каждый чих протаскивать в стандарт изменения рантайма, это максимально intrusive.
Ну почему же "на каждый". Просто некоторые оптимизации лучше делать на уровне рантайма, а не костыльными решениями.
Q>Расскажи, что ты предполагаешь услышать?
Я предполагаю услышать "ок, я понял, идея была плохая".

Q>Вот есть в стандарте метод TrimExcess(), который я считаю неудачным. Как его выводить из обращения? Какой план?

Нет никакого плана. В том-то и проблема — энергичные недальновидные люди вхреначили этот метод, и теперь нам с ним жить всю жизнь.
Q>Убирание любого публичного метода, а не только обсуждаемого, будет ломающим.
Вот видите, вы начинаете понимать.
Q>Я считаю, что это оправдано. Овнеры репозитория corefx принимают решение, кажется ли им оно оправданным.
Ну, вы же спрашивали мнение публики? Я вам своё мнение привожу. И его разделяют ещё многие здесь.

Q>У тебя какие-то отфонарные критерии, для позволения мне сабмитить пропозалы или нет. Какие ещё пререквизиты, расскажи? Безоговорочное неприятие `Dispose()`, это раз. Что ещё?

Почему "позволение"? Покупать и употреблять алкоголь в РФ можно любому, кому исполнилось 18. Тем не менее, многим этого делать не стоит.
Q>Чтоб сразу всем списком. И предложи овнерам репозитория corefx добавить это в их список требований к пропозалам, чтоб в одном месте. Скинь ссылку на discussion только.
Списка, к сожалению, нет. Есть общее пожелание "способности прогнозировать последствия предложенных действий", и не только положительных.

Q>Про Java не знаю. А в C++ так, как я сказал. Например, с прошлого века там была RVO. И была она, конечно, ненадёжная и непереносимая кросскопиляторно. И неявно ломалась при стечении разных обстоятельств. И всю дорогу педалировалась рекомендация не возвращать из функций большие объекты в надежде на RVO. Чтоб регламентировать и оптимизировать сопутствующие сценарии, добавили явные языковые средства (rvalue-ссылки, move-конструкторы) и библиотечные (std::move, std::forward и друзья). А не прикручивали в стандарт требования к рантайму и кодогенерации с навязыванием escape-анализа и т.д.

Вот видите — вы ровно это и описываете: сначала это была внутренняя оптимизация компилятора. Много лет была.

S>>Ничуть не лучше, чем string.IsPalindrome().


Q>`string.IsPalindrome()` может быть реализован поверх существующего API Строки. `List<T>.ReleaseBuffer()` не может быть реализован поверх существующего API Листа.

Может, но неэффективно. Для эффективной реализации проверки на палиндромность надо бы дать доступ к байт-буферу внутри строки.
У вас же тоже есть вполне себе годный .ToArray().
Q>Мне, например, нужен метод `SwapRemove()` как в стандартном векторе Rust. Но я могу его реализовать поверх API Листа, поэтому добавление во фреймворк не предлагаю. А сброс буфера не могу, поэтому предлагаю.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: Байтовые буферы
От: Qbit86 Кипр
Дата: 07.11.18 09:26
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То есть новую версию FCL вы навязать можете, а рантайм — нет?


Я предлагаю точечное добавление в стандартную библиотеку. На тех, кто не использует этот функционал, добавление никак не повлияет. Ты же предлагаешь патчить рантайм escape-анализом и решать «задачу» в общем случае. Это затронет всех, кто обновит рантайм — независимо от того, надо оно им или нет. Не говоря уже о том, что трудоёмкость и багоёмкость такой переделки рантайма на три-четыре десятичных порядка больше, чем добавление предложенного метода. И предсказать последствия и оценить риски гораздо, ГОРАЗДО сложнее. Это к слову про упомянутую тобой «способность прогнозировать последствия предложенных действий». Предлагая просто пропатчить рантайм ты таковую способность не демонстрируешь.

S>Ну почему же "на каждый". Просто некоторые оптимизации лучше делать на уровне рантайма, а не костыльными решениями.


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

S>Я предполагаю услышать "ок, я понял, идея была плохая".


Ок, я тебе больше скажу, если вдруг подобный метод примут, и он не зайдёт, я лично запилю PR с добавлением атрибута [Obsolete("Ок, идея оказалась плохая")].
Но лучше, чтоб это выяснилось на уровне обсуждения, конечно, до внедрения.

Q>>Я считаю, что это оправдано. Овнеры репозитория corefx принимают решение, кажется ли им оно оправданным.

S>Ну, вы же спрашивали мнение публики? Я вам своё мнение привожу. И его разделяют ещё многие здесь.

Ты процитировал фразу в отрыве от контекста — предыдущей реплики. Это просто был ответ на «Расширение должно быть оправданным».

Q>>А в C++ так, как я сказал. Например, с прошлого века там была RVO. И была она, конечно, ненадёжная и непереносимая кросскопиляторно. И неявно ломалась при стечении разных обстоятельств. И всю дорогу педалировалась рекомендация не возвращать из функций большие объекты в надежде на RVO. Чтоб регламентировать и оптимизировать сопутствующие сценарии, добавили явные языковые средства (rvalue-ссылки, move-конструкторы) и библиотечные (std::move, std::forward и друзья). А не прикручивали в стандарт требования к рантайму и кодогенерации с навязыванием escape-анализа и т.д.


S>Вот видите — вы ровно это и описываете: сначала это была внутренняя оптимизация компилятора. Много лет была.


И полагаться на неё было нельзя.

Q>>`string.IsPalindrome()` может быть реализован поверх существующего API Строки. `List<T>.ReleaseBuffer()` не может быть реализован поверх существующего API Листа.

S>Может, но неэффективно. Для эффективной реализации проверки на палиндромность надо бы дать доступ к байт-буферу внутри строки.

Может, именно поэтому новые API для работы с текстом в стандартной библиотеке оперируют — о ужас — байтовыми буферами? :)
Глаза у меня добрые, но рубашка — смирительная!
Re[9]: Байтовые буферы
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.11.18 10:13
Оценка:
Здравствуйте, Qbit86, Вы писали:
Q>Я предлагаю точечное добавление в стандартную библиотеку. На тех, кто не использует этот функционал, добавление никак не повлияет.
Вот именно. То есть никаких бенефитов никто, кроме пары-тройки фанатов, не получит. Зато маинтейнить это добро надо годами.
Более того, это "улучшение" потом возможно придётся откатывать — когда появятся причины сделать внутри List<T> не плоский массив, а список массивов a-la rope.
Будет как с интернированием строк: метод есть, фичи — нету.

Q>Ты же предлагаешь патчить рантайм escape-анализом и решать «задачу» в общем случае. Это затронет всех, кто обновит рантайм — независимо от того, надо оно им или нет.

Конечно. И это — замечательно: улучшения доедут до всех пользователей, а не только тех, которых обслуживают фанаты перформанса.

Q>Не говоря уже о том, что трудоёмкость и багоёмкость такой переделки рантайма на три-четыре десятичных порядка больше, чем добавление предложенного метода.


Q>И предсказать последствия и оценить риски гораздо, ГОРАЗДО сложнее. Это к слову про упомянутую тобой «способность прогнозировать последствия предложенных действий». Предлагая просто пропатчить рантайм ты таковую способность не демонстрируешь.
Я вижу, что вы не следите за моей мыслью. Да, у меня ограничены прогностические способности. Но если через год после внедрения предложенного мной изменения мы обнаружим в нём фатальный недостаток, то у меня уже готов план отступления. Мы просто выпилим эту оптимизацию из новой версии JIT, и всё вернётся на круги своя. Мне не потребуется бегать за сотней приложений, которые пользуются десятком библиотек, зависящих от неудачного метода.

S>>Ну почему же "на каждый". Просто некоторые оптимизации лучше делать на уровне рантайма, а не костыльными решениями.

Q>Наоборот, патч рантайма — это костыльный оверинжиниринг там, где достаточно конкретного решения на подходящем уровне.
На мой взгляд, предлагаемое конкретное решение — плохое.
S>>Я предполагаю услышать "ок, я понял, идея была плохая".
Q>Ок, я тебе больше скажу, если вдруг подобный метод примут, и он не зайдёт, я лично запилю PR с добавлением атрибута [Obsolete("Ок, идея оказалась плохая")].
Это и есть ваш план отступления? К сожалению, между навешиванием атрибута и реальным выпиливанием из фреймворка могут пройти десятилетия.
Q>Но лучше, чтоб это выяснилось на уровне обсуждения, конечно, до внедрения.
В жизни так не бывает. Вон, в Java были приняты решения, о которых потом пожалели. В дотнете от некоторых из них отказались, но сделали свои ошибки.
Вы так легко предлагаете увеличить площадь поверхности... Я продакт менеджером работаю не первый год. И неоднократно наблюдал, каких чудовищных денег и усилий стоит избавление от фич, ставших ненужными.

Q>И полагаться на неё было нельзя.

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

Q>>>`string.IsPalindrome()` может быть реализован поверх существующего API Строки. `List<T>.ReleaseBuffer()` не может быть реализован поверх существующего API Листа.

S>>Может, но неэффективно. Для эффективной реализации проверки на палиндромность надо бы дать доступ к байт-буферу внутри строки.

Q>Может, именно поэтому новые API для работы с текстом в стандартной библиотеке оперируют — о ужас — байтовыми буферами?

Ну да. Именно поэтому. Внезапно оказалось, что прибивать полезные методы гвоздями к классу string — плохая идея.

Чтобы серъёзно обсуждать библиотечные изменения, нужно иметь примеры (желательно реального) кода, который мы хотим улучшить.
Потом мы можем подумать, как бы этот код выглядел в идеальном случае. И только тогда можно придумывать какие-то решения.

Пока что вы упорно отказываетесь показать реальный код, который бы получил измеримые преимущества, не потеряв в читаемости.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Легко предлагать
От: Qbit86 Кипр
Дата: 07.11.18 12:42
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Более того, это "улучшение" потом возможно придётся откатывать — когда появятся причины сделать внутри List<T> не плоский массив, а список массивов a-la rope.


«...И используется он как обёртка гарантированно лежащего внутри массива, а не туманная «индексируемая коллекция». Эта его реализация без всяких сомнений навечно прибита гвоздями и попытка её «оптимизировать» неизбежно приведёт к величайшему шит-сторму в истории .NET — негативный наблюдаемый импакт будет обеспечен.»
Автор: Qbit86
Дата: 06.11.18


S>Но если через год после внедрения предложенного мной изменения мы обнаружим в нём фатальный недостаток, то у меня уже готов план отступления.


Вы — это кто? Разработчики официального рантайма .NET для всех или разработчики кастомного рантайма .NET для ограниченной аудитории непрограммистов?

S>Мы просто выпилим эту оптимизацию из новой версии JIT, и всё вернётся на круги своя.


И почему твои рассуждения о ломающих изменениях тут перестают быть применимы? Ведь за это время кто-то завяжется на новое поведение с escape-анализом, и откат не «вернёт всё на круги своя», а тупо дропнет им перформанс?

S>На мой взгляд, предлагаемое конкретное решение — плохое.


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

S>Это и есть ваш план отступления? К сожалению, между навешиванием атрибута и реальным выпиливанием из фреймворка могут пройти десятилетия.


Всё так. На эти риски идут, добавляя любое расширение функционала.

S>Вы так легко предлагаете увеличить площадь поверхности...


Я не предлагаю это легко. Я предложил самое простое решение с минимумом взаимодействия с остальными частями фреймворка — предварительно изучив актуальную имплементацию Листа на предмет реализуемости. Так например, предлагаемый возврат пары (array, count) реализовать уже ГОРАЗДО СЛОЖНЕЕ, чем приведённый в шапке пропозала вариант. Туда окажется вовлечённым много типов и других частей фреймворка. Хотя это на первый взгляд незаметно.

S>Пока что вы упорно отказываетесь показать реальный код, который бы получил измеримые преимущества, не потеряв в читаемости.


Упоминал уже в треде: http://rsdn.org//forum/dotnet/7292465
Автор: Sinclair
Дата: 07.11.18


S>Кто это вам сказал? Компилятор С++ и Java JIT с вами не согласны.


Это мне сказал Страуструп: «Prefer library solutions over language changes if feasible » (В пейпере эта фраза приводится просто со ссылкой на Страуструпа, а не как его точная цитата. Но я уверен, что встречал это и в D&E C++ или в TC++PL — это общеизвестный принцип развития языка.)
Глаза у меня добрые, но рубашка — смирительная!
Re[11]: Легко предлагать
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.11.18 14:37
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>«...И используется он как обёртка гарантированно лежащего внутри массива, а не туманная «индексируемая коллекция». Эта его реализация без всяких сомнений навечно прибита гвоздями и попытка её «оптимизировать» неизбежно приведёт к величайшему шит-сторму в истории .NET — негативный наблюдаемый импакт будет обеспечен.»
Автор: Qbit86
Дата: 06.11.18

А можно как-то обосновать это смелое утверждение? А то я тоже могу сам на себя ссылаться.

S>>Но если через год после внедрения предложенного мной изменения мы обнаружим в нём фатальный недостаток, то у меня уже готов план отступления.

Q>Вы — это кто? Разработчики официального рантайма .NET для всех или разработчики кастомного рантайма .NET для ограниченной аудитории непрограммистов?
Мы — это комьюнити, естественно.
S>>Мы просто выпилим эту оптимизацию из новой версии JIT, и всё вернётся на круги своя.
Q>И почему твои рассуждения о ломающих изменениях тут перестают быть применимы? Ведь за это время кто-то завяжется на новое поведение с escape-анализом, и откат не «вернёт всё на круги своя», а тупо дропнет им перформанс?
Потому, что изменение перформанса не является ломающим изменением. Семантика остаётся той же самой, а количество аллокаций заметить без тщательного профилирования невозможно.
Я в пятый примерно раз прошу привести пример кода, который прямо умирает-умирает от того, что ToArray() возвращает копию, а не сам буфер. Без него, вообще-то, говорить даже теоретически не о чем.

Q>Всё так. На эти риски идут, добавляя любое расширение функционала.

Не, не любое. А только то, польза от которого превышает этот риск.

S>>Вы так легко предлагаете увеличить площадь поверхности...

Q>Я не предлагаю это легко. Я предложил самое простое решение с минимумом взаимодействия с остальными частями фреймворка — предварительно изучив актуальную имплементацию Листа на предмет реализуемости. Так например, предлагаемый возврат пары (array, count) реализовать уже ГОРАЗДО СЛОЖНЕЕ, чем приведённый в шапке пропозала вариант. Туда окажется вовлечённым много типов и других частей фреймворка. Хотя это на первый взгляд незаметно.
Ну, вообще-то — нет. Кто вам может помешать добавить out-аргумент для массива, а из метода возвращать count?
Понятно, что более естественный вариант — это возвращать ArraySegment. Потому что он для этого и предназначен. И его нетрудно дальше передать как в API, нативно поддерживающий ArraySegment, так и в API, который ожидает (array, offset, count). Такое решение чуть-чуть легче использовать правильно, чем ваше предложение. Но оно всё ещё недостаточно хорошо, чтобы заслужить реализацию.

Q>Упоминал уже в треде: http://rsdn.org//forum/dotnet/7292465
Автор: Sinclair
Дата: 07.11.18

Вы ссылаетесь на моё сообщение. В нём примеров кода нет.

Q>Это мне сказал Страуструп: «Prefer library solutions over language changes if feasible » (В пейпере эта фраза приводится просто со ссылкой на Страуструпа, а не как его точная цитата. Но я уверен, что встречал это и в D&E C++ или в TC++PL — это общеизвестный принцип развития языка.)

Всё правильно дяденька говорит. Но ещё лучше, чем library solutions, работают улучшения компилятора.
Хороший язык — как раз такой, который позволяет написать хороший компилятор.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: Легко предлагать
От: Qbit86 Кипр
Дата: 07.11.18 16:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

Q>>«...попытка её «оптимизировать» неизбежно приведёт к величайшему шит-сторму в истории .NET — негативный наблюдаемый импакт будет обеспечен.»

S>А можно как-то обосновать это смелое утверждение? А то я тоже могу сам на себя ссылаться.

Что именно обосновать? Что непрерывная память более cache friendly и обеспечивает более быструю индексацию, чем чанки? Ну не знаю. Например: https://lwn.net/Articles/250967/
Лист — как ты сказал? — третий по частоте использования класс в .NET? Если вдруг с обновлением/«оптимизацией» вместо быстрой индексации и contiguous memory мы получим чанки (что само по себе увеличит нагрузку на GC, потому что удерживаемых управляемых объектов будет больше) — определённо это заметит много заинтересованных в производительности людей.

S>Потому, что изменение перформанса не является ломающим изменением. Семантика остаётся той же самой, а количество аллокаций заметить без тщательного профилирования невозможно.




Вырезка из профилировщика Unity. Коричневый пик здесь — пробуждение сборщика мусора. Сборщик мусора в Unity, прямо скажем, не очень. (По крайней мере в той старой версии, что мы используем; но и в текущей, насколько мне известно, тоже.) Он без поколений и без перемещений. Каждый такой запуск сборщика — это подлагивание у игрока в мобильной 3D-игре. Естественно желание такие пики сделать реже. А если вдруг они станут чаще, то это будет заметным на глаз. Реальность, данная нам в ощущениях.

S>Кто вам может помешать добавить out-аргумент для массива, а из метода возвращать count?


Это — допустимый вариант. Подобный TryReleaseBuffer(), например, похож на существующие прецеденты вроде MemoryStream.TryGetBuffer(ArraySegment<Byte>)

S>Понятно, что более естественный вариант — это возвращать ArraySegment. Потому что он для этого и предназначен.


Я бы предпочёл отдельный ArrayPrefix<T> (урезанный ArraySegment<T>). Этот тип сам по себе полезен в сценариях, скажем, где от аллокаций массива переходим к извлечению их из ArrayPool<T> (где вернуться может более длинный массив).

S>И его нетрудно дальше передать как в API, нативно поддерживающий ArraySegment, так и в API, который ожидает (array, offset, count).


Всё так.

S>Такое решение чуть-чуть легче использовать правильно, чем ваше предложение.


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

S>Но оно всё ещё недостаточно хорошо, чтобы заслужить реализацию.


А что нужно сделать, чтобы было достаточно хорошо? (Менять рантайм не в счёт.)

S>Вы ссылаетесь на моё сообщение. В нём примеров кода нет.


Это артефакт копипасты, попробую ещё раз: http://rsdn.org/forum/dotnet/7291686
Автор: Qbit86
Дата: 06.11.18
Глаза у меня добрые, но рубашка — смирительная!
Re[13]: Легко предлагать
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.11.18 17:11
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Что именно обосновать? Что непрерывная память более cache friendly и обеспечивает более быструю индексацию, чем чанки? Ну не знаю. Например: https://lwn.net/Articles/250967/

Обосновать возникновение шитсторма. Напальцевые рассуждения про эффективность непрерывной памяти не интересуют.
Интересуют реальные измерения. Вдруг окажется, что экономия в AddRange и RemoveAt перевешивает в реальных сценариях проигрыш индексации?

Q>Лист — как ты сказал? — третий по частоте использования класс в .NET? Если вдруг с обновлением/«оптимизацией» вместо быстрой индексации и contiguous memory мы получим чанки (что само по себе увеличит нагрузку на GC, потому что удерживаемых управляемых объектов будет больше) — определённо это заметит много заинтересованных в производительности людей.

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

S>>Потому, что изменение перформанса не является ломающим изменением. Семантика остаётся той же самой, а количество аллокаций заметить без тщательного профилирования невозможно.


Q>Image: 20-profiling-cropped.png


Q>Вырезка из профилировщика Unity. Коричневый пик здесь — пробуждение сборщика мусора. Сборщик мусора в Unity, прямо скажем, не очень. (По крайней мере в той старой версии, что мы используем; но и в текущей, насколько мне известно, тоже.) Он без поколений и без перемещений. Каждый такой запуск сборщика — это подлагивание у игрока в мобильной 3D-игре. Естественно желание такие пики сделать реже. А если вдруг они станут чаще, то это будет заметным на глаз. Реальность, данная нам в ощущениях.

И? Вы же понимаете, что те же самые чанки при большом количестве Add дадут меньшую нагрузку на GC — потому, что к моменту окончания Add будет меньше "выброшенной" памяти?
Почему вы думаете, что суммарный эффект будет отрицательным?
Почему вы думаете, что аллокации в List<T>.ToArray() вообще дают заметный вклад в перформанс вашей 3d-игры?

Если у вас есть технические аргументы (то есть измерения), то велком.

S>>Понятно, что более естественный вариант — это возвращать ArraySegment. Потому что он для этого и предназначен.


Q>Я бы предпочёл отдельный ArrayPrefix<T> (урезанный ArraySegment<T>). Этот тип сам по себе полезен в сценариях, скажем, где от аллокаций массива переходим к извлечению их из ArrayPool<T> (где вернуться может более длинный массив).

На мой вкус, этот сценарий не заслуживает отдельного типа — зачем? Чем именно так интересен потребителю ArraySegment с Offset == 0, что нужен целый отдельный тип для него?
Q>Я это рассматриваю как один из вариантов предлагаемого. Не как какой-то отдельный способ; это я уже предлагал как альтернативу в пропозале.
S>>Но оно всё ещё недостаточно хорошо, чтобы заслужить реализацию.
Q>А что нужно сделать, чтобы было достаточно хорошо? (Менять рантайм не в счёт.)
Нужен, наверное, ArrayBuilder с внятной семантикой.
S>>Вы ссылаетесь на моё сообщение. В нём примеров кода нет.
Q>Это артефакт копипасты, попробую ещё раз: http://rsdn.org/forum/dotnet/7291686
Автор: Qbit86
Дата: 06.11.18

И здесь ничего интересного нет. Вот вы взяли ровно 1 вхождение из 172, и делаете из него далеко идущие выводы. Причём не видно, скажем, как именно заполняется эта коллекция — сколько там аллокаций к моменту вызова .ToArray()?
В других местах — есть ли обращения к элементам после .ToArray()? Т.е. удастся ли вообще заменить этот код на предлагаемый вами метод?

Вы всё ещё не обосновали значимость проблемы. Не выявили сценарии использования. А уже хотите обсуждать решения.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: меньше "выброшенной" памяти
От: Sharov Россия  
Дата: 07.11.18 19:20
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>И? Вы же понимаете, что те же самые чанки при большом количестве Add дадут меньшую нагрузку на GC — потому, что к моменту окончания Add будет меньше "выброшенной" памяти?


А можно ентот тезис как-то обосновать, а то я не понимаю почему меньше? Это как минимум от сценариев использования зависит.
Кодом людям нужно помогать!
Re[14]: Высокие стандарты
От: Qbit86 Кипр
Дата: 07.11.18 21:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

Q>>Например: https://lwn.net/Articles/250967/

S>Напальцевые рассуждения про эффективность непрерывной памяти не интересуют.

А меня не интересует опровергать безумные идеи про замену массива чанками в самой популярной коллекции фреймворка.

S>Поэтому я верю в измерения.


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

S>Почему вы думаете, что аллокации в List<T>.ToArray() вообще дают заметный вклад в перформанс вашей 3d-игры?


А если значительный вклад дают не они — значит ли это, что надо допускать пессимизацию, если есть потенциальный дешёвый способ её избежать?

S>Чем именно так интересен потребителю ArraySegment с Offset == 0, что нужен целый отдельный тип для него?


Компактнее тип, эффективнее его передача, проще его использование.

Q>>А что нужно сделать, чтобы было достаточно хорошо? (Менять рантайм не в счёт.)

S>Нужен, наверное, ArrayBuilder с внятной семантикой.

Был бы ArrayBuilder — хорошо. И это независимо от наличия ReleaseBuffer() в Листе.

S>Вы всё ещё не обосновали значимость проблемы. Не выявили сценарии использования. А уже хотите обсуждать решения.


Не вижу противоречия, почему бы и не пообсуждать решение? Не нравится качество обсуждения — так ведь принимать участие необязательно.

Мне было интересно послушать мнение коллег о востребованности этого функционала. Из треда можно сделать вывод, что для данной аудитории функционал, похоже, не востребован — тоже результат!

А слушать про несоответствие уровня дискуссии чьим-то высоким стандартам — это неинтересно.

———

Бонус-трек: свежее про оптимизацию аллокаций и внедрение этого вашего escape-анализа в .NET Core:
https://twitter.com/EgorBo/status/1060178512169046016
https://github.com/dotnet/coreclr/pull/20814
Глаза у меня добрые, но рубашка — смирительная!
Re[15]: меньше "выброшенной" памяти
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.18 04:06
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

S>>И? Вы же понимаете, что те же самые чанки при большом количестве Add дадут меньшую нагрузку на GC — потому, что к моменту окончания Add будет меньше "выброшенной" памяти?

S>А можно ентот тезис как-то обосновать, а то я не понимаю почему меньше? Это как минимум от сценариев использования зависит.
Внутри List<T> лежит совершенно стандартная стратегия удвоения при превышении.
Это означает, что к моменту, когда Capacity достигает 2^N, "выброшено" уже 2^N-4.
Если бы мы выделяли память чанками, то "предыдущие" чанки остаются достижимыми, а не выбрасываются. Такой список ест память вдвое медленнее, чем обычный. И при этом нет оверхеда на копирование при ресайзе.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: ArrayBuilder<T>.Buffer
От: Sharowarsheg  
Дата: 08.11.18 04:17
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Q>С ArrayBuilder<T> свои проблемы, его по всей видимости нельзя сделать одновременно (изменяемой) структурой и безопасным в смысле владения. Мне-то норм, но у людей вызывает сомнение даже передача буфера. Что уж говорить про шаринг буфера.


Да нет,

передача буфера не вызвает сомнения
шаринг буфера, а также пулинг буферов, а также шаринг пуленых буферов, и ещё более адский глум тоже не вызывает сомнения

сомнение вызывает необходимость сувать все эти забавные частные случаи в основные библиотеки.
Отредактировано 08.11.2018 4:21 Sharowarsheg . Предыдущая версия .
Re[3]: Clear()
От: Sharowarsheg  
Дата: 08.11.18 04:19
Оценка:
Здравствуйте, Qbit86, Вы писали:

S>>А что должно произойти с List<T> после этой операции? Он как-то задиспозится и начнет ругаться при вызове своих методов?


Q>Нет, он просто перейдёт в своё исходное состояние со ссылкой на пустой массив. Наблюдаемое поведение как у `Clear()`.


Правильно ли я понял, что в этом месте выделяется новый пустой массив?
Re[15]: Высокие стандарты
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.18 04:25
Оценка:
Здравствуйте, Qbit86, Вы писали:
Q>Я бы посмотрел на твои измерения при проталкивании пропозала по изменению реализации Листа. (В отличие от моего пропозала, твой бы затронул абсолютно всех пользователей Листа, с него и спросу больше.) А потом бы снисходительно перебирал: синтетические микробенчмарки не подходят, нужны реальные сценарии; твои реальные сценарии не подходят, нужны пользовательские, со всей статистикой.
Если мне придёт в голову предложить performance-related изменение, то я обязательно проведу измерения.
Иначе я сам себя не смогу убедить в его полезности.

S>>Почему вы думаете, что аллокации в List<T>.ToArray() вообще дают заметный вклад в перформанс вашей 3d-игры?

Q>А если значительный вклад дают не они — значит ли это, что надо допускать пессимизацию, если есть потенциальный дешёвый способ её избежать?
Нет, это значит, что вашу энергию по борьбе за производительность эффективнее будет приложить в другом месте.
Я придерживаюсь той же позиции по отношению к производительности, что и Эрик Липперт: оптимизация без профайлера — деньги на ветер.
Даже если вам кажется, что есть дешёвый способ улучшить производительность, его надо измерять.
А в данном случае способ недешёвый.
S>>Чем именно так интересен потребителю ArraySegment с Offset == 0, что нужен целый отдельный тип для него?
Q>Компактнее тип, эффективнее его передача, проще его использование.
Это опять какие-то абстрактные аргументы. Я вот не вижу никаких упрощений в использовании. Чтобы их обосновать, надо приводить примеры кода.

Q>>>А что нужно сделать, чтобы было достаточно хорошо? (Менять рантайм не в счёт.)

S>>Нужен, наверное, ArrayBuilder с внятной семантикой.
Q>Был бы ArrayBuilder — хорошо. И это независимо от наличия ReleaseBuffer() в Листе.
Q>Не вижу противоречия, почему бы и не пообсуждать решение? Не нравится качество обсуждения — так ведь принимать участие необязательно.
Мне вполне нравится качество обсуждения. Оно вообще запредельно для любого технического форума, особенно русскоязычного.
Не нравится необоснованность идеи.

Q>Мне было интересно послушать мнение коллег о востребованности этого функционала. Из треда можно сделать вывод, что для данной аудитории функционал, похоже, не востребован — тоже результат!

Пока что я делаю вывод, что в вашем проекте этот функционал тоже не востребован. Обоснования востребованности так и не прозвучало, а жаль.

Q>Бонус-трек: свежее про оптимизацию аллокаций и внедрение этого вашего escape-анализа в .NET Core:

Q>
Q>https://github.com/dotnet/coreclr/pull/20814
Да, отлично. Хотя до специфического сценария "заменить копирование массива на передачу указателя" тут довольно далеко.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[15]: Длина
От: _NN_ www.nemerleweb.com
Дата: 08.11.18 05:18
Оценка:
Здравствуйте, Qbit86, Вы писали:

Что насчёт своего ArrayBuilder-а ?
Сколько есть у вас в коде случаев когда методы возвращают именно List, а не IEnumerable или List создаётся локально ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Пустой массив
От: Qbit86 Кипр
Дата: 08.11.18 06:05
Оценка: +1
Здравствуйте, Sharowarsheg, Вы писали:

S>Правильно ли я понял, что в этом месте выделяется новый пустой массив?


Нет. В шапке пропозала даже приведена реализация:
/// <summary>
/// Extracts the internal array and replaces it with a zero length array.
/// </summary>
/// <returns>An array of type <see cref="T:T[]"/> that is <see cref="Capacity"/> in length.</returns>
/// <remarks>
/// This array is <see cref="Capacity"/> in length,
/// so the caller should save actual <see cref="Count"/> before calling this method.
/// </remarks>
public T[] MoveToArray()
{
    T[] result = _items;
    _size = 0;
    _items = s_emptyArray;
    _version++;
    return result;
}


Здесь `s_emptyArray` это:
private static readonly T[] s_emptyArray = new T[0];
Глаза у меня добрые, но рубашка — смирительная!
Re[16]: Надо уточнять
От: Qbit86 Кипр
Дата: 08.11.18 06:53
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Что насчёт своего ArrayBuilder-а ?


Тут на самом деле надо уточнять, что за ArrayBuilder<T>. Подразумевается класс, или изменяемая структура, или ref struct? У каждого варианта свои недостатки и ограничения.

_NN>Сколько есть у вас в коде случаев когда методы возвращают именно List, а не IEnumerable или List создаётся локально ?


Тут в свою очередь надо уточнять, что значит «у вас в коде». Например, в рабочем проекте ОГРОМНАЯ куча методов, которые принимают и возвращают List<T> — вопреки FDG. Единоличную ответственность за весь трэш и угар я нести не готов :) Но и заменять все Листы на IEnumerable<T> тоже так себе идея («Avoid LINQ»).
Глаза у меня добрые, но рубашка — смирительная!
Re[17]: Надо уточнять
От: xpalex  
Дата: 08.11.18 07:59
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Q>Тут на самом деле надо уточнять, что за ArrayBuilder<T>. Подразумевается класс, или изменяемая структура, или ref struct? У каждого варианта свои недостатки и ограничения.


Так у вас есть прекрасная возможность сделать свою реализацию, которая будет иметь удовлетворяющую вас производительность/gc-pressure, будет совместима с IList и, например, будет ломаться при попытке использования объекта после вызова ToArray()/ReleaseBuffer().

Q>Тут в свою очередь надо уточнять, что значит «у вас в коде». Например, в рабочем проекте ОГРОМНАЯ куча методов, которые принимают и возвращают List<T> — вопреки FDG. Единоличную ответственность за весь трэш и угар я нести не готов Но и заменять все Листы на IEnumerable<T> тоже идея («Avoid LINQ»).

Но вы же как-то собирались заменять вызовы List<T>.ToArray() на .ReleaseBuffer()? Если грепом, то как убедится, что все вызовы List<T>.ToArray() в вашем проекте являются передачей владения?
А если просматривать глазами, то можно вместо замены .ToArray() делать замену new List() на new ArrayBuilder().

P.S. Вы же понимаете, что ваш метод нарушает ISP?
Re[17]: Надо уточнять
От: _NN_ www.nemerleweb.com
Дата: 08.11.18 08:06
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, _NN_, Вы писали:


_NN>>Что насчёт своего ArrayBuilder-а ?


Q>Тут на самом деле надо уточнять, что за ArrayBuilder<T>. Подразумевается класс, или изменяемая структура, или ref struct? У каждого варианта свои недостатки и ограничения.

Добавьте себе оба ArrayBuilder и ValueArrayBuilder

_NN>>Сколько есть у вас в коде случаев когда методы возвращают именно List, а не IEnumerable или List создаётся локально ?


Q>Тут в свою очередь надо уточнять, что значит «у вас в коде». Например, в рабочем проекте ОГРОМНАЯ куча методов, которые принимают и возвращают List<T> — вопреки FDG. Единоличную ответственность за весь трэш и угар я нести не готов Но и заменять все Листы на IEnumerable<T> тоже идея («Avoid LINQ»).


Насколько сложно просто поменять List<T> на MyList<T> ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[16]: меньше "выброшенной" памяти
От: Sharov Россия  
Дата: 08.11.18 09:48
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Это означает, что к моменту, когда Capacity достигает 2^N, "выброшено" уже 2^N-4.


Благодарю, но мальнький вопрос по арифметике. Почему 2^(N-4) (или (2^N)-4 ??), а не те же 2^N, т.е. 2^N используется, 2^N свободно?
Кодом людям нужно помогать!
Re[17]: DefaultCapacity
От: Qbit86 Кипр
Дата: 08.11.18 09:50
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

S>Благодарю, но мальнький вопрос по арифметике. Почему 2^(N-4) (или (2^N)-4 ??), а не те же 2^N, т.е. 2^N используется, 2^N свободно?


private const int DefaultCapacity = 4;
Глаза у меня добрые, но рубашка — смирительная!
Re: List<T>.MoveToArray()
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 08.11.18 10:41
Оценка: +2
Здравствуйте, Qbit86, Вы писали:

Q>Семантика: отказаться от владения низлежащим буфером и передать его наружу. Используется как array builder для интеропа с низкоуровневыми API, которые получают на вход сегмент массива.


Странно добавлять такое спустя 18 лет. И не понятно, зачем модифицировать List, если нужна функциональность ArrayBuilder.
Пропозал дикий — в своём уме я бы не стал писать такой код с Capacity. Capacity должен быть параметром метода, по дефолту выхлоп Capacity == Length. В остальных случаях должен делаться ресайз по месту. Фремворк такого не даёт. По уму нужен специальный тип с безопасной поддержкой in-place resize.
Re[17]: меньше "выброшенной" памяти
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.18 10:50
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>>Это означает, что к моменту, когда Capacity достигает 2^N, "выброшено" уже 2^N-4.


S>Благодарю, но мальнький вопрос по арифметике. Почему 2^(N-4) (или (2^N)-4 ??), а не те же 2^N, т.е. 2^N используется, 2^N свободно?

Потому, что изначальный размер — 4.
После первого удвоения Capacity = 8; выброшенный размер — 4.
После второго Capacity = 16, выброшенный размер — 12 (8+4).
И так далее.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Код с Capacity
От: Qbit86 Кипр
Дата: 08.11.18 12:34
Оценка: -1
Здравствуйте, Ikemefula, Вы писали:

I>Странно добавлять такое спустя 18 лет.


Странно, что подобный core-функционал не был добавлен изначально.

I>И не понятно, зачем модифицировать List, если нужна функциональность ArrayBuilder.


Типа: зачем в Лист добавлять RemoveAt(), если нужна функциональность Add(). Пусть будет отдельный тип с Add(), и отдельный с RemoveAt()!

I>Пропозал дикий — в своём уме я бы не стал писать такой код с Capacity. Capacity должен быть параметром метода, по дефолту выхлоп Capacity == Length.


Про Capacity не понял, что за «код с Capacity»? В реализации этой фичи свойство Capacity Листа не используется.
Глаза у меня добрые, но рубашка — смирительная!
Re[16]: меньше "выброшенной" памяти
От: _FRED_ Черногория
Дата: 08.11.18 13:56
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Внутри List<T> лежит совершенно стандартная стратегия удвоения при превышении.

S>…И при этом нет оверхеда на копирование при ресайзе.

Сорри, если я не правильно понял, но, кажется, не "при ресайзе", а "при добавлении". Ведь при удалении (тоже в некотором роде "ресайз") из середины (чанка) в таком списке без копирования "хвоста" (как минимум, в одном чанке), кажется, не обойтись.
Help will always be given at Hogwarts to those who ask for it.
Re[18]: ISP
От: Qbit86 Кипр
Дата: 08.11.18 14:19
Оценка:
Здравствуйте, xpalex, Вы писали:

X>будет иметь удовлетворяющую вас производительность/gc-pressure, будет совместима с IList


Это, кстати, взаимоисключающие вещи.

X>будет ломаться при попытке использования объекта после вызова ToArray()/ReleaseBuffer().


Ломаться-то зачем? Предложенная реализация экземпляр Листа не ломает, он как новенький.

X>Но вы же как-то собирались заменять вызовы List<T>.ToArray() на .ReleaseBuffer()? Если грепом


Нет, конечно.

X>А если просматривать глазами, то можно вместо замены .ToArray() делать замену new List() на new ArrayBuilder().


Почему это обязательно будет ArrayBuilder? Это во многих случаях будет всё та же индексируемая коллекция общего назначения. Для которой построение массива не является единственной конечной целью.

X>P.S. Вы же понимаете, что ваш метод нарушает ISP?


Ок, а методы Clear() и TrimExccess() его нарушают? Может, вообще класс Лист надо расслоить по ответственностям — отдельный класс для добавления, отдельный класс для удаления, и т.д.? Типа: зачем требовать метода очистки, создай свой класс ListRemover.
Глаза у меня добрые, но рубашка — смирительная!
Re[16]: Добровольная пессимизация
От: Qbit86 Кипр
Дата: 08.11.18 16:05
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Я придерживаюсь той же позиции по отношению к производительности, что и Эрик Липперт: оптимизация без профайлера — деньги на ветер.


А я придерживаюсь той же, что и Герб Саттер про добровольную пессимизацию: «certain efficient design patterns and coding idioms should just flow naturally from your fingertips and are no harder to write than the pessimized alternatives. This is not premature optimization; it is avoiding gratuitous pessimization».

Заменять везде List<T> на самопальную структуру для построения массивов — это было бы затратной преждевременной оптимизацией. Без профилирования так лучше не делать, конечно.
Но если у тебя уже есть средства, позволяющие сразу при написании использовать подходящие идиомы — это позволит без затрат заранее избежать случаев, требующих потом профилирования и принятия решений об оптимизации.

Я хочу, чтоб такие средства были доступны всем.
Глаза у меня добрые, но рубашка — смирительная!
Re[17]: меньше "выброшенной" памяти
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.18 17:12
Оценка: 18 (1)
Здравствуйте, _FRED_, Вы писали:
_FR>Сорри, если я не правильно понял, но, кажется, не "при ресайзе", а "при добавлении". Ведь при удалении (тоже в некотором роде "ресайз") из середины (чанка) в таком списке без копирования "хвоста" (как минимум, в одном чанке), кажется, не обойтись.
Да, имелся в виду ресайз "вверх", через EnsureCapacity.
Есть и другие сценарии ресайза — например, прямое указание Capacity меньше текущей. Там тоже есть копирование.

RemoveAt() ресайза не вызывает (в текущей реализации).

Если делать List<T> на основе списка (или дерева) ArraySegment, то можно очень сильно сократить количество копирований и аллокаций при всех этих операциях.
Например, RemoveAt(0) в нынешнем листе означает копирование всего списка; "чанк" реализация обойдётся парой инкрементов/декрементов.
Даже вырезание из середины приведёт не к копированию стоимостью O(N),а к O(logN) операции по сплиту сегмента.

При этом индексация будет стоить O(logN), а для случаев массовой индексации типа for(int i=const; i<list.Count;i++) Console.WriteLine(list) можно свести стоимость индексации к O(1).

Поэтому мне забавно видеть уверенные рассуждения о том, как быстро ударит такая замена по фанатам производительности. Тут без тщательного вымерения [i]реальных
сценариев использования List<T> не обойдёшься.
К сожалению, в гитхабе нет поиска по метаданным — невозможно найти все обращения к List<T>.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: Добровольная пессимизация
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.18 17:25
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Sinclair, Вы писали:


S>>Я придерживаюсь той же позиции по отношению к производительности, что и Эрик Липперт: оптимизация без профайлера — деньги на ветер.


Q>А я придерживаюсь той же, что и Герб Саттер про добровольную пессимизацию: «certain efficient design patterns and coding idioms should just flow naturally from your fingertips and are no harder to write than the pessimized alternatives. This is not premature optimization; it is avoiding gratuitous pessimization».


Q>Заменять везде List<T> на самопальную структуру для построения массивов — это было бы затратной преждевременной оптимизацией. Без профилирования так лучше не делать, конечно.

Ну пока что получается, что вы предлагаете заменять простой фрагмент кода list.ToArray() (который, возможно, даже не отдельная строка — а просто выражение в списке аргументов метода) на вот такую простыню:
var size = list.Count;
var array = list.ReleaseBuffer();
something.DoSome(..., array, 0, size, ...)

Это что, are no harder to write than the pessimized alternatives? Точно?
Ок, предположим, мы починили ваше предложение, перейдя на arraySegment:
var a = list.ReleaseBuffer();
something.DoSome(..., a.Array, a.Offset, a.Count, ...)

Сильно лучше? Прямо-таки flow naturally from your fingertips?
Или реальные программисты таки продолжат писать вот так:
something.DoSome(..., list.ToArray(), ...)


Ну вот поэтому первое правило дизайна любой библиотеки — начните с примеров использования. Вы за всю дискуссию привели ровно две строчки кода, которые ничего не говорят ни о том, откуда берутся данные в List, ни о том, куда они потом деваются.
Может оказаться, что в данном конкретном месте можно просто заменить List<T> на T[] выше по коду, и ничего не сломается.
Может оказаться, что API, куда потом уезжает массив, в принципе не умеет принимать отдельный Count, и вам всё равно придётся его переписывать, чтобы воспользоваться ReleaseBuffer. Так что проще не теребить coreCLR предложениями, а сходу переписать этот API на приём List<T>, и избежать лишних аллокаций.
Может оказаться, что в остальных 171 местах интересного вам проекта после .ToArray() никакого Clear() нету, и передать владение не удастся.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: ArrayPool<T>.Rent
От: Qbit86 Кипр
Дата: 09.11.18 07:40
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Это что, are no harder to write than the pessimized alternatives? Точно?


Вполне.

S>Или реальные программисты таки продолжат писать вот так...


Я вот недавно использовал новый стандартный ArrayPool{T}. Представляете, возвращают сырой массив рандомной длины, без оборачивания в сегменты даже. Чудом выжил!

S>Ну вот поэтому первое правило дизайна любой библиотеки — начните с примеров использования.


Пример использования я привёл в шапке.

S>Вы за всю дискуссию привели ровно две строчки кода, которые ничего не говорят ни о том, откуда берутся данные в List, ни о том, куда они потом деваются.


Откуда вообще берутся данные в листе? Добавляются, фильтруются, группируются, сортируются.

S>в остальных 171 местах интересного вам проекта


Да он тебе больше интересен, чем мне. Рабочему проекту гипотетический новый метод Листа всё равно не поможет, там пока залочено на Юнитёвом варианте Mono и BCL времён .NET 3.5.

Именно чтобы не ограничиваться только своими сценариями, я создал этот топик с конкретным воспросом: «вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке?» Например, на Гитхабе в обсуждение issue пришёл чувак из aspnet и сказал, что им бы пригодился, с примерами кода.
Глаза у меня добрые, но рубашка — смирительная!
Re[19]: ArrayPool<T>.Rent
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.11.18 08:30
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Вполне.

Ок, вопросов больше не имею.
Q>Я вот недавно использовал новый стандартный ArrayPool{T}. Представляете, возвращают сырой массив рандомной длины, без оборачивания в сегменты даже. Чудом выжил!
Юмор засчитан. Аргумент — нет. Семантика метода Rent не требует никакого оборачивания в сегменты, по очевидным причинам.

Q>Пример использования я привёл в шапке.

По одному примеру судить о бенефитах невозможно.

Q>Откуда вообще берутся данные в листе? Добавляются, фильтруются, группируются, сортируются.

"Вообще" не интересует. Вы не хотите понять простого соображения: единственный критерий истины — это практика.
Для любого решения можно привести десятки "аргументов" как за, так и против.
Чтобы принять решение, нужно проверить эти аргументы на их существенность.

Q>Да он тебе больше интересен, чем мне. Рабочему проекту гипотетический новый метод Листа всё равно не поможет, там пока залочено на Юнитёвом варианте Mono и BCL времён .NET 3.5.


Q>Именно чтобы не ограничиваться только своими сценариями, я создал этот топик с конкретным воспросом: «вам бы пригодился метод List<T>.MoveToArray(), будь он в стандартной поставке?» Например, на Гитхабе в обсуждение issue пришёл чувак из aspnet и сказал, что им бы пригодился, с примерами кода.


Разбираем примеры кода. Ваши, иллюстрацию порочности подхода, и того чувака.

[tr]
Код курильщика Код нормального человека
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
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 grouping = string.Join(", ", localList.Skip(1).Take(3)); // no copies
Console.WriteLine(grouping); // Annie, Sara, Katrin
var localList = new List<string>();
// Populating, filtering, sorting, grouping...
localList.Add("Camille");
localList.Add("Annie");
localList.Add("Sara");
localList.Add("Katrin");
localList.Add("Kari");
localList.RemoveAt(3);
localList.RemoveAt(3);

string[] array = localList.MoveToArray(); // Want to move, not to copy.
string grouping = string.Join(", ", array, 1, 3);
Console.WriteLine(grouping); // Annie, Sara, Kari
var localList = new List<string>();
// Populating, filtering, sorting, grouping...
localList.Add("Camille");
localList.Add("Annie");
localList.Add("Sara");
localList.Add("Katrin");
localList.Add("Kari");
localList.RemoveAt(3);
localList.RemoveAt(3);

string grouping = string.Join(", ", localList.Skip(1).Take(3)); // safe
Console.WriteLine(grouping); // Annie, Sara
 private T[] GetOrderedMetadataSlow<T>() where T : class
        {
            var items = new List<T>();
            for (var i = 0; i < _items.Length; i++)
            {
                if (_items[i] is T item)
                {
                    items.Add(item);
                }
            }

            var array = items.ToArray();
            _cache.TryAdd(typeof(T), array);
            return array;
}
 private List<T> GetOrderedMetadataSlow<T>() where T : class
        {
            var items = new List<T>(_items.OfType<T>());
            _cache.TryAdd(typeof(T), items);
            return items;
}
Тут чуваки просто не нашли ничего умнее, чем заложиться на ковариантность массивов в дотнете. Больше ни по какой причине им там массивы не нужны. Можно просто заменить их лёгким движением на List<T>, и уменьшить количество аллокаций без пересмотра системной библиотеки. Всё равно при вытаскивании из кэша они выполняют приведение типов.
Остальные примеры рассматривать лень, судя по всему — там тоже надуманные проблемы. То есть их явно аллокации там не беспокоили — так что никакой ReleaseBuffer или MoveToArray они бы применять и не стали бы.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 09.11.2018 9:32 Sinclair . Предыдущая версия .
Re[3]: Код с Capacity
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 09.11.18 10:02
Оценка:
Здравствуйте, Qbit86, Вы писали:

I>>Странно добавлять такое спустя 18 лет.


Q>Странно, что подобный core-функционал не был добавлен изначально.


Спасибо, капитан, ты перефразировал сказаное мной.

I>>И не понятно, зачем модифицировать List, если нужна функциональность ArrayBuilder.

Q>Типа: зачем в Лист добавлять RemoveAt(), если нужна функциональность Add(). Пусть будет отдельный тип с Add(), и отдельный с RemoveAt()!

Ты умело скипнул всё важное. У тебя List и ArrayBuilder получилось одно и то же. А я говорю, что нужен новый тип данных для безопасного ресайза.
List это довольно неэффективный способ реализации ArrayBuilder. Вроде бы метод нужен для оптимизаций в низкоуровневых операция, а реально фигня какая то выходит.

I>>Пропозал дикий — в своём уме я бы не стал писать такой код с Capacity. Capacity должен быть параметром метода, по дефолту выхлоп Capacity == Length.


Q>Про Capacity не понял, что за «код с Capacity»? В реализации этой фичи свойство Capacity Листа не используется.


Размер массива, Length, будет соответствовать именно Капасити списка.
Как получить профит от фичи ?
Профит от фичи получается в том случае, если уменьшается количество копирований и инстанцирований. Согласен?
Если нужен именно массив, то, как правило, без лишних ячеек. Например, другая либа тупо полагается на свойство Length, что вобщем то норма.
List при добавлении элкментов удваивает размер внутреннего массива. Если конвертируем в массив, у нас получаются те самые лишние ячейки.
И как быть, если мне нужен массив конкретной длины ? Опаньки — заранее устанавливать тот самый Капасити. Почему так — а потому, что ресайз на месте не поддерживается.

Теперь самое интересное — если уж пошла работа с внешним массивом, то, как правило нужно заполнение с любой позиции массива, а не только частный случай с 0го элемента. В ArrayBuilder это не проблема реализовать, в List выбора нет.

Для своего кода мне совершенно ни к чему такой метод. Мне проще уже руками сделать все от точки до точки. Чем такой поделкой пользоваться, предпочту Linq и в конце сделаю итерацию с заполнением внешнего массива.
Re[20]: Benchmark
От: Qbit86 Кипр
Дата: 09.11.18 14:00
Оценка: 111 (1)
Здравствуйте, Sinclair, Вы писали:

S>string grouping = string.Join(", ", localList.Skip(1).Take(3)); // no copies

S>Код нормального человека

:) Всё в соответствии с предположением, которое я привёл в шапке топика: «Конечно, это должно заботить только тех, кто волнуется о количестве и объёме аллокаций — т.е. в общем случае мало кого.»

S>единственный критерий истины — это практика.


Окей, для любителей практики бенчмарки завезли:
BenchmarkDotNet=v0.11.2, OS=Windows 10.0.17763.55 (1809/October2018Update/Redstone5)
Intel Core i5-4430 CPU 3.00GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
  [Host]     : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
  Job-GXRYCP : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3190.0
  Job-ULDJUH : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
  Job-RTCSLU : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT

IterationCount=3  LaunchCount=1  WarmupCount=3

 Method |       Jit | Platform | Runtime |     Toolchain |     Mean |      Error |     StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
------- |---------- |--------- |-------- |-------------- |---------:|-----------:|-----------:|------:|--------:|------------:|------------:|------------:|--------------------:|
   Move | LegacyJit |      X86 |     Clr |       Default | 219.9 ns |  52.638 ns |  2.8853 ns |  1.00 |    0.00 |      0.0482 |           - |           - |               152 B |
   Copy | LegacyJit |      X86 |     Clr |       Default | 263.1 ns |   7.499 ns |  0.4110 ns |  1.20 |    0.01 |      0.0582 |           - |           - |               184 B |
   Linq | LegacyJit |      X86 |     Clr |       Default | 477.2 ns |  92.518 ns |  5.0712 ns |  2.17 |    0.05 |      0.0811 |           - |           - |               256 B |
        |           |          |         |               |          |            |            |       |         |             |             |             |                     |
   Move |    RyuJit |      X64 |     Clr |       Default | 191.8 ns |   1.135 ns |  0.0622 ns |  1.00 |    0.00 |      0.0813 |           - |           - |               256 B |
   Copy |    RyuJit |      X64 |     Clr |       Default | 216.2 ns |  25.988 ns |  1.4245 ns |  1.13 |    0.01 |      0.1016 |           - |           - |               320 B |
   Linq |    RyuJit |      X64 |     Clr |       Default | 440.6 ns | 223.906 ns | 12.2730 ns |  2.30 |    0.06 |      0.1345 |           - |           - |               424 B |
        |           |          |         |               |          |            |            |       |         |             |             |             |                     |
   Move |    RyuJit |      X64 |    Core | .NET Core 2.1 | 173.9 ns |  19.981 ns |  1.0952 ns |  1.00 |    0.00 |      0.0813 |           - |           - |               256 B |
   Copy |    RyuJit |      X64 |    Core | .NET Core 2.1 | 211.4 ns |  72.060 ns |  3.9499 ns |  1.22 |    0.03 |      0.1016 |           - |           - |               320 B |
   Linq |    RyuJit |      X64 |    Core | .NET Core 2.1 | 410.4 ns | 127.284 ns |  6.9769 ns |  2.36 |    0.05 |      0.1116 |           - |           - |               352 B |

// * Legends *
  Mean                : Arithmetic mean of all measurements
  Error               : Half of 99.9% confidence interval
  StdDev              : Standard deviation of all measurements
  Ratio               : Mean of the ratio distribution ([Current]/[Baseline])
  RatioSD             : Standard deviation of the ratio distribution ([Current]/[Baseline])
  Gen 0/1k Op         : GC Generation 0 collects per 1k Operations
  Gen 1/1k Op         : GC Generation 1 collects per 1k Operations
  Gen 2/1k Op         : GC Generation 2 collects per 1k Operations
  Allocated Memory/Op : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 ns                : 1 Nanosecond (0.000000001 sec)


Код бенчмарка: https://github.com/qbit86/misnomer/blob/2700d67ebe5929afc4b0ecdba6fcb04f55a02abd/benchmarks/Misnomer.Rist.Benchmark/StringJoinBenchmark.cs
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;

namespace Misnomer
{
    public class StringJoinBenchmark
    {
        [Benchmark(Baseline = true)]
        public string Move()
        {
            Rist<string> list = CreateAndPopulateList();

            string[] array = list.MoveToArray();
            return string.Join(", ", array, 1, 3);
        }

        [Benchmark]
        public string Copy()
        {
            Rist<string> list = CreateAndPopulateList();

            string[] array = list.ToArray();
            return string.Join(", ", array, 1, 3);
        }

        [Benchmark]
        public string Linq()
        {
            Rist<string> list = CreateAndPopulateList();

            IEnumerable<string> enumerable = list.Skip(1).Take(3);
            return string.Join(", ", enumerable);
        }

        private static Rist<string> CreateAndPopulateList()
        {
            var list = new Rist<string>();
            // Populating, filtering, sorting, grouping...
            list.Add("Camille");
            list.Add("Annie");
            list.Add("Sara");
            list.Add("Katrin");
            list.Add("Kari");

            return list;
        }
    }
}
Глаза у меня добрые, но рубашка — смирительная!
Re[4]: Лишние ячейки
От: Qbit86 Кипр
Дата: 09.11.18 15:38
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Размер массива, Length, будет соответствовать именно Капасити списка.


Всё верно, но закладываться на это, и даже описывать в документации необязательно. Достаточно того, что вернётся массив длиной больше, чем Count:
int count = list.Count;
int capacity = list.Capacity;
string[] array = list.MoveToArray();
Debug.Assert(count <= array.Length); // Обязательно.
Debug.Assert(capacity == array.Length); // Не обязательно; просто следствие внутреннего устройства.
ReadOnlySpan<string> span = array.AsSpan(0, count);


I>Профит от фичи получается в том случае, если уменьшается количество копирований и инстанцирований. Согласен?


Да. И количества аллокаций в штуках, что может оказаться важнее объёма аллокаций в сумме.

I>Если нужен именно массив, то, как правило, без лишних ячеек.


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

I>И как быть, если мне нужен массив конкретной длины ?


Перестать мыслить в терминах array и array.Length, а начать мыслить в терминах пары (buffer, count). В том смысле, что про все рабочие массивы допускать, что они могут быть с «хвостом». К этому подталкивают разработчики corefx, вводя в в обиход ArrayPool<T> и Span<T>.

I>Опаньки — заранее устанавливать тот самый Капасити. Почему так — а потому, что ресайз на месте не поддерживается.


Да бог с ним с ресайзом на месте. Даже если он был бы, у текущего Листа всё равно нельзя отнять буфер — хоть с «лишними» ячейками, хоть без.

I>Теперь самое интересное — если уж пошла работа с внешним массивом


Я никоим образом не предлагаю через Лист работать с внешним публично доступным массивом. Это нарушает инварианты Листа.

I>Чем такой поделкой пользоваться, предпочту Linq и в конце сделаю итерацию с заполнением внешнего массива.


LINQ'ом не везде можно пользоваться, потому что он приводит к лишним аллокациям. Например, в гайдлайнах разработчиков компилятора C# Roslyn про LINQ явно сказано — AVOID.
Глаза у меня добрые, но рубашка — смирительная!
Re[5]: Лишние ячейки
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 11.11.18 16:48
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Ikemefula, Вы писали:


I>>Размер массива, Length, будет соответствовать именно Капасити списка.


Q>Всё верно, но закладываться на это, и даже описывать в документации необязательно. Достаточно того, что вернётся массив длиной больше, чем Count:


А библиотека, которая требует массив, принимает всего один параметр — массив, — и ничего не знает про лишние ячейки. Твои действия ?

Q>Вот это, по моему опыту, не особо критично. Особенно, если массив короткоживущий (передал в API, а по возврату выбросил). Да пусть у него будут эти лишние ячейки.


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

I>>И как быть, если мне нужен массив конкретной длины ?


Q>Перестать мыслить в терминах array и array.Length, а начать мыслить в терминах пары (buffer, count). В том смысле, что про все рабочие массивы допускать, что они могут быть с «хвостом». К этому подталкивают разработчики corefx, вводя в в обиход ArrayPool<T> и Span<T>.


Я говорю про приемник этого массива. Откуда гарантия, что этот приемник съест лишние ячейки ?

Q>Да бог с ним с ресайзом на месте. Даже если он был бы, у текущего Листа всё равно нельзя отнять буфер — хоть с «лишними» ячейками, хоть без.


Разумеется. Кому нужна поддержка двух частных случаев ?

I>>Теперь самое интересное — если уж пошла работа с внешним массивом

Q>Я никоим образом не предлагаю через Лист работать с внешним публично доступным массивом. Это нарушает инварианты Листа.

Ты предлагаешь решение для какогото частного случая или двух, до кучи крайне неэффективное.
Re[6]: Общий случай
От: Qbit86 Кипр
Дата: 11.11.18 17:51
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>А библиотека, которая требует массив, принимает всего один параметр — массив, — и ничего не знает про лишние ячейки. Твои действия ?


С такими библиотеками никогда не сталкивался. Если столкнулся бы с API, что не позволяет передать часть массива — сделал бы копию.

I>Это работает в частном случае, когда ты сам пишешь код и никуда не передаешь этот массив. Обычно все ровно наоборот — массивы нужны для взаимодейсвтия с низкоуровневым API.


И этот низкоуровневый API обычно принимает (start, count).

I>Я говорю про приемник этого массива. Откуда гарантия, что этот приемник съест лишние ячейки ?


А откуда ты обычно черпаешь гарантию, что API сделает то, о чём заявляет в документации?

Q>>Да бог с ним с ресайзом на месте. Даже если он был бы, у текущего Листа всё равно нельзя отнять буфер — хоть с «лишними» ячейками, хоть без.

I>Разумеется. Кому нужна поддержка двух частных случаев ?

И как твой ресайз позволит избавиться от лишней аллокации массива при попытке получить массив из Листа?

I>Ты предлагаешь решение для какогото частного случая или двух, до кучи крайне неэффективное.


Я не знаю, какой «общий случай» ты имеешь в виду, и каким образом он эффективнее простой передачи владения существующим массивом вместо создания нового массива-дубликата.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: Общий случай
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 23.11.18 09:44
Оценка:
Здравствуйте, Qbit86, Вы писали:

I>>А библиотека, которая требует массив, принимает всего один параметр — массив, — и ничего не знает про лишние ячейки. Твои действия ?


Q>С такими библиотеками никогда не сталкивался. Если столкнулся бы с API, что не позволяет передать часть массива — сделал бы копию.


И для чего тогда этот кульный метод, если надо самому делать копию?

I>>Это работает в частном случае, когда ты сам пишешь код и никуда не передаешь этот массив. Обычно все ровно наоборот — массивы нужны для взаимодейсвтия с низкоуровневым API.

Q>И этот низкоуровневый API обычно принимает (start, count).

Это далеко не факт. Часто надо подготовить буфер определенной длины и заполнить его различными данными. И совсем необязательно данные идут из одного источника.

I>>Я говорю про приемник этого массива. Откуда гарантия, что этот приемник съест лишние ячейки ?

Q>А откуда ты обычно черпаешь гарантию, что API сделает то, о чём заявляет в документации?

Полагаю, ты имеешь ввиду гарантии вида: "передавайте любого размера массив, мы сами догадаемся, где там нужное, а где не нужное".
Ни разу не видел такого. Обычно или голый массив, и сразу фейл, или, что реже, массив, стартовая позиция и количество.

Q>>>Да бог с ним с ресайзом на месте. Даже если он был бы, у текущего Листа всё равно нельзя отнять буфер — хоть с «лишними» ячейками, хоть без.

I>>Разумеется. Кому нужна поддержка двух частных случаев ?

Q>И как твой ресайз позволит избавиться от лишней аллокации массива при попытке получить массив из Листа?


Элементрно. Мне нуже буфер размера 100. Капсити — 128. Вместо создания нового экземпляра и копирования вручную, я просто сообщаю желаемый размер и фремворк обрезает массив до 100 элементов.

I>>Ты предлагаешь решение для какогото частного случая или двух, до кучи крайне неэффективное.

Q>Я не знаю, какой «общий случай» ты имеешь в виду, и каким образом он эффективнее простой передачи владения существующим массивом вместо создания нового массива-дубликата.

Твой кейс не подразумевает простой передачи владения и ты сам же предлагаешь копировать вручную. Просто потому что капасити будет всегда не того размера, что нужно вызываемому код. Т.е. всегда придется заниматься копированием вручную. Следовательно, это равносильно toArray()
И нахрена тогда твой метод?
Re[8]: Общий случай
От: Qbit86 Кипр
Дата: 23.11.18 10:03
Оценка:
Здравствуйте, Ikemefula, Вы писали:

Q>>С такими библиотеками никогда не сталкивался. Если столкнулся бы с API, что не позволяет передать часть массива — сделал бы копию.

I>И для чего тогда этот кульный метод, если надо самому делать копию?

Для работы с теми API, которые таки принимают фрагмент массива (с другими я не сталкивался). Тогда делать копию не нужно.

Q>>И этот низкоуровневый API обычно принимает (start, count).

I>Это далеко не факт. Часто надо подготовить буфер определенной длины и заполнить его различными данными.

Давай примеры из BCL? Интересует пример API, котоый принимает на вход буфер, но не предоставляет перегрузку, принимающую (offset, count) или (start, length).

Q>>И как твой ресайз позволит избавиться от лишней аллокации массива при попытке получить массив из Листа?

I>Элементрно. Мне нуже буфер размера 100. Капсити — 128. Вместо создания нового экземпляра и копирования вручную, я просто сообщаю желаемый размер и фремворк обрезает массив до 100 элементов.

В моём примере не надо ничего копировать или аллоцировать:
string[] array = list.MoveToArray();
return string.Join(", ", array, 1, 3);

Приведи аналогичный код, считая, что у тебя есть API для ресайза массива. Вот обрезал ты у листа внутренний недоступный тебе буфер. Как ты извлечёшь его для передачи в API, который принимает массив, а не лист?

I>Твой кейс не подразумевает простой передачи владения и ты сам же предлагаешь копировать вручную.


Нет. Я не предлагаю копировать вручную.

I>Просто потому что капасити будет всегда не того размера, что нужно вызываемому код. Т.е. всегда придется заниматься копированием вручную.


Нет. Копировать возможно придётся только в тех редких случаях, если API не предоставляет перегрузки с (offset, count) или (start, length). Но я с такими случаями в стандартной библиотеке не сталкивался, а ты их не приводишь.
Глаза у меня добрые, но рубашка — смирительная!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.