Здравствуйте, vdimas, Вы писали:
V>Ну, в C++ изначально маппинг прост, поэтому, выделенные ORM непопулярны. V>Да и банальный шаблон Фасад ничего не стоит в рантайме, т.е. штрафа за абстракцию нет.
V>Что касается общения с БД — в плюсах принято вручную контроллировать ресурсы, например, самим управлять пулом соединений, самим решать, как именно посылать запросы к базе (синхронно, безблокировочно или асинхронно), как ожидать ответ и т.д. В дотнете же это всё "даётся сверху" без малейшей возможности настроить под себя. Просто тебе дан некий "наиболее общий сценарий", скажи спасибо и за это.
V>Ну и, низлежащие драйвера связи с БД дают доступ к памяти прочитанной строки БД, т.е., в отличие от дотнетных дров, нет надобности копировать эти данные, да еще в "вертикальное" представление в памяти, когда каждый столбец рекордсета представлен массивом. Прямо из поданной драйвером памяти данные непосредственно используются по чтению.
Это где это каждый столбец представлен массивом? Рекордсет традиционно представлен "массивом" строк, каждая из которых — "массив" значений.
При этом "копировать", собственно, ничего не надо. В реализации IDataRow можно делать конвертацию на лету при помощи MemoryMarshal.
V>Особенно когда речь идёт о локальных in-proc базах-хранилищах и непосредственного использования их низкоуровневого АПИ, там и вовсе эффективность на порядки выше.
Ну, вот на эту тему я последние месяцы как раз экспериментирую. V>В последних дотнетах тоже есть возможность переписать драйверы общения с БД на похожий манер (через упомянутую сборку Unsafe), но вряд ли это сделают, бо легаси такое легаси...
Unsafe не должен быть нужен. Сильно пушка большая. Можно испортить больше, чем улучшить.
V>И да, в плюсах полно реализаций "самого общего сценария" V>https://oatpp.io/docs/components/orm/ V>(с полутыка найдёшь еще несколько) V>Но как-то явного лидера не выявилось, по всем перечисленным причинам.
Ну, в прошлый раз при разборе лидеров ORM в плюсах оказалось, что самые передовые из них находятся где-то на уровне JDBC, т.е. в глубоком прошлом по сравнению с Linq. С тех пор, правда, вышел новый стандарт — может кто-то и взялся напилить что-то более современное.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[43]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Разве что абстракции бесплатны и способ их выбора прост. S>При правильной реализации абстракции в дотнете тоже близки к бесплатным.
Это если транзитивно протягивать зависимость от платформы в свой код, т.е. генерить разные бинари под разные платформы, как это происходит в системных либах.
Тогда можно сделать без абстракций (в дотнетном смысле), просто задействуя разный код для разных конечных сборок.
Но в случае дотнета решиться на такое не очень просто.
V>>В месте вызова делают редко, иначе управляемость кода падает. V>>Т.е., одна обыгрываемая кроссплатформенная функциональность должна обитать в одном месте в исходниках. S>Ну, то есть тот же подход C#, вид сбоку. Всё равно всё упирается в #ifdef.
В C# можно пользоваться тем, что если до вызова external, помеченного DllImport, дело не доходит, то этого кода как бы и нет.
В этом случае приходится принимать решение, где у нас будет абстракция.
Идеально будет воткнуться туда, где уже есть виртуальные вызовы, а не создавать +1 виртуальный слой.
(по крайней мере для нашей специфики это так — лишний виртуальный вызов играет рояль)
В итоге обыгрывание кроссплатформенности не выходит достаточно "точечным".
Понятно, что это всё специфика, ну мы же тут каждый своим субъективным опытом делимся...
V>>Под линуха и макось пока приходится делать с 0-ля. S>Неужто всё ещё нет инструмента, который умеет определения структур из .h файлов перегонять напрямую в шарп? Там же вроде как более-менее прямолинейно всё.
Не скажи.
Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией.
Т.е., вариантов чуть ли не на любой вызов АПИ дофига.
Т.е., даже когда беру готовые описания интеропа — порой прилично их допиливаю, но всё-равно это экономит кучу времени, ес-но.
S>Так-то понятно, что авторы библиотек на C++ поставляют хидеры на С++, а pinvoke хелперы не поставляют
Та да.
Особенно весело, когда эти описания отличаются для разных сборок Linux, например, в случае select — зависит от параметра ядра max handles, т.е. размер структуры fd_set бывает разный.
S>Да, вот тут есть некоторый факап дизайна дотнетного АПИ. Понятно, что хотелось спрятать все эти InternalSafeHandle с глаз подальше; но в платформе-то есть куча API, которые оперируют именно хэндлами, причём более-менее ООПшным образом. Есть несколько способов всё это исправить, но они довольно-таки затратные.
Дотнет пытается дать высокоуровневое "всё изкаробки".
Т.е., все эти вещи так или иначе связаны с асинхронным режимом работы, вот он и даёт.
Сейчас постепенно расползается по АПИ системных библиотек 3-е поколение асинхроннщины — на основе ValueTask.
Т.е. эволюция была такой — BeginOp/EndOp, Task, ValueTask.
Однако, с ValueTask есть кое-какие проблемы — непонятно как гарантировать, что после использования Result этот экземпляр ValueTask не будет использован в будущем.
Есть специальный метод ValueTask.Preserve для таких случаев, но тогда получаются гарантии из разряда вербальных.
Идеально было бы сделать ValueTask как ref-struct, чтобы ограничить время жизни локальным scope, но тогда их невозможно будет использовать для захватываемых переменных async-автомата, т.е. для асинхронного кода, для которого ValueTask и предназначен, собсно.
С другой стороны, этого и не требуется, т.к. м/у вхождениями в автомат работает не сам экземпляр ValueTask, а его awaiter, т.е. сохранять в машинке надо его, но проблема семантики ref struct остаётся — это надо будет сделать AsyncMethodBuilder совсем уж умным и что-то сделать с компилятором C#, чтобы дело вообще дошло до вызова своего AsyncMethodBuilder.
В общем, туда как начинаешь вникать — всё за всё цепляется, принимать решения сложно, как всегда бывает, когда однозначно хорошего решения нет.
Каждый божий раз выбираешь некий набор компромиссов из альтернативных их наборов.
V>>Раньше мы работали на микросекундных задержках, сейчас бодаемся за наносекундные. V>>Жаль, одна только стоимость вызова нейтивной ф-ии сравнима с задержками, даваемыми нейтивной частью уже с приготовленными данными. )) S>Руки дойдут — посмотрю бенчмарки. Не должно там быть такого оверхеда — речь про единицы десятков ассемблерных инструкций.
Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал.
Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX.
V>>И не потому что "ловушки GC", "фреймы стека" и прочее — а просто глупый. V>>Например, два-три раза подряд записать в стековую переменную ноль разными способами, и это значение нигде потом не используется. S>Это скорее вопрос к качеству IL-кода. Джит, по большому счёту, рассчитан на то, что семантические оптимизации делает компилятор высокого уровня.
Простой пример:
interface IConfig {
bool SomeFlag1 { get; }
}
class SomeClass<TConfig>
where TConfig : struct, IConfig
{
public void Foo() {
if(default(TConfig).SomeFlag1) {
// case 1
} else {
// case 2
}
}
}
struct Config1 : IConfig {
public bool SomeFlag1 => true;
}
struct Config2 : IConfig {
public bool SomeFlag1 => false;
}
...
SomeClass<Config1> c1;
c1.Foo();
SomeClass<Config2> c2;
c2.Foo();
После работы JIT с оптимизацией изсчезает ветвление в методе Foo, т.к. ветвление происходит по константе, т.е. ассемблер показывает, что остаётся просто одна из веток.
Но, создание экземпляра Config1 длиной 1 байт происходит.
Причём, происходит в 2 этапа — сначала под него выделяется память и обнуляется.
Потом в эту память записывается 0 в результате вызова дефолтного конструктора.
Потом эта память нигде не используется, т.к. вызов св-ва SomeFlag1 JIT заинлайнил, соптимизировал и даже выкинул ненужную ветку if.
Кстате, кто-то сетовал, что в C# нельзя шаблоны генериков делать константами — вот, можно, через тип-"провайдер" таких констант.
Это техника из плюсов, где в подобных случаях при ветвлении на константе оптимизирующий компилятор сразу отбрасывает лишнюю ветку кода, поэтому, плюсовики без проблем позволяют себе лишние if-ы в подобных случаях.
(для воссоздания контекста — злейшим врагом современных программ являются лишние ветвления, т.к. они потенциально сбрасывают конвейер процессора)
S>Именно там должна проводиться трассировка использования (вот эти вот все варнинги про value assigned to t is never used). У джита мало ресурсов и очень мало времени на то, чтобы отслеживать глобальные потоки данных.
Что странно — лишнюю ветку выкинул, а лишние данные — нет.
У JIT-компилятора джавы с этим никаких проблем, т.к. блок escape-анализа отслеживает жизненный путь данных, и если данные не нужны, они и не создаются.
Дотнетный джит отродясь escape-анализом не увлекался, а зря — там достаточно много можно на этом выиграть, в обработке строк и промежуточных коллекций уж точно.
S>Я когда в прошлом году экспериментировал, убедился, что начиная с определённого количества переменных в методе джит просто забивает на частоты использования и lifetime, и тупо уносит всё в стек.
Он не справляется даже в показанном простейшем случае.
Я периодически открываю issue-s в дотнете, но тут рука пока не поднялась, бо явно не самый нужный сценарий для отрасли, хотя был бы полезен конкретно мне. ))
Я всё жду (очень много лет) когда они приличный AOT-оптимизатор заделают.
Вот туда уже стоит вкладываться.
S>На самом деле джит тоже опенсорсный, и прогресс в нём впечатляет. Народ постоянно контрибутит туда офигенные вещи. Про шестой дотнет я пока не смотрел, а вот в пятом было добавлено до фига "смысловых" оптимизаций.
Дотнет ускорился однозначно в низкоуровневых операциях.
Например, мне пришлось выкинуть достаточно большую библиотеку обработки-конверсии строк, которая давала прирост в разы на прошлых дотнетах, с версии Core 3.1 давала меньше, а с выходом 5.0 перестала иметь смысл...
Но, блин, это всё самые базовые вещи...
И я когда-то потратил прилично времени на эту либу, и она была нужна долго, а вот теперь вложения помножились на ноль. ))
V>>В итоге застой в .Net продлился примерно на 5-6 лет дольше, чем высмеиваемый (справедливо) когда-то застой в С++. S>Такого не может быть Дотнету столько лет нет, сколько будет, если прибавить 5 лет к срокам застоя в плюсах
Плюсы активно менялись до стандарта 2003.
А потом на 8 лет застой.
Т.е., речь не про общее время жизни технологии, а про периоды отсутствия заметного развития.
Сейчас обе технологии развиваются неплохо, можно смело закладываться на них в будущих проектах.
V>>Липперт, конечно, сладкий болтун, особенно по мелочам, но вовремя подняться над ситуацией и осмотреть её с высоты птичьего полёта не смог. V>>Наверно, был очень занят все эти годы. S>Пока Липперт работал в команде, прогресс с компилятором был ого-го себе.
Сейчас прогресс именно языка виднее.
Разумеется, в т.ч. из-за вклада Липперта когда-то.
Его ошибка классическая — он попытался съесть слона целиком, а стоило это делать по частям.
S>Ну, как мы уже выяснили, по сравнению с вами и Билл Гейтс — неудачник
Гейтс красавчик, не надо ля-ля.
Его решения всегда были достаточно оперативны, своевременны (ключевое).
Даже когда он якобы "просрал" интернет — это просирание было от силы год и было наверстано с большим запасом в течении опять же года.
Это был шок и трепет, когда из отстающего они через год превратились в законодателя мод в интернете, что вся отрасль сидела на IE затем почти два десятилетия.
А как конкуренты "обиделись" — любо дорого было посмотреть.
Но своей обидой нанесли вред индустрии, как ни крути, все эти судебные процессы против бесплатного IE.
По сегодняшним меркам выглядит дико.
S>Лично я считаю Липперта очень, очень квалифицированным разработчиком. Как и Хейльсберга
Сравнил хрен с пальцем.
Хейльсберг тоже красавчег.
Малость странно, что ты сравниваешь людей с потрясающим нюхом, т.е. даром предвидения (никакого "дара" нет, разумеется, это хорошие аналитически скиллы, умение видеть одну и ту же картинку с разных фокусных расстояний) с "просто хорошим ремесленником".
Здравствуйте, Sinclair, Вы писали:
V>>Ну и, низлежащие драйвера связи с БД дают доступ к памяти прочитанной строки БД, т.е., в отличие от дотнетных дров, нет надобности копировать эти данные, да еще в "вертикальное" представление в памяти, когда каждый столбец рекордсета представлен массивом. Прямо из поданной драйвером памяти данные непосредственно используются по чтению. S>Это где это каждый столбец представлен массивом?
В дотнете.
S>Рекордсет традиционно представлен "массивом" строк, каждая из которых — "массив" значений.
Строки в датасете — это, грубо, маппинг сущностей-строк на индексы в массивах.
S>При этом "копировать", собственно, ничего не надо. В реализации IDataRow можно делать конвертацию на лету при помощи MemoryMarshal.
Можно посмотреть на такую реализацию?
V>>В последних дотнетах тоже есть возможность переписать драйверы общения с БД на похожий манер (через упомянутую сборку Unsafe), но вряд ли это сделают, бо легаси такое легаси... S>Unsafe не должен быть нужен. Сильно пушка большая. Можно испортить больше, чем улучшить.
Это всё риторическое. ))
Избавление от лишних копирований — неплохая награда, чтобы вот прям так категорично...
V>>И да, в плюсах полно реализаций "самого общего сценария" V>>https://oatpp.io/docs/components/orm/ V>>(с полутыка найдёшь еще несколько) V>>Но как-то явного лидера не выявилось, по всем перечисленным причинам. S>Ну, в прошлый раз при разборе лидеров ORM в плюсах оказалось, что самые передовые из них находятся где-то на уровне JDBC, т.е. в глубоком прошлом по сравнению с Linq.
Подходы другие.
Если берут плюсы, то выжимают эффективность, иначе бы зачем брать плюсы?
S>С тех пор, правда, вышел новый стандарт — может кто-то и взялся напилить что-то более современное.
Периодически пилят, но оно не очень популярно.
Плюсы вообще редко общаются с базой напрямую, кроме как для локальных хранилищ, но там схема данных обычно тривиальнейшая.
А если общаются с сервером, то чаще на ответной стороне сервер приложений со своим кешированием, который отдаёт ответ на предопределённые запросы через предопределённые же типизированные наборы данных. И тоже задержки малость не те, к которым привыкли в дотнете.
И обычно данные справочного характера кешируются на клиенте, т.е. по сети лишний раз не гоняются, что тоже резко отличается от принятого в дотнете (по крайней мере, гдя я на это внимательно смотрел).
Re[44]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, Sinclair, Вы писали:
S>>Это где это каждый столбец представлен массивом? Рекордсет традиционно представлен "массивом" строк, каждая из которых — "массив" значений. S>>При этом "копировать", собственно, ничего не надо. В реализации IDataRow можно делать конвертацию на лету при помощи MemoryMarshal. S> В DataTable. Там типизированный массив колонок.
Едрить!
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[36]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:
V>В C# можно пользоваться тем, что если до вызова external, помеченного DllImport, дело не доходит, то этого кода как бы и нет.
Можно, но тогда нужно бранчиться в вызывающем коде, чтобы выбрать правильный DllImport.
V>В этом случае приходится принимать решение, где у нас будет абстракция. V>Идеально будет воткнуться туда, где уже есть виртуальные вызовы, а не создавать +1 виртуальный слой. V>(по крайней мере для нашей специфики это так — лишний виртуальный вызов играет рояль)
А что у вас за специфика? Всё ещё непонятно, где же нужно так много микроскопических переходов менеджед/натив. V>В итоге обыгрывание кроссплатформенности не выходит достаточно "точечным". субъективным опытом делимся...
V>Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией.
По-моему, нормальный вариант — только один. Если API принимает struct, то мы и отдаём struct.
ref struct нужен для очень специальных сценариев.
V>Та да. V>Особенно весело, когда эти описания отличаются для разных сборок Linux, например, в случае select — зависит от параметра ядра max handles, т.е. размер структуры fd_set бывает разный.
Ну, вот за это мы не очень любим линукс
К сожалению, нормального способа работы со структурами переменного размера в дотнете/С# я не вижу.
То есть можно сделать враппер вокруг byte[], который работает при помощи MemoryMarshal, и даже приемлемо по эффективности (после JIT все эти обращения к MemoryMarhshal.Cast<byte, IntPtr>(data.AsSpan().Slice(sizeof(int))[index] превращаются в in-place инструкции типа LEA), но код выглядит, мягко говоря, нечитаемым.
V>Дотнет пытается дать высокоуровневое "всё изкаробки". V>Т.е., все эти вещи так или иначе связаны с асинхронным режимом работы, вот он и даёт. V>Сейчас постепенно расползается по АПИ системных библиотек 3-е поколение асинхроннщины — на основе ValueTask. V>Т.е. эволюция была такой — BeginOp/EndOp, Task, ValueTask.
Да дело даже не в этом. В платформе АПИ объектно-ориентирован — есть множество "типов" хэндлов, которые могут обрабатываться единым образом.
В дотнете выбрана совершенно другая иерархия типов, из-за которой у сокета и файла нет ничего общего.
Поэтому крайне сложно реализовать библиотеку, которая бы, к примеру, умела ждать на разнородном наборе объектов. Даже не переходя к ValueTask и прочим нюансам управления асинхронщиной.
V>Идеально было бы сделать ValueTask как ref-struct, чтобы ограничить время жизни локальным scope, но тогда их невозможно будет использовать для захватываемых переменных async-автомата, т.е. для асинхронного кода, для которого ValueTask и предназначен, собсно.
Вот именно.
V>С другой стороны, этого и не требуется, т.к. м/у вхождениями в автомат работает не сам экземпляр ValueTask, а его awaiter, т.е. сохранять в машинке надо его, но проблема семантики ref struct остаётся — это надо будет сделать AsyncMethodBuilder совсем уж умным и что-то сделать с компилятором C#, чтобы дело вообще дошло до вызова своего AsyncMethodBuilder.
Ну, вообще движение в подобных направлениях есть. Недавно читал про реквест по расширению Linq query comprehension, чтобы можно было подсовывать свои реализации Queryable pattern.
V>В общем, туда как начинаешь вникать — всё за всё цепляется, принимать решения сложно, как всегда бывает, когда однозначно хорошего решения нет. V>Каждый божий раз выбираешь некий набор компромиссов из альтернативных их наборов.
Ну, да.
S>>Руки дойдут — посмотрю бенчмарки. Не должно там быть такого оверхеда — речь про единицы десятков ассемблерных инструкций.
V>Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал. V>Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX.
Ну по идее так и должно быть. Если маршалинга нет (а для достижения этого есть все предпосылки), то должно делаться именно это.
V>После работы JIT с оптимизацией изсчезает ветвление в методе Foo, т.к. ветвление происходит по константе, т.е. ассемблер показывает, что остаётся просто одна из веток. V>Но, создание экземпляра Config1 длиной 1 байт происходит.
V>Причём, происходит в 2 этапа — сначала под него выделяется память и обнуляется. V>Потом в эту память записывается 0 в результате вызова дефолтного конструктора. V>Потом эта память нигде не используется, т.к. вызов св-ва SomeFlag1 JIT заинлайнил, соптимизировал и даже выкинул ненужную ветку if.
Нда, прикольно. Надо будет посмотреть на C# 10, где есть статические мемберы в интерфейсах. В нём можно убрать вот этот вот default(TConfig), и избежать вызова конструктора. Мне казалось, что одна из оптимизаций в пятом дотнете должна была убрать вызов конструктора для структур нулевого размера, но нет.
V>Кстате, кто-то сетовал, что в C# нельзя шаблоны генериков делать константами — вот, можно, через тип-"провайдер" таких констант.
V>Это техника из плюсов, где в подобных случаях при ветвлении на константе оптимизирующий компилятор сразу отбрасывает лишнюю ветку кода, поэтому, плюсовики без проблем позволяют себе лишние if-ы в подобных случаях.
V>(для воссоздания контекста — злейшим врагом современных программ являются лишние ветвления, т.к. они потенциально сбрасывают конвейер процессора)
S>>Именно там должна проводиться трассировка использования (вот эти вот все варнинги про value assigned to t is never used). У джита мало ресурсов и очень мало времени на то, чтобы отслеживать глобальные потоки данных.
V>Он не справляется даже в показанном простейшем случае. V>Я периодически открываю issue-s в дотнете, но тут рука пока не поднялась, бо явно не самый нужный сценарий для отрасли, хотя был бы полезен конкретно мне. ))
Лучше открывать pull request
V>Сейчас прогресс именно языка виднее. V>Разумеется, в т.ч. из-за вклада Липперта когда-то.
В основном — именно благодаря рослину. Тот компилятор, который был до него, не имело смысла опенсорсить — количество потенциальных контрибуторов туда было бы нулевым, особенно с учётом трудностей отладки.
V>Сравнил хрен с пальцем. V>Хейльсберг тоже красавчег.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[37]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией. S>По-моему, нормальный вариант — только один. Если API принимает struct, то мы и отдаём struct.
Или class, если требуется сохранить затем.
S>ref struct нужен для очень специальных сценариев.
Практически всегда, если данные не предназначены для последующего хранения, вплоть до того, что могут ссылаться на невалидную область памяти.
S>К сожалению, нормального способа работы со структурами переменного размера в дотнете/С# я не вижу. S>То есть можно сделать враппер вокруг byte[], который работает при помощи MemoryMarshal, и даже приемлемо по эффективности (после JIT все эти обращения к MemoryMarhshal.Cast<byte, IntPtr>(data.AsSpan().Slice(sizeof(int))[index] превращаются в in-place инструкции типа LEA), но код выглядит, мягко говоря, нечитаемым.
В select в линухах подаются битовые массивы, поэтому stackalloc решает вопрос.
Длина битовых массивов идёт первым аргументом — как максимальный номер хендла.
Но вообще г-но мамонта этот select, из времён, когда 640 кB хватало на всё.
S>Да дело даже не в этом. В платформе АПИ объектно-ориентирован — есть множество "типов" хэндлов, которые могут обрабатываться единым образом. S>В дотнете выбрана совершенно другая иерархия типов, из-за которой у сокета и файла нет ничего общего.
Дотнет унаследовал группы системных объектов от Windows, поэтому невозможно ожидать сигнала готовности сокета и файла в одной группе.
S>Поэтому крайне сложно реализовать библиотеку, которая бы, к примеру, умела ждать на разнородном наборе объектов.
В виндах это делается через IOCP.
Правда, ноль гибкости — один раз привязал сокет к completion port и с концами.
V>>В общем, туда как начинаешь вникать — всё за всё цепляется, принимать решения сложно, как всегда бывает, когда однозначно хорошего решения нет. V>>Каждый божий раз выбираешь некий набор компромиссов из альтернативных их наборов. S>Ну, да.
Но мне тут периодически пытаются доказать, что это не так. ))
V>>Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал. V>>Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX. S>Ну по идее так и должно быть. Если маршалинга нет (а для достижения этого есть все предпосылки), то должно делаться именно это.
А как же замкнуть фрейм стека для GC?
V>>Он не справляется даже в показанном простейшем случае. V>>Я периодически открываю issue-s в дотнете, но тут рука пока не поднялась, бо явно не самый нужный сценарий для отрасли, хотя был бы полезен конкретно мне. )) S>Лучше открывать pull request
Такие у меня тоже есть в текущем дотнете, но по-мелочи.
В RuiJIT сейчас чёрт ногу сломит, его надо серьёзно переделывать.
S>В основном — именно благодаря рослину. Тот компилятор, который был до него, не имело смысла опенсорсить — количество потенциальных контрибуторов туда было бы нулевым, особенно с учётом трудностей отладки.
ХЗ, именно в то время опенсорсный GCC развивался бешенными темпами, что аж догнал MSVC.
Re[38]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, Sinclair, Вы писали:
V>>>Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией. S>>По-моему, нормальный вариант — только один. Если API принимает struct, то мы и отдаём struct.
V>Или class, если требуется сохранить затем.
И сразу нарываемся на custom marshaller. Зачем?
V>Практически всегда, если данные не предназначены для последующего хранения, вплоть до того, что могут ссылаться на невалидную область памяти.
Хранение хранению рознь. У реф структ слишком много ограничений, поэтому использовать их захочется точечно.
S>>К сожалению, нормального способа работы со структурами переменного размера в дотнете/С# я не вижу. S>>То есть можно сделать враппер вокруг byte[], который работает при помощи MemoryMarshal, и даже приемлемо по эффективности (после JIT все эти обращения к MemoryMarhshal.Cast<byte, IntPtr>(data.AsSpan().Slice(sizeof(int))[index] превращаются в in-place инструкции типа LEA), но код выглядит, мягко говоря, нечитаемым.
V>В select в линухах подаются битовые массивы, поэтому stackalloc решает вопрос. V>Длина битовых массивов идёт первым аргументом — как максимальный номер хендла.
Ну это тривиальный частный случай. Как только мы встречаем что-то сложнее примитивного массива интов, начинаются танцы с бубном. Так-то можно и дотнетовый массив отмаршаллить без копирования.
V>>>Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал. V>>>Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX. S>>Ну по идее так и должно быть. Если маршалинга нет (а для достижения этого есть все предпосылки), то должно делаться именно это.
V>А как же замкнуть фрейм стека для GC?
А зачем?
V>Такие у меня тоже есть в текущем дотнете, но по-мелочи. V>В RuiJIT сейчас чёрт ногу сломит, его надо серьёзно переделывать.
Я на своём уровне некомпетентности вижу только одну глобальную проблему: нет, как такового, хотспоттинга. Tiered compilation выглядит как жалкое подобие левой руки. Может, оно и блеснёт в какой-то момент (например, развязав руки дорогостоящим оптимизациям в Tier1), но мне кажется, что как-то это всё очень вяло. Чтобы выжимать из managed code максимум, надо уметь помимо "ой, давайте при первом проходе просто отдавать говнокод" делать очень много profile-based вещей, типа спекулятивного инлайнинга. Как я понимаю, в текущей архитектуре джита и CLR это вообще недостижимо. Максимум, что мы сможем получить эволюционным путём — это tier2 поверх tier0 и tier1.
S>>В основном — именно благодаря рослину. Тот компилятор, который был до него, не имело смысла опенсорсить — количество потенциальных контрибуторов туда было бы нулевым, особенно с учётом трудностей отладки.
V>ХЗ, именно в то время опенсорсный GCC развивался бешенными темпами, что аж догнал MSVC.
Ну, тут спорный момент. Я в чём-то понимаю позицию команды. Для GCC была уверенность в том, что коммиты не будут в будущем списаны за ненадобностью.
Для Рослин была совершенно стандартная ситуация легаси — когда есть уже ясное видение, что в долгосрочной перспективе мы не хотим ехать на существующей технологии; инкрементально переехать на новую технологию мы не можем; при этом в каждый момент мы понимаем, что если мы вкладываемся в легаси, то получаем быстрый результат, но при этом а) отнимаем ресурсы от проекта портирования, и б) увеличиваем количество кода, которое надо будет портировать. Это отдаляет "светлое будущее" с удвоенной скоростью.
Многие компании на этом вообще ломаются. А MS ничего, всё же выехали с рослиным. Стиснули зубы, понимая, что рискуют аудиторией, но пилили.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[44]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали: V>Строки в датасете — это, грубо, маппинг сущностей-строк на индексы в массивах.
Ну, я не против того, чтобы делать это для какой-то конкретной задачи. Column-based storage вполне неплохой выбор для целого ряда нагрузок; и внутри оно организуется относительно несложно.
В том смысле, что альтернатива — это иметь массив структур заранее неизвестного (порождённого в рантайме на лету) типа.
Понятно, что сделать это можно, но авторы DataTable на это не решились. Это ок.
А вот навязывать такое в качестве реализации всем остальным... Ну, так.
S>>При этом "копировать", собственно, ничего не надо. В реализации IDataRow можно делать конвертацию на лету при помощи MemoryMarshal.
V>Можно посмотреть на такую реализацию?
Имя интерфейса перепутал.
IDataRecord, а не IDataRow.
Воображаем примерно такую штуку:
public struct DataRecord: IDataRecord
{
private Span<byte> _rawData;
public int GetInt32(int i) =>
GetFieldType(i) == typeof(int)
? MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, int>(_rawData.Slice(GetFieldOffset(i)));
: Convert.ToInt32(GetValue(i));
...
}
V>Это всё риторическое. )) V>Избавление от лишних копирований — неплохая награда, чтобы вот прям так категорично...
Я пока не очень представляю, в каких сценариях я бы выбрал Unsafe вместо MemoryMarshal.
V>Если берут плюсы, то выжимают эффективность, иначе бы зачем брать плюсы?
Чтобы выжимать эффективность, в основном надо думать о динамическом построении запросов. Пока в примерах идёт передача SQL Statement в виде тупо строки, про производительность можно вообще не заикаться.
Экономим микросекунды, проигрываем секунды на неудачных планах запросов.
V>Плюсы вообще редко общаются с базой напрямую, кроме как для локальных хранилищ, но там схема данных обычно тривиальнейшая.
Ну, так и есть. Вместо "напрямую" наверняка используется какая-то существующая инфраструктура типа ODBC или ADO. V>А если общаются с сервером, то чаще на ответной стороне сервер приложений со своим кешированием, который отдаёт ответ на предопределённые запросы через предопределённые же типизированные наборы данных. И тоже задержки малость не те, к которым привыкли в дотнете.
Сервер приложений там на чём написан? На дотнете?
V>И обычно данные справочного характера кешируются на клиенте, т.е. по сети лишний раз не гоняются, что тоже резко отличается от принятого в дотнете (по крайней мере, гдя я на это внимательно смотрел).
Эта технология строго ортогональнаа применяемой платформе. Client-side join и кэширование можно строить хоть на дотнете, хоть на плюсах, хоть на жаве. Там скорее вопросы возникают к архитектуре в крупном масштабе — типа "а как мы этот кэш инвалидируем".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, vdimas, Вы писали:
V>Простой пример:
V>Но, создание экземпляра Config1 длиной 1 байт происходит.
V>Причём, происходит в 2 этапа — сначала под него выделяется память и обнуляется. V>Потом в эту память записывается 0 в результате вызова дефолтного конструктора. V>Потом эта память нигде не используется, т.к. вызов св-ва SomeFlag1 JIT заинлайнил, соптимизировал и даже выкинул ненужную ветку if.
Да, на эту тему ишью видел. Текущий JIT не очень любит dup, который генерирует шарп в случае default(T).SomeFileOrProp. Но это можно обойти, если написать так:
TConfig config = default;
if(config.SomeFlag1) {
// case 1
}
else {
// case 2
}
Тогда JIT все разруливает правильно. Можно (статическое) свойство сделать, которое возвращает private TConfig Config => default и обращаться уже к нему, что JIT тоже поймет
Здравствуйте, Sinclair, Вы писали:
S>Нда, прикольно. Надо будет посмотреть на C# 10, где есть статические мемберы в интерфейсах. В нём можно убрать вот этот вот default(TConfig), и избежать вызова конструктора. Мне казалось, что одна из оптимизаций в пятом дотнете должна была убрать вызов конструктора для структур нулевого размера, но нет.
Ишью на эту тему уже есть, вроде в 5 хотели исправить, не успели, в 6 (Preview 7) тоже не сделали. Там проблема c dup, который плохо понимает их VN и анализ потока данных на этом ломается, что видно по сгенерированному коду. Дело в том, что шарп пытается уменшить объем IL кода или просто упрощает себе работу и вставляет dup для дублирования вершины стека и код получается такой:
то соответственно и в ASM ничего лишнего уже не будет.
S>Нда, прикольно. Надо будет посмотреть на C# 10, где есть статические мемберы в интерфейсах. В нём можно убрать вот этот вот default(TConfig), и избежать вызова конструктора.
Да, ничего лишнего. Только что проверил
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[44]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>Это где это каждый столбец представлен массивом? НС>Это старый добрый DataSet так устроен. Знания про работу с БД, видимо, 15-летней давности или когда там датасеты окончательно протухли.
Ну и кто тут опять бред несёт? ))
Dapper (который маппер поверх ADO.Net) для несложных/одноразовых вещей используют аж бегом, и в ближайшие годы отказываться от него не собираются.
Он примерно вдвое быстрее EF при холодном старте.
Re[45]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Строки в датасете — это, грубо, маппинг сущностей-строк на индексы в массивах. S>Ну, я не против того, чтобы делать это для какой-то конкретной задачи. Column-based storage вполне неплохой выбор для целого ряда нагрузок;
Оно там не столько для каких то нагрузок, сколько для облегчения жизни GC. Там же идеология в те времена была — по сути in memory db с change tracking. Как следствие — большое количество долго живущих классов. Вот и оптимизировали, устраняя классы и боксинг.
S>В том смысле, что альтернатива — это иметь массив структур заранее неизвестного (порождённого в рантайме на лету) типа.
Во времена первой версии дотнета с генерацией кода было все довольно непросто. А учитывая что тип записи мог формироваться динамически (AddColumn никто не отменял), а GC сборки удалять не умел совсем — это еще и прекрасные такие грабли.
... << RSDN@Home 1.3.17 alpha 5 rev. 62>>
Re[45]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, vdimas, Вы писали: V>>Строки в датасете — это, грубо, маппинг сущностей-строк на индексы в массивах. S>Ну, я не против того, чтобы делать это для какой-то конкретной задачи. Column-based storage вполне неплохой выбор для целого ряда нагрузок;
Для системы типов .Net и способа-то другого нет.
Остальное в общем случае требует динамическую кодогенерацию, т.е. размен скорости холодного старта на "естественность" представления.
S>и внутри оно организуется относительно несложно.
Про сложность речи не было.
Я указывал на то, что в дотнете в любом случае дважды происходит дополнительное копирование уже принятых данных.
Неважно — в датасет, или в entity-объекты всевозможных вариантов EF.
То бишь, любые принятые от BD данные на клиенте будут утроены в объёме.
(на самом деле более чем утроены, т.к. после боксирования примитивные типы занимают в памяти прилично места в сравнении с исходным размером)
S>В том смысле, что альтернатива — это иметь массив структур заранее неизвестного (порождённого в рантайме на лету) типа. S>Понятно, что сделать это можно, но авторы DataTable на это не решились. Это ок.
На технике 2001-го года? (когда дотнет разрабатывали)
Я бы тоже не решился.
S>А вот навязывать такое в качестве реализации всем остальным... Ну, так.
Однако, некоторые ORM-мапперы до сих пор живут поверх DataSet и живут неплохо.
Т.е. востребованы.
Скорость холодного старта тоже не последнюю рояль играет, т.е. для клиентской стороны выбор технологии DAL опять и снова не так прост.
S>IDataRecord, а не IDataRow. S>Воображаем примерно такую штуку:
Причём, независимо от технологии DAL — ORM поверх DataSet, LinqToDB или EF — все они проходят через код по ссылке.
Но это еще не самое грустное...
Под капотом, в драйвере связи с БД будут готовые к использованию табличные данные, но в дотнете сделали так, что их можно лишь зачитать однонаправленно, даже если запрошен тип рекордсета снапшот, а он практически всегда именно такой, бо динамические рекордсеты признаны злейшим злом еще во второй половине 90-х.
В OLEDB и ODBC в похожих случаях можно рассматривать зачитанные данные как коллекцию с произвольным доступом и читать/навигироваться по данным, находящимся непосредственно в приёмном буфере драйвера (эти драйвера юзер-спейсные, ес-но, бо они лишь формируют и парсят потоки байт).
Собсно, DAO когда-то, хоть его и ругали за отсутствие оптимизирующего движка для сложных запросов, но в относительно простых запросах по локальной БД ему не было равных в шустрости. В т.ч. потому что приложению на его основе не требовалось копировать данные — данные читались прямо в момент обработки события WM_PAINT контрола-грида прямо из буфера драйвера.
На той технике и выбора-то другого не было.
V>>Избавление от лишних копирований — неплохая награда, чтобы вот прям так категорично... S>Я пока не очень представляю, в каких сценариях я бы выбрал Unsafe вместо MemoryMarshal.
Думаю, сейчас представлений у тебя чуть больше, а я это копнул еще на первой бете дотнета, бо тогда с базами работал плотнее, чем сейчас.
И продолжаю уже 20 лет оставаться в недоумении, что с тех пор ничего толком не изменилось.
Т.е., сильно изменилось на верхнем уровне, а на нижнем — такое же мракобесие 20-тилетней давности.
V>>Если берут плюсы, то выжимают эффективность, иначе бы зачем брать плюсы? S>Чтобы выжимать эффективность, в основном надо думать о динамическом построении запросов.
Мы это уже обсуждали.
Всё мн-во, скажем так, "структур" запросов в реальных приложениях конечно, кроме специализированных приложений для динамического построения произвольных запросов, которых я видел всего парочку за всю карьеру.
То бишь, с клиента в продуманном приложении достаточно послать ID запроса и параметры к нему.
И на стороне базы неплохо бы эти запросы оформить в виде сохраненных view или процедур.
Понятно, что EF избавляет от надобности программировать БД, т.е. позволяет рожать наколенные решения пачками...
(EF сама воссоздаёт недостающие таблицы по метаинформации маппинга)
Но тогда все эти наши многочисленные обсуждения про грамотное индексирование данных и прочее идёт фтопку. ))
А еще есть SQL-хинты, которые порой могут поднять быстродействие нагруженной базы на порядок.
В общем, именно поэтому на исключение этапа программирования БД я смотрю несколько скептически.
(хотя и согласен, что многие задачи такой тщательности разработки не требуют)
S>Пока в примерах идёт передача SQL Statement в виде тупо строки, про производительность можно вообще не заикаться.
А твой LinQ не строку формирует в итоге, что ле?
Тоже строку, только медленнее.
Во все времена быстрее всего было вызвать хранимку в синтаксисе ODBC "{call InsertOrder(, 10, ?, ?, ?)}" c забинженными аргументами.
(синтаксис работает не только с ODBC-дровами)
Быстрее будет только непосредственная навигация по индексированным наборам in-proc баз, когда заранее получены референсы на все таблицы, процедуры и скомпиллированные запросы.
Например, когда MySql используется в виде in-proc либы.
S>Экономим микросекунды, проигрываем секунды на неудачных планах запросов.
Началось...
У "красных" все планы запросов заведомо удачные, у "белых" заведомо нет, поэтому "наши" всегда побеждают. ))
Но ответить на простой вопрос "почему ты так решил?" уже 15-й год не можешь.
Предлагаю с таким подходом завязать раз и навсегда, бо именно так любое обсуждение превращается в абсурд.
V>>Плюсы вообще редко общаются с базой напрямую, кроме как для локальных хранилищ, но там схема данных обычно тривиальнейшая. S>Ну, так и есть. Вместо "напрямую" наверняка используется какая-то существующая инфраструктура типа ODBC или ADO.
Я же уже писал выше — границы абстракций DAL при таких раскладах выгодней делать чуть выше, т.е. не по OLEDB/ODBC/ADO и провайдерам/адаптерам диалектов SQL, а прямо по всему DAL.
Это отличается от принятой в дотнете практики.
С другой стороны, шаблонный код позволяет выглядеть "этому" так, будто границы абстракций проходят заметно ниже, чем есть в реальности.
Т.е., в любом случае трудоёмкость сравнимая, т.к. сравнимая степень переиспользования кода.
Просто приличная часть абстракций перетекает из runtime в compile-time, как оно принято в плюсах.
V>>А если общаются с сервером, то чаще на ответной стороне сервер приложений со своим кешированием, который отдаёт ответ на предопределённые запросы через предопределённые же типизированные наборы данных. И тоже задержки малость не те, к которым привыкли в дотнете. S>Сервер приложений там на чём написан? На дотнете?
Редко.
Обычно плюсы или джава.
Хотя и джавой ту джаву тоже с трудом можно назвать, чего они только не ухитряются делать, эмулируя value-типы на массивах байт.
V>>И обычно данные справочного характера кешируются на клиенте, т.е. по сети лишний раз не гоняются, что тоже резко отличается от принятого в дотнете (по крайней мере, гдя я на это внимательно смотрел). S>Эта технология строго ортогональнаа применяемой платформе.
Разумеется.
Что не мешает мне наблюдать принятые в той или иной технологии практики.
S>Client-side join и кэширование можно строить хоть на дотнете, хоть на плюсах, хоть на жаве. Там скорее вопросы возникают к архитектуре в крупном масштабе — типа "а как мы этот кэш инвалидируем".
И опять разумеется.
Но стоит мне сравнить объективно происходящее со сложившейся ситуацией в Дельфи когда-то, где "если нужный ТКомпонент не найден, то задача не имеет решения", как на нейтив в отместку льются разливные реки помоев.
Т.е., степень пригорания зашкаливающая, хотя дотнет не запрещает разрабатывать собственные технологии, чем конкретно я на дотнете активно занят.
И почему наши дотнетные библиотеки неплохо продаются.
Т.е. не как у тебя по работе, где продаётся, скорее, инфраструктура, чем код, а просто либа с выделенной функциональностью и нехарактерными для мира дотнета техническими характеристиками.
На принятые в дотнете мейнстримовые практики мне приходится смотреть не просто так, а с целью хорошо в них ориентироваться.
Потому что невозможно формулировать так: "нам нужна быстрая программа!", нужно уметь ответить на вопрос "насколько быстрая?"
Т.е., с чем будем сравнивать?
Где параметры, по достижении которых задачу можно будет считать решенной (хотя бы на очередной итерации)?
Здравствуйте, Ночной Смотрящий, Вы писали:
V>>Dapper (который маппер поверх ADO.Net) для несложных/одноразовых вещей используют аж бегом, НС>Dapper был написан в те самые времена, когда оно было актуально. Так и осталось.
Ты озвучил "протухло", я показал пример, где отнюдь.
Re[47]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Ночной Смотрящий, Вы писали:
V>>Ты озвучил "протухло", я показал пример, где отнюдь. НС>За последние 10 лет я не видел ни одного живого проекта с датасетами, хотя проектов с БД я видел за это время очень много.
Ясно, т.е. опять неверно построил аргументацию, а я снова ломай голову, как бы дать человеку понять, что он отвечает невпопад, и чтобы не наблюдать потом возгорание...
Обсуждалось устройство внутренних кишок.
Я до этой итерации не задумывался о важности того момента, прямым образом эти кишки пользуют или через ORM-мапперы, типа Dapper.
Да и сейчас думать в эту сторону облом.
Здравствуйте, Sinclair, Вы писали:
S>>>По-моему, нормальный вариант — только один. Если API принимает struct, то мы и отдаём struct. V>>Или class, если требуется сохранить затем. S>И сразу нарываемся на custom marshaller. Зачем?
На практике не нарываемся, если класс содержит только примитивные типы в полях.
Структуры в АПИ обычно идут по указателю, на стороне C# это ref для struct или просто классы по ссылке.
Маршаллер пинит экземпляры таких классов, вот и весь маршаллинг.
V>>Практически всегда, если данные не предназначены для последующего хранения, вплоть до того, что могут ссылаться на невалидную область памяти. S>Хранение хранению рознь. У реф структ слишком много ограничений
Ограничений, считай, два всего:
— они могут располагаться только на стеке;
— не могут быть аргументами генериков.
Оба случая хорошо подходят под интероп.
V>>В select в линухах подаются битовые массивы, поэтому stackalloc решает вопрос. V>>Длина битовых массивов идёт первым аргументом — как максимальный номер хендла. S>Ну это тривиальный частный случай. Как только мы встречаем что-то сложнее примитивного массива интов, начинаются танцы с бубном.
Такой же stackalloc начинается.
V>>А как же замкнуть фрейм стека для GC? S>А зачем?
Чтобы GC игнорировал стек нейтивных вызовов.
Т.е., возможна зебра вглубь всех вызовов:
— из управляемого кода в нейтив;
— оттуда приходит колбэк в управляемый код (многие перечисления в системных АПИ так работают);
— из этого фрейма опять вызывается что-то в нейтиве.
Указанные два "островка" управляемого стека должны быть связаны в однонаправленный список, от текущего верха стека к его нижним фреймам.
Дотнетный дебаггер при отладке, кстате, показывает места, где участки нейтивного стека пропущены.
V>>Такие у меня тоже есть в текущем дотнете, но по-мелочи. V>>В RuiJIT сейчас чёрт ногу сломит, его надо серьёзно переделывать. S>Я на своём уровне некомпетентности вижу только одну глобальную проблему: нет, как такового, хотспоттинга. Tiered compilation выглядит как жалкое подобие левой руки.
Tiered compilation и есть hotspot.
В моём примере с IConfig до появления Tiered compilation джит сразу генерил свой оптимизированный, но глупый код.
Теперь этот код надо исполнять сколько-то раз в цикле, чтобы джит его соптимизировал, иначе там 1-в-1 с исходником.
Tiered compilation выглядит как попытка ускорить старт приложения и ничего более.
Средней величины дотнетное приложение до него запускалось по 600-800 ms, сейчас примерно вдвое быстрее.
Особенно чувствительны к этой технологии те приложения, где инициализируется много статических данных (например, WPF и прочие, где описаны мильоны dependency property).
Cтоимость джита инициализирующего кода была в разы больше стоимости его исполнения, а код-то одноразовый.
S>Может, оно и блеснёт в какой-то момент (например, развязав руки дорогостоящим оптимизациям в Tier1), но мне кажется, что как-то это всё очень вяло. Чтобы выжимать из managed code максимум, надо уметь помимо "ой, давайте при первом проходе просто отдавать говнокод" делать очень много profile-based вещей, типа спекулятивного инлайнинга. Как я понимаю, в текущей архитектуре джита и CLR это вообще недостижимо. Максимум, что мы сможем получить эволюционным путём — это tier2 поверх tier0 и tier1.
Спасёт только качественный AOT.
Но разговоры об этом идут последние лет 15.
Последнее актуальное:
We are building a new version of Crossgen – Crossgen 2 – which starts with a new code base architected to be a compiler that can perform analysis and optimizations not possible with the previous version.
К нашей пенсии всё будет ОК, не переживай. ))
V>>ХЗ, именно в то время опенсорсный GCC развивался бешенными темпами, что аж догнал MSVC. S>Ну, тут спорный момент. Я в чём-то понимаю позицию команды. Для GCC была уверенность в том, что коммиты не будут в будущем списаны за ненадобностью. S>Для Рослин была совершенно стандартная ситуация легаси — когда есть уже ясное видение, что в долгосрочной перспективе мы не хотим ехать на существующей технологии;
Ммм...
В общем, там несравнимые объемы и сложность кода, имелось ввиду это.
Если тебе Рослин кажется сложным проектом — ты сильно заблуждаешься.
Рослин простой как балалайка, хотя и достаточно объемный.
И что странно — нормального оптимизирующего компилятора C# всё еще нет.
Т.е. слишком многое оставлено джиту такого, что можно причесать и до джит.
Например, тот же пример с SomeClass<Config1> c1 = ...; c.Foo();
Если все типы объявлены в одной сборке, там нефик делать соптимизировать лишнее компилятором.
Аналогично куча строковых вычислений и прочих, особенно при инициализации — компилятор честным образом отдаёт это всё в рантайм, хотя там до половины и более зачастую вычислимы в compile time.
S>инкрементально переехать на новую технологию мы не можем; при этом в каждый момент мы понимаем, что если мы вкладываемся в легаси, то получаем быстрый результат, но при этом а) отнимаем ресурсы от проекта портирования, и б) увеличиваем количество кода, которое надо будет портировать. Это отдаляет "светлое будущее" с удвоенной скоростью.
Да не факт.
Основное при разработке сложных проектов — хорошо понимать собственный код.
Итеративность в этом смысле привносит оперативность.
Т.е., в чём-то больше работы, да, но в целом происходящее оперативнее, т.к. некоторые бока/недоработки нового кода раньше вскрываются.
Любому "светлому будущему" надо по классике отводить примерно 9 месяцев на что-то средней сложности и не более 18-ти для чего-то совсем кардинального.
А тут 5-6 лет возни.
Это
И не переубедишь. ))
S>Многие компании на этом вообще ломаются.
Именно.
Просто MS сломать не так просто.
S>А MS ничего, всё же выехали с рослиным. Стиснули зубы, понимая, что рискуют аудиторией, но пилили.
Да, аудитория начала разбегаться.
Спас ход конём — выкатили для линухов и ушли в опенсорс, аудитория потихоньку возвращается.