Информация об изменениях

Сообщение Re[48]: MS забило на дотнет. Питону - да, сишарпу - нет? от 03.09.2021 10:26

Изменено 03.09.2021 11:33 Sinclair

Re[48]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:

V>Я привожу что есть по факту.

V>А то, что у тебя — это надо самим делать обертку поверх ODBC и там малость не так тривиально, но это уже подробности.
Я тоже привожу то, что есть по факту.

S>>Посмотрите, например, сюда: https://referencesource.microsoft.com/#system.data/Microsoft/SqlServer/Server/SqlDataRecord.cs


V>Не ту ссылку дал, надо смотреть сюда:

V>https://referencesource.microsoft.com/#system.data/fx/src/data/Microsoft/SqlServer/Server/MemoryRecordBuffer.cs,17
V>Путь данных еще более извилист и всё-равно данные еще дважды копируются.
Где они там дважды копируются? Там же лежит union.
V>И в любом случае твой пример только для MSSQL, а для любых других баз на основе OLEDB или ODBC будет как я дал ссылку на исходники дотнета.
Вы опять путаете дотнет как платформу и набор библиотек поверх платформы.
В дотнете есть IDataRecord, который можно реализовать очень эффективно. Вы же почему-то пишете про то, как в нативных приложениях мы используем нативные оптимизированные драйвера БД; а в дотнете вы хотите использовать максимально абстрактный код. Давайте тогда я в нативе буду использовать COM через IDispatch и конвертировать все данные через Variant.

V>>>Под капотом, в драйвере связи с БД будут готовые к использованию табличные данные, но в дотнете сделали так, что их можно лишь зачитать однонаправленно, даже если запрошен тип рекордсета снапшот, а он практически V>Через миллион телодвижений унутре.

V>linq2db — это самая вершинка айберга, пока данные до него дойдут — сто раз вспотеют.
Данные в него напрямую попадают из IDataRecord, поверх которого генерируется специфический для возвращаемого типа код. Как я показал, в реализации IDataRecord мы можем избегать копирования и выдавать данные напрямую из того буфера, который нам отдала СУБД в качестве результата.

V>А чем рендеринг в HTML/JSON принципиально отличается от рендеринга куда-то еще?

Тем, что нам не нужно бегать по датасету взад-вперёд. Это прямо противопоказано с точки зрения эффективности.

V>ОК, пусть даже в одну сторону.

V>Я всё-равно не понимаю, зачем унутре реализации в дотнете создают копии данных для каждой строки.
"В дотнете" ничего не создают. Конкретные дата провайдеры пишутся в соответствии с представлениями о прекрасном каждой из команд.

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

V>Грубо, для каждого поля подготавливается акцессор.
V>Будут его потом дёргать или нет — дело клиента.
Так и есть. Для обобщённой реализации — см. IDataRecord.
Для специфичной — делаем враппер, который достаёт данные согласно разметке.
Но эту реализацию никто не навязывает — тем более, что средства для нормальной работы в таком стиле появились в платформе, грубо говоря, два года назад.

V>Сорри, но ты сейчас малость из пальца насасываешь.

V>Оно примерно так и было, как есть сейчас.
Я говорю про Span<T>, Memory<T>, MemoryMarshal, ref struct, unmanaged constraint.
Это — революционные изменения в платформе, без которых бессмысленно говорить о каких-либо оптимизациях. Т.к. например банальный парсинг даты из хидера HTTP требовал сначала скопировать байты, затем превратить их в UTF16, и потом мучительно парсить — то есть речь о двойном копировании с двукратным раздуванием объёма.

V>Да и сам OLEDB драйвер к MS SQL когда-то был оберткой над ODBC-кодом их же драйвера, пока не написали родной для OLEDB.

Это всё и так понятно. Я и говорю — верхний уровень всё ещё работает на технологиях типа LayoutKind.Explicit с FieldOffset(0).

V>Не аргумент.

V>Таких отображений в этом "первом приложении" (которое магазин какой-нить) аж одно на каждую группу товаров.
Что такое "аж одно"? У тебя на N колонок 2N вариантов order by

S>>В каждом втором — кнопки "отфильтровать" в заголовках колонок.


V>Попадает в мой случай.

Что именно попадает в ваш случай? У вас будет N параметров к вашей хранимке, из которых M будет NULL?

S>>В каждом третьем — кнопки "скрыть/показать столбцы".


V>Где скрыть их можно на любом уровне, особенно если речь шла о клиенте.

V>Т.е. на уровне запроса к БД, на уровне сервера приложений, прямо на клиенте.

V>Вряд ли в клиентском приложении при сокрытии или отображении колонок будет заново делаться запрос к базе.

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

S>>Всё это — динамические запросы.


V>Ну вот смотри, я понаставил кучу флажков:

V>https://www.wildberries.ru/catalog/elektronika/telefony-i-gadzhety/mobilnye-telefony?sort=newly&amp;cardsize=c246x328&amp;page=1&amp;f10466=3722640%3B514814%3B3725238%3B3725535%3B18630%3B18632%3B18640%3B441183%3B3199823&amp;f90746=214513%3B142905%3B140423%3B140427%3B140428%3B172656%3B172657&amp;f75438=3698872%3B3698698%3B3749957%3B130329%3B3698382%3B174305%3B81543%3B81534%3B121020

V>Флажки булевские, т.е. кодируются битами.

V>Покажи, где ты здесь увидел динамический запрос?

V>Не, я примерно понимаю, на что ты намекал, на длиннющие условия навроде:

V>OR WorkTime=T133 OR WorkTime=T160 OR ...
V>И так по всем группам условий, не только по WorkTime...
V>И не говори, что ты на это не намекал, потому что только такое представление требует динамического построения. ))
Например — да.

V>А стоит взять битовый вектор, и запрос динамически строить не придётся — просто подаётся требуемая маска как аргумент.

V>На стороне базы маска займёт столько integer-полей, сколько требуется.
V>Допустим, аж пару BigInt, итого у нас комбинаторика по колву масок:
V>0 0
V>0 1
V>1 0
V>1 1
V>Всего 4 запроса.
V>Или два — 0 0 и 1 1, т.е., если есть хоть один бит — формируем маску целиком.
Отличная идея, коллега. Вы только что заставили движок вашей базы всегда делать table scan.
Потому, что другого способа сделать where (flags & @flags) > 0 не существует.
Это будет прекрасно приемлемо работать ровно до тех пор, пока у вас хватает кэша на то, чтобы держать всю базу в памяти.
Ну, и пока у вас набор всех значений всех атрибутов не очень велик — что позволяет уложить их в битики.

V>Показал.

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


V>Но хинты уникальны для различных провайдеров.

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

V>И с чего ты решил, что это быстрее, даже если бы в некоей другой технологии тоже формировали такую же ужасную строку?

V>Судя по интонации подаваемого тобой — "просто нравится".
Быстрее получается тогда, когда мы не пишем внутри нашей хранимки код типа
... inner join customer c on c.id = o.customerID
where (c.Name like '%'&@customerName&'%' or (@customerName is null))

V>>>Тоже строку, только медленнее.
S>>Теряем микросекунды, выигрываем разы.

V>Если строки запросов те же — ничего не выигрываешь.

Ну так в том то и дело, что не те же.


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

Это так кажется. Можно включить sql server profiler и убедиться, что значения агрументов (те самые ?) передаются не какой-то магией, а просто текстовой строкой. Которая точно так же клеится динамически.
И экономия достигается не за счёт константности, а за счёт того, что используется готовый план запроса из кэша.


V>Откуда у тебя "один запрос"?

V>Можно цитату?
Вы же сами приводите пример с prepared statement с фиксированным списком аргументов


V>И что помешало тебе самостоятельно пройти дисциплину "теория кодирования информации"?

V>Там несложно.
Встречный вопрос: что помешало почитать хоть что-то про устройство СУБД? Ну, то есть помимо понимания, какой именно SQL написать для получения результата, разобраться, как именно этот SQL исполняется?


V>Как ты при проектировании борешься с избыточностью, не понимая самого этого понятия?

V>Блин, 2^N запросов ))
А как вы при проектировании боретесь со стоимостью исполнения, с O(N) и O(N*M) вместо O(log(N))?

V>А смысл, если для получения одного значения int одного поля будут десятки виртуальных вызовов и пара промежуточных копирований?

Повторюсь: для получения одного значения int одного поля при корректной реализации в дотнете будет сделан один виртуальный вызов без промежуточных копирований.
А если реализовать интерфейс в struct типе, то можно избавиться и от вызовов совсем. То есть при перекладывании данных из провайдера в JSON будет напрямую StringBuilder.Append(и вот тут заинлайненный код обрашения к _dataBufferPtr + currentRecordOffset + _fieldOffset).


V>Если в джавовских программах доходит до эмуляции value-типов массивами байт, то можешь примерно представить степень вылизанности остального.

Отлично представляю. Те программы, которые написаны без лобзика по вазелину, работают из рук вон плохо. За время стартапа типичной джава программы у меня успевают прогнаться тесты дотнет-библиотеки.
Джаву более-менее спасает могучий джит — если ему удаётся заинлайнить вызываемый код, то результирующий бинарь вполне приличен. Если нет — то адские тормоза, честная интерпретация, и прочее.

V>Хотя, всё-равно нубство, их LMAX Disruptor сосёт рядом с нашими решениями.

А то.

V>Я хорошо в курсе, что в дотнете так вылизывать пока мест не принято, к счастью.

V>И даже высмеивается.
V>Но чем громче высмеивается, тем нам лучше.
Понятия не имею, кем и что высмеивается. В дотнете вылизывать очень долго было нельзя. Сейчас появилась возможность; и люди ей пользуются.

V>Много тыщ в год за лицензию на каждую либу.

V>Прайс публично не висит, продажами занимаются сейлзы индивидуально, дать более точную инфу не могу себе позволить.
Ну хоть адрес лендинга ваших либ дайте — почитаю маркетинговые заманухи.
Re[48]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, vdimas, Вы писали:

V>Я привожу что есть по факту.

V>А то, что у тебя — это надо самим делать обертку поверх ODBC и там малость не так тривиально, но это уже подробности.
Я тоже привожу то, что есть по факту.

S>>Посмотрите, например, сюда: https://referencesource.microsoft.com/#system.data/Microsoft/SqlServer/Server/SqlDataRecord.cs


V>Не ту ссылку дал, надо смотреть сюда:

V>https://referencesource.microsoft.com/#system.data/fx/src/data/Microsoft/SqlServer/Server/MemoryRecordBuffer.cs,17
V>Путь данных еще более извилист и всё-равно данные еще дважды копируются.
Где они там дважды копируются? Там же лежит union.
V>И в любом случае твой пример только для MSSQL, а для любых других баз на основе OLEDB или ODBC будет как я дал ссылку на исходники дотнета.
Вы опять путаете дотнет как платформу и набор библиотек поверх платформы.
В дотнете есть IDataRecord, который можно реализовать очень эффективно. Вы же почему-то пишете про то, как в нативных приложениях мы используем нативные оптимизированные драйвера БД; а в дотнете вы хотите использовать максимально абстрактный код. Давайте тогда я в нативе буду использовать COM через IDispatch и конвертировать все данные через Variant.

V>>>Под капотом, в драйвере связи с БД будут готовые к использованию табличные данные, но в дотнете сделали так, что их можно лишь зачитать однонаправленно, даже если запрошен тип рекордсета снапшот, а он практически V>Через миллион телодвижений унутре.

V>linq2db — это самая вершинка айберга, пока данные до него дойдут — сто раз вспотеют.
Данные в него напрямую попадают из IDataRecord, поверх которого генерируется специфический для возвращаемого типа код. Как я показал, в реализации IDataRecord мы можем избегать копирования и выдавать данные напрямую из того буфера, который нам отдала СУБД в качестве результата.

V>А чем рендеринг в HTML/JSON принципиально отличается от рендеринга куда-то еще?

Тем, что нам не нужно бегать по датасету взад-вперёд. Это прямо противопоказано с точки зрения эффективности.

V>ОК, пусть даже в одну сторону.

V>Я всё-равно не понимаю, зачем унутре реализации в дотнете создают копии данных для каждой строки.
"В дотнете" ничего не создают. Конкретные дата провайдеры пишутся в соответствии с представлениями о прекрасном каждой из команд.

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

V>Грубо, для каждого поля подготавливается акцессор.
V>Будут его потом дёргать или нет — дело клиента.
Так и есть. Для обобщённой реализации — см. IDataRecord.
Для специфичной — делаем враппер, который достаёт данные согласно разметке.
Но эту реализацию никто не навязывает — тем более, что средства для нормальной работы в таком стиле появились в платформе, грубо говоря, два года назад.

V>Сорри, но ты сейчас малость из пальца насасываешь.

V>Оно примерно так и было, как есть сейчас.
Я говорю про Span<T>, Memory<T>, MemoryMarshal, ref struct, unmanaged constraint.
Это — революционные изменения в платформе, без которых бессмысленно говорить о каких-либо оптимизациях. Т.к. например банальный парсинг даты из хидера HTTP требовал сначала скопировать байты, затем превратить их в UTF16, и потом мучительно парсить — то есть речь о двойном копировании с двукратным раздуванием объёма.

V>Да и сам OLEDB драйвер к MS SQL когда-то был оберткой над ODBC-кодом их же драйвера, пока не написали родной для OLEDB.

Это всё и так понятно. Я и говорю — верхний уровень всё ещё работает на технологиях типа LayoutKind.Explicit с FieldOffset(0).

V>Не аргумент.

V>Таких отображений в этом "первом приложении" (которое магазин какой-нить) аж одно на каждую группу товаров.
Что такое "аж одно"? У тебя на N колонок 2N вариантов order by

S>>В каждом втором — кнопки "отфильтровать" в заголовках колонок.


V>Попадает в мой случай.

Что именно попадает в ваш случай? У вас будет N параметров к вашей хранимке, из которых M будет NULL?

S>>В каждом третьем — кнопки "скрыть/показать столбцы".


V>Где скрыть их можно на любом уровне, особенно если речь шла о клиенте.

V>Т.е. на уровне запроса к БД, на уровне сервера приложений, прямо на клиенте.

V>Вряд ли в клиентском приложении при сокрытии или отображении колонок будет заново делаться запрос к базе.

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

S>>Всё это — динамические запросы.


V>Ну вот смотри, я понаставил кучу флажков:

V>https://www.wildberries.ru/catalog/elektronika/telefony-i-gadzhety/mobilnye-telefony?sort=newly&amp;cardsize=c246x328&amp;page=1&amp;f10466=3722640%3B514814%3B3725238%3B3725535%3B18630%3B18632%3B18640%3B441183%3B3199823&amp;f90746=214513%3B142905%3B140423%3B140427%3B140428%3B172656%3B172657&amp;f75438=3698872%3B3698698%3B3749957%3B130329%3B3698382%3B174305%3B81543%3B81534%3B121020

V>Флажки булевские, т.е. кодируются битами.

V>Покажи, где ты здесь увидел динамический запрос?

V>Не, я примерно понимаю, на что ты намекал, на длиннющие условия навроде:

V>OR WorkTime=T133 OR WorkTime=T160 OR ...
V>И так по всем группам условий, не только по WorkTime...
V>И не говори, что ты на это не намекал, потому что только такое представление требует динамического построения. ))
Например — да.

V>А стоит взять битовый вектор, и запрос динамически строить не придётся — просто подаётся требуемая маска как аргумент.

V>На стороне базы маска займёт столько integer-полей, сколько требуется.
V>Допустим, аж пару BigInt, итого у нас комбинаторика по колву масок:
V>0 0
V>0 1
V>1 0
V>1 1
V>Всего 4 запроса.
V>Или два — 0 0 и 1 1, т.е., если есть хоть один бит — формируем маску целиком.
Отличная идея, коллега. Вы только что заставили движок вашей базы всегда делать table scan.
Потому, что другого способа сделать where (flags & @flags) > 0 не существует.
Это будет прекрасно приемлемо работать ровно до тех пор, пока у вас хватает кэша на то, чтобы держать всю базу в памяти.
Ну, и пока у вас набор всех значений всех атрибутов не очень велик — что позволяет уложить их в битики.

V>Показал.

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


V>Но хинты уникальны для различных провайдеров.

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

V>И с чего ты решил, что это быстрее, даже если бы в некоей другой технологии тоже формировали такую же ужасную строку?

V>Судя по интонации подаваемого тобой — "просто нравится".
Быстрее получается тогда, когда мы не пишем внутри нашей хранимки код типа
... inner join customer c on c.id = o.customerID
where (c.Name like '%'&@customerName&'%' or (@customerName is null))

V>>>Тоже строку, только медленнее.
S>>Теряем микросекунды, выигрываем разы.

V>Если строки запросов те же — ничего не выигрываешь.

Ну так в том то и дело, что не те же.


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

Это так кажется. Можно включить sql server profiler и убедиться, что значения агрументов (те самые ?) передаются не какой-то магией, а просто текстовой строкой. Которая точно так же клеится динамически.
И экономия достигается не за счёт константности, а за счёт того, что используется готовый план запроса из кэша.


V>Откуда у тебя "один запрос"?

V>Можно цитату?
Вы же сами приводите пример с prepared statement с фиксированным списком аргументов


V>И что помешало тебе самостоятельно пройти дисциплину "теория кодирования информации"?

V>Там несложно.
Встречный вопрос: что помешало почитать хоть что-то про устройство СУБД? Ну, то есть помимо понимания, какой именно SQL написать для получения результата, разобраться, как именно этот SQL исполняется?


V>Как ты при проектировании борешься с избыточностью, не понимая самого этого понятия?

V>Блин, 2^N запросов ))
А как вы при проектировании боретесь со стоимостью исполнения, с O(N) и O(N*M) вместо O(log(N))?

V>А смысл, если для получения одного значения int одного поля будут десятки виртуальных вызовов и пара промежуточных копирований?

Повторюсь: для получения одного значения int одного поля при корректной реализации в дотнете будет сделан один виртуальный вызов без промежуточных копирований.
А если реализовать интерфейс в struct типе, то можно избавиться и от вызовов совсем. То есть при перекладывании данных из провайдера в JSON будет напрямую StringBuilder.Append(и вот тут заинлайненный код обрашения к _dataBufferPtr + currentRecordOffset + _fieldOffset).


V>Если в джавовских программах доходит до эмуляции value-типов массивами байт, то можешь примерно представить степень вылизанности остального.

Отлично представляю. Те программы, которые написаны без лобзика по вазелину, работают из рук вон плохо. За время стартапа типичной джава программы у меня успевают прогнаться тесты дотнет-библиотеки.
Джаву более-менее спасает могучий джит — если ему удаётся заинлайнить вызываемый код, то результирующий бинарь вполне приличен. Если нет — то адские тормоза, честная интерпретация, и прочее.

V>Хотя, всё-равно нубство, их LMAX Disruptor сосёт рядом с нашими решениями.

А то.

V>Я хорошо в курсе, что в дотнете так вылизывать пока мест не принято, к счастью.

V>И даже высмеивается.
V>Но чем громче высмеивается, тем нам лучше.
Понятия не имею, кем и что высмеивается. В дотнете вылизывать очень долго было нельзя. Сейчас появилась возможность; и люди ей пользуются.

V>Много тыщ в год за лицензию на каждую либу.

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