Здравствуйте, vdimas, Вы писали:
V>А как, по-твоему, возращается по-значению структура, размер которой больше ширины регистра?
Как и всё — кладётся в стек.
V>Для инфы, в этом сценарии существует т.н. return value optimization (гугл).
V>>>Получить ref или readonly ref-ссылку на поле структуры через метод самой структуры нельзя, можно только через метод-расширение, где саму структуру передавать через ref this.
V>Офигеть. ))
V>Ты только что нашёл баг компилятора:
Нда, прикольно.
V>Надо проверить в 10-м C# и зарепортить, если еще не починили.
V>В любых flexible-структурах фактическая длина известна после получения данных из некоторого АПИ.
V>Некий публичный метод или св-во могли бы возвращать Span.
Да, можно так. А можно и скрыть факт наличия спана (если мы не хотим, чтобы его ещё куда-то кастили), и просто выставлять наружу индексер.
V>1. Дотнет не гарантирует фактический порядок расположение полей, необходимо задать StructLayout хотя бы Sequential
Ну да, естественно.
V>Мне показалось, что ты хотел сделать Span как часть описания структуры.
Нет, тогда конечно же сломается Layout.
V> public Span<int> AsSpan() => MemoryMarshal.CreateSpan(ref _data0[0], Length);
Я так и использую — в тех местах, где мне нужен Span.
Вот, текущее состояние экспериментов
https://github.com/evilguest/atropos/blob/main/Atropos/BranchNode.cs#L37
К сожалению, ожидаемый инлайнинг this[] в
https://github.com/evilguest/atropos/blob/main/Atropos/BranchNode.cs#L28 не происходит, несмотря на то, что T — это struct тип, точно известный в момент JIT
Поэтому с перформансом у текущей версии всё гораздо хуже, чем у предыдущей.
V>Тогда for-циклы по полному Span-у соптимизируют одну проверку за выход за диапазон, как это делается для обычных массивов.
Именно.
V>Неблокирующий вызов не означает не использование примитивов синхронзации, а там сказано:
V>V>* Not manipulate locks or other concurrency primitives
Опять же в доке сказано, что из разных потоков работать с зокетами нельзя. Так что явно там нет никаких примитивов синхронизации.
V>На моей машине в 17 раз.
V>И у тебя там замеры вообще странные были, если по твоим замерам простой вызов выходил 7 ns.
Возможно, я там поделил неверно. В
текущей версии количество внутренних итераций поправлено, так что можно просто заменять миллисекунды на наносекунды.
V>Т.е., при каждом обновлении.
Чаще
V>Выглядит так, что у тебя своеобразные представления как о возможностях AOT, так и о возможностях hot-spot оптимизаций.
У меня своеобразные представления о том, как что
должно работать.
V>>>Скорее, оптимизатор заинлайнит что-то в кишках XML-сериализатора, но верхние уровни пойдут как есть.
S>>Ну, в нашем случае мы написали высокопроизводительный XML-сериализатор на $"<flightData><flightNo>{Escape(d.GetString(0))}</flightNo><departureDate>{d.GetDate(1).ToString(r)}</departureDate></flightData>"
V>Вручную писаный сериализатор будет оптимизирован и в АОТ,
Каким образом? На основании чего АОТ будет выполнять спекулятивный инлайнинг?
V>Верно, АОТ потенциально способно убирать лишнюю косвенность.
V>Например, "выпрямлять" в памяти банальный List<T>.
Что значит "выпрямлять"? Он же уже и так прямой.
V>Статистика в основном нужна для определения мест, где требуется оптимизация.
Мы ходим по кругу. Ну вот мы увидели, что у нас в горячем цикле вызывается _serializer.Serialize(dataReader, stringBuilder).
Дальше что? Это косвенный вызов по интерфейсу.
V>В рекордсете из 1001 элемента первые 1000 будут прочитаны без оптимизации.
V>Затем будет выполнена дорогостоящая оптимизация и последний элемент прочитают оптимальным образом.
Ага. Зато последующие обращения к этому же датасету будут уже оптимальны. А АОТ так и оставит косвенный вызов через VMT.
V>У тебя в "одном и том же месте" каждый раз будут разные экземпляры DataReader и потенциально хотя бы немного отличающиеся схемы, хотя бы в плане nullable-полей, особенно если со стороны базы позвали какой-нить union. И даже если схемы будут те же — это будут другие экземпляры схем, вот в чём прикол.
При чём тут экземпляры? Речь о коде методов, т.е. о содержимом VMT. И о ветвлениях.
V>В случае DataReader в нынешнем виде не прокатит — слишком большая иерархия объектов, всю её проверять на соответствие текущему хот-спот-коду будет накладней получаемых от хот-спот плюшек.
И удивительным образом, Java ухитряется сворачивать вполне себе прикладной код в приемлемый x86/x64.
V>Тогда динамически (по индексу) будет обращение только к строке такой таблицы, а столбец известен статически.
V>Тогда проверка соответствия лейаута объекта будет сводиться к проверке только ссылок на метаинформацию столбцов рекодрсета, где эти столбцы ссылаются на одни и те же статические-заготовленные конвертеры.
И даже такую конструкцию способен улучшить хотспот.
V>Т.е. сравни — проверить только равенство ссылок в объекте верхнего уровня или рекурсивно пройтись по кучерявым объектам, проверяя равенство всех полей всех дочерних элементов.
V>Счётчик сложности в последнем случае скажет "извините, в другой раз".
Вот это уже интересный вопрос. Всё зависит от того, сколько у нас реальных типов используется внутри тела цикла. Там не нужно проверять "равенство всех-всех полей".
V>"Норма" там в простейших случаях.
V>Я разбирал примеры работы джавовского хот-спота — покрывает только тривиальнейшие случаи и уже даже про них трубят как о победе.
Я настолько глубоко именно в джавовском хотспоте не разбирался — в основном читал статьи и общался с людьми, которые пилили высокопроизводительные приложения. Причём ещё в начале 2000х, задолго до GraalVM.
V>Распространённая ошибка.
V>Я показал устройство таблицы конвертеров в сообщении, на которое уже давал ссылку.
V>Читабельность и поддерживаемость (то бишь расширяемость) прекрасная.
V>А в варианте с новыми указателями на ф-ии — еще и максимально-эффективная, т.е. даже, грубо, на асме или IL быстрее не сделаешь.
Навскидку не видно, благодаря чему это будет быстрее вызова через интерфейс — ведь в реальности у нас будет не просто вызов по указателю. Сначала надо будет достать по указателю таблицу функций (рукопашный аналог VMT), и уже потом из неё выбирать слот. Впрочем, нужно профилировать.
V>ОК, про гипотетический хотспот пока спорить не буду, по крайней мере пока его нет в природе для .Net.
Да, пока что наша проблема не в том, что хотспот недостаточно хорош, а в том, что его вовсе нет. Умение выбрать места для оптимизации у JIT уже есть, но этого недостаточно — с таким уровнем умения обыграть АОТ невозможно по определению.
V>Нигде.
Вот то-то и оно. Так что вы итоге мы обсуждаем гипотетический АОТ vs гипотетичексий хотспот.
В моей идеальной картине мира они сосуществуют: то есть АОТ работает заранее, возможно, с применением PGO. Как опциональный компонент, призванный улучшить время холодного старта.
А затем в дело вступает хотспот, который докомпилирует динамический код (если такой появится), и использует рантайм статистику для дальнейших оптимизаций уже и так неплохого кода.
V>Даже существующий полноценный доступен только для iOS на основе mono (ХЗ какого он там качества).
V>А виндовые UWP-приложения на .Net Core UWP компиляются серверами магазина Windows под сетку устройств, тот код публично недоступен.
Так нам же не исходники АОТ нужны. Была бы возможность плагинов — был бы публичный API.
V>Я помню обратное — ты так и не прошёл понимание про повторно-используемые "кубики" при построении плана запроса.
Я привёл пример "плана запроса" и просил показать, какие его запчасти можно повторно использовать для альтернативных планов того же запроса, или каких-то других запросов.
V>Псевдокод давался.
Да, мной.
V>Это для динамических вещей.
V>Для статических еще в момент компиляции известны наличествующие индексы.
Для любых. Дело не в динамике, а в количестве сочетаний.
V>Поэтому, комбинаторика будет только по различиям в статистике, где для многих типов индексов, которые я обозвал enum (разновидность справочных данных эдакого системного плана, т.е. которые жестко привязаны к версии приложения) — статистика известна на момент компиляции. То бишь, кол-во уникальных значений в индексе, характер этих значений (подряд или разрежёнными "островками" и т.д.).
В реальной системе таких "индексов" пренебрежимо мало.
V>Т.е., вместо
V>V>class SomeClass { int a, b, c; };
V>SomeClass c1 = new SomeClass[42];
V>
V>У них идёт:
V>V>int[] aFields = new int[42];
V>int[] bFields = new int[42];
V>int[] cFields = new int[42];
V>
Ну, так это известный трюк, связанный не столько с хотспотом, сколько с отсутствием value-типов. А иногда — и с тем, что мы хотим получить автовекторизацию, которая не работает в случае int a; int b; int c.
V>А в самых критических случаях и вовсе:
V>V>class SomeClassHelper {
V> const int FIELD_COUNT = 3;
V> const int FIELD_A_OFFSET = 0;
V> const int FIELD_B_OFFSET = 1;
V> const int FIELD_C_OFFSET = 2;
V> public void write(int[] array, int index, int a, int b, int c) {
V> index = index * FIELD_COUNT;
V> array[index + FIELD_A_OFFSET] = a;
V> ...
V>}
Это тоже известный трюк
V>int[] data = ...
V>SomeClassHelper.Write(data, index, a, b, c);
V>
V>Смысл примерно должен быть понятен.
V>А если поля разных типов (по ширине разных), то всё еще забавнее происходит над массивом байт.
V>Вытягивают её ручками, помогая хот-споту.
Ну, по честному это применяется только в том случае, если перформанс прямо очень важен, а себестоимость разработки — нет. А обычные гражданские приложения, написанные идиоматическим образом, работают
приемлемо быстро.
V>Сам понимаешь, поддерживаемость ТАКОГО кода ни идёт ни в какое сравнение с предложенной мною микро-оптимизацией на основе таблицы диспетчеризации конвертеров.
Да, с этим я не спорю. Именно поэтому мне CLR нравится больше, чем JVM.