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

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

Изменено 03.09.2021 18:08 vdimas

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

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

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

Так я попросил показать, что это за код, где живет, который
GetFieldType(i) == typeof(int)
      ? MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, int>(_rawData.Slice(GetFieldOffset(i)));
      : Convert.ToInt32(GetValue(i));


И да, если читать из поля Int16, например, Int32, то опять пляшем через боксинг, верно?


V>>https://referencesource.microsoft.com/#system.data/fx/src/data/Microsoft/SqlServer/Server/MemoryRecordBuffer.cs,17

V>>Путь данных еще более извилист и всё-равно данные еще дважды копируются.
S>Где они там дважды копируются? Там же лежит union.

Ну так из приемного буфера (который уже managed) — в MemoryRecordBuffer, а оттуда в датасет или в поля объектов какого-нить ORM.


V>>И в любом случае твой пример только для MSSQL, а для любых других баз на основе OLEDB или ODBC будет как я дал ссылку на исходники дотнета.

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

И все пишут в своих проектах собственные дрова к БД?
Или пользуются имеющимся в дотнете?

Куда я тебе показал в первый раз — это реализация ODBC драйвера.
Все базы имеют ODBC-дрова, но далеко не все имеют OLEDB.
Например, нет OLEDB драйвера к самой популярной в вебе базе MySQL.
Следующая по популярности идёт PostgreSQL, к ней тоже живые/актуальные только ODBC-дрова.


S>Вы же почему-то пишете про то, как в нативных приложениях мы используем нативные оптимизированные драйвера БД; а в дотнете вы хотите использовать максимально абстрактный код. Давайте тогда я в нативе буду использовать COM через IDispatch и конвертировать все данные через Variant.


Я говорил как оно есть по-факту, а ты рассуждаешь как оно могло бы быть, если бы все в мире действовали самым разумным способом.

"В нейтиве оптимизированные" — это ты только что придумал.
Дрова там обычные, бо парсить разметку любого протокола много ума не надо.
Оптимизированным получается сам сценарий зачитки данных, потому что максимально непосредственный.

Кстате, посмотрел OLEDB-реализацию в последних исходниках дотнета, там, действительно, читают через OLEDB акцессоры.
Но только странная ситуация выходит — под MSSQL свой кривой драйвер, под Oracle свой, популярные остальные базы доступны через ODBC.
Для кого остаётся OLEDB?
Для MS Access! ))

И еще меня крайне смущает объём велосипедостроения в каждом таком драйвере.
Там должно было быть до 80% общего кода, если не больше.


V>>linq2db — это самая вершинка айберга, пока данные до него дойдут — сто раз вспотеют.

S>Данные в него напрямую попадают из IDataRecord

Ну вот я тебе ссылку дал на кишки драйвера к MSSQL.
Это я уже молчу о том, что половину типов по той ссылке можно было сделать value-type.
По-факту там ад и ужас, нагрузка на GC на ровном месте.
Классическое "сделано на отгребись".


S>поверх которого генерируется специфический для возвращаемого типа код.


Не особо специфический — просто набор сгенерённых акцессоров к полям объекта (это "статические" объекты, т.е. для одного целевого entity создаются лишь однажды для каждого его поля), замапленный на колонки конкретного рекордсета. Если маппер достаточно умный, то при надобности вставит свои конвертеры, например где надо из Int16 в Int32, чтобы чтение уже скопированных и закешированных данных обходилось без боксинга/анбоксинга, как оно есть сейчас.

Т.е., в общем случае 3 вложенных виртуальных ф-ии на одно чтение уже закешированных данных.
А нет, это еще не чтение, это всё еще продолжается заполнение "модели" данными.
Чтение клиентским кодом будет когда-нибудь потом.

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

Сравнить с нейтивом, в метаинформации маппинга которого три указателя — смещение источника, смещение приёмника и адрес ф-ии преобразования (в простейшем случае — адрес простого копировщика на известное кол-во байт).

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

Да, в последней версии OLEDB "враппера" увидел похожу схему, с учётом испорченности дотнета — тоже примерно 4 вирутальных вызова на чтение одного поля.
Хотя, еще 2-3 года назад смотрел — был такой же мрак, как и в других провайдерах.


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


Разумеется, можем.
Если бы принципиально не могли, не было бы обсуждения этой темы.


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

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

Это для динамических противопоказано, а для снапшотных до фени.

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


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

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

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


S>Так и есть. Для обобщённой реализации — см. IDataRecord.


Я и смотрел, и ссылки тебе давал.


S>Для специфичной — делаем враппер, который достаёт данные согласно разметке.


Не так быстро.
Не "враппер", а полноценный драйвер.
Например, полностью своя подмена драйвера ODBC.

То бишь, самим реализовать всё семейство с 0-ля: IDbConnection, IDbTransaction, IDbCommand и еще пару десятков (если не больше) сущностей из модели ADO.Net.


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


Я тебя уже 3-й раз прошу дать координаты этого "нового стиля".
Исходники же дотнета открыты, какие проблемы?

Вот тебе их ODBC "враппер":
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Data.Odbc/src/System/Data/Odbc/DbDataRecord.cs#L43
На Линухах у разработчика будет задействован он с вероятностью 99%.

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

OLEDB, смотрю, действительно пару лет назад причесали.
Жаль, в линухах его нет.


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

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

Зачем "скопировать"?
StreamReader поверх сокета был всегда.
А потом еще и асинхронный давно.


V>>Таких отображений в этом "первом приложении" (которое магазин какой-нить) аж одно на каждую группу товаров.

S>Что такое "аж одно"? У тебя на N колонок 2N вариантов order by

Не, сортировка в магазинах по ограниченному кол-ву критериев: по цене, популярности, рейтингу.
И почти никогда нет последовательной сортировки, т.е. сортировка практически во всех популярных (проходных) онлайн-магазинах по одному из предоставленных критериев в одну из сторон, на этом с сортировкой всё.


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

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

ЧТД.
Почему база передаёт NULL-поля в виде битового вектора, и почему я не могу поступать так же?


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

S> Ну вот и я о том же. Будет выполнен тормозной запрос с большим количеством join-ов

Без джоинов.
Онлайн-магазины — это почти всегда розница.
Там карточный учёт, а не средневзвешенный.
Т.е. карточка целиком содержит информацию о товаре.

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


S>а потом половина вытащенных данных будет дропнута перед показом.


Я бы хотел посмотреть на живой пример, где это так.


S>И вы ещё спрашиваете меня, почему я считаю белых неэффективными.


Я считаю, что ты надумываешь условия под ответ.
Предлагаю поступать наоборот.


V>>Всего 4 запроса.

V>>Или два — 0 0 и 1 1, т.е., если есть хоть один бит — формируем маску целиком.
S>Отличная идея, коллега. Вы только что заставили движок вашей базы всегда делать table scan.

Если бы.

Когда партиции булевские, то на десятках-сотнях этих индексов от булевых полей ничего не выигрывается.
Индексам для пользы от них требуется хоть какая-то "ширина" и самих индексов желательно немного.
Т.е., если простая сумма всех уникальных значений всех индексов сравнимо с кол-вом данных — выигрыша от индекса не будет.
(не считая partition key, а он наверняка будет суррогатный ID карточки какой-нить, т.е. не играет рояли в рассматриваемых запросах).

Тут наоборот, все биты надо собрать в одно-два поля и по ним сделать индекс.
И тогда будет столько прыжков по индексу, сколько значащих бит в маске.


S>Потому, что другого способа сделать where (flags & @flags) > 0 не существует.


1. Только надо на не равно нулю проверять.
2. Ставлю на то, что ты заблуждаешься.
3. Десятки-сотни индексов по булевым или совсем "узким" данным всё-равно ничего не дадут в плане эффективности.

Можно поэкспериментировать с планами и посмотреть.


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


И опять эта демагогия.
Во-первых, предложенный тобой способ всё-равно медленне/затратнее.
Во-вторых, вытесняет кеш скомпиллированных запросов из базы, т.к. каждая из 2^N комбинация уникальная.
В-третьих, онлайн-базы базы на жёстких дисках уже много лет как не живут, а обращения к флеш-накопителю, да еще всегда через рейд — это не сильно медленее, чем из оперативы.

И да, кол-во товаров всегда конечное.
"Бесконечные" движения по ним, но в движениях уже никаких 2^N комбинаций изобретать не надо.
Как делать "универсальный движитель" по складу я тебе как-то показывал.


S>Ну, и пока у вас набор всех значений всех атрибутов не очень велик — что позволяет уложить их в битики.


Не у меня, а у всех их немного.
Я тебе дал ссылку на самый продвинутый магазин именно в плане фильтрации-поиска товаров.
На wildberries заходят не за чем-то конкретным, а "просто посмотреть, что в мире эдакого есть", он заточен именно под такой сценарий.
Т.е. вряд ли ты найдёшь магазин с большим кол-вом управляемых признаков выборки.

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


V>>Показал.

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

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


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

V>>Судя по интонации подаваемого тобой — "просто нравится".
S>Быстрее получается тогда, когда мы не пишем внутри нашей хранимки код типа

Не-не-не, способ общения с базой ортогонален способу формирования условий на клиенте.
Клиент всё-равно пошлёт по своему внутреннему протоколу серверу приложений всё эти флаги-ограничения для поиска товаров, а как сервак эти флаги преобразует в запрос — про это клиент и знать не будет.
(например, речь об андроидном приложении вайлдберриз)


S>
S>... inner join customer c on c.id = o.customerID
S>where (c.Name like '%'&@customerName&'%' or (@customerName is null))
S>


Я имел ввиду, что если запрос формируется не через Linq, а непосредственной конкатенацией строк, почему это должно работать медленнее?


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

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

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

А я тут пока упражняюсь в дисциплине задействования хранимки для вывода конкретно экрана обзора/выбора товаров, потому что это самый что ни на есть жирный пример против хранимки.
Это ж вызов! ))
Вот и упражняюсь.
На других экранах: корзина, доставка/трекинг, история покупок, платежей, общений с персоналом и т.д. и т.п. всё проще, нифига не вызов.

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


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

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

Хреновый драйвер, значит, или специально для тебя в текст отрендерили, чтобы тебе было понятней.
ODBC и OLEDB умеют передавать именно команды на вызовы процедур с аргументами по протоколу.
Да еще с такими, которые в текст не так-то просто перевести, например, binary.


S>И экономия достигается не за счёт константности, а за счёт того, что используется готовый план запроса из кэша.


Вооот.
Но там на всём экономия.
И на сервере сразу понятно, что это вызов хранимки, и на клиенте формировать команду проще.


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

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

Я все-равно не понял, почему у меня не может быть несколько хранимок?


S>Встречный вопрос: что помешало почитать хоть что-то про устройство СУБД?


Да, так что помешало?


S>Ну, то есть помимо понимания, какой именно SQL написать для получения результата, разобраться, как именно этот SQL исполняется?


Ага, особенно когда у нас значения комбинируются по OR по десяткам индексов.
Ты эта, поиграй с планами на досуге.


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

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

В случае OR по M признакам будет O(log(N)*M)
При том что N нифига к бесконечности не стремится, т.к. у нас в работе всегда некая одна группа товаров.

Вот так я и борюсь, не теоретизируя о жестких дисках и бесконечном N, а проверяя/сравнивая варианты организации данных.
Ты планы-то покрути.


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

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

Примерно три в лучшем случае.


S>А если реализовать интерфейс в struct типе, то можно избавиться и от вызовов совсем.


И где можно на такую реализацию посмотреть?


S>То есть при перекладывании данных из провайдера в JSON будет напрямую StringBuilder.Append(и вот тут заинлайненный код обрашения к _dataBufferPtr + currentRecordOffset + _fieldOffset).


Да я ж не против, я ж сам всякие трюки парой сообщений выше показывал.
Я про текущее состояние отрасли спрашиваю.

Про то, откуда взялось "если обращаемся к базе, то экономить на чём бы то ни было бессмысленно".
И ведь верное утверждение для текущего состояния.


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

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

Возможно.
Но при разогреве такие вылизанные сервера пока мест уделывают дотнетные.
Кроме наших, конечно. ))

Нам сейчас мешает то, что на серверной стороне дотнету пока доверия нет в плане надёжности.
И это не от нас зависит.
Зато клиентские либы раскупаются неплохо.
Т.е., .Net Core, таки, "выстрелил".


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


Ес-но. И код пишется под джит и тщательнейшим образом профилируется, благо в джаве репорты от джита подробные и вменяемые, а в дотнете джит — чёрный ящик.
И я хорошо понимаю — почему.
Стоит отлить из бетона какой-нить публичный АПИ к джиту, и эту гирю на ногах так просто потом не сбросишь.
В общем, на системном уровне дотнет пока мест находится в стадии поиска себя.


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

V>>И даже высмеивается.
V>>Но чем громче высмеивается, тем нам лучше.
S>Понятия не имею, кем и что высмеивается.

Да хотя бы на этом форуме.
Не приветствуется вылизывание, подвергается остракизму.


S>В дотнете вылизывать очень долго было нельзя. Сейчас появилась возможность; и люди ей пользуются.


Да можно было.
Реинтерпретация памяти и до Span-времён работала неплохо.
Просто теперь к ней подключили реинтерпретацию управляемой памяти.
Раньше в этих сценариях тупо пинили.
Но всё-равно в дотнете всегда всё пинится.
Создаётся копия строки (именно копия), или строка копируется в буфер — пинятся оба конца.
Преобразуется из UTF16 в UTF8 — тоже пинятся источник и приёмник.
Под капотом там обычный unsafe и указатели.
Именно поэтому теперь моя либа по конверсии строк стала не нужна. ))


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

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

Не уверен, что стоит публично это делать, в той области know how — понятие очень нервное.
Если прям сильно интересно — стукни в личку, дам под честное слово.
Re[49]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:

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

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

Так я попросил показать, что это за код, где живет, который
GetFieldType(i) == typeof(int)
      ? MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, int>(_rawData.Slice(GetFieldOffset(i)));
      : Convert.ToInt32(GetValue(i));


И да, если читать из поля Int16, например, Int32, то опять пляшем через боксинг, верно?


V>>https://referencesource.microsoft.com/#system.data/fx/src/data/Microsoft/SqlServer/Server/MemoryRecordBuffer.cs,17

V>>Путь данных еще более извилист и всё-равно данные еще дважды копируются.
S>Где они там дважды копируются? Там же лежит union.

Ну так из приемного буфера (который уже managed) — в MemoryRecordBuffer, а оттуда в датасет или в поля объектов какого-нить ORM.


V>>И в любом случае твой пример только для MSSQL, а для любых других баз на основе OLEDB или ODBC будет как я дал ссылку на исходники дотнета.

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

И все пишут в своих проектах собственные дрова к БД?
Или пользуются имеющимся в дотнете?

Куда я тебе показал в первый раз — это реализация ODBC драйвера.
Все базы имеют ODBC-дрова, но далеко не все имеют OLEDB.
Например, нет OLEDB драйвера к самой популярной в вебе базе MySQL.
Следующая по популярности идёт PostgreSQL, к ней тоже живые/актуальные только ODBC-дрова.


S>Вы же почему-то пишете про то, как в нативных приложениях мы используем нативные оптимизированные драйвера БД; а в дотнете вы хотите использовать максимально абстрактный код. Давайте тогда я в нативе буду использовать COM через IDispatch и конвертировать все данные через Variant.


Я говорил как оно есть по-факту, а ты рассуждаешь как оно могло бы быть, если бы все в мире действовали самым разумным способом.

"В нейтиве оптимизированные" — это ты только что придумал.
Дрова там обычные, бо парсить разметку любого протокола много ума не надо.
Оптимизированным получается сам сценарий зачитки данных, потому что максимально непосредственный.

Кстате, посмотрел OLEDB-реализацию в последних исходниках дотнета, там, действительно, читают через OLEDB акцессоры.
Но только странная ситуация выходит — под MSSQL свой кривой драйвер, под Oracle свой, популярные остальные базы доступны через ODBC.
Для кого остаётся OLEDB?
Для MS Access! ))

И еще меня крайне смущает объём велосипедостроения в каждом таком драйвере.
Там должно было быть до 80% общего кода, если не больше.


V>>linq2db — это самая вершинка айберга, пока данные до него дойдут — сто раз вспотеют.

S>Данные в него напрямую попадают из IDataRecord

Ну вот я тебе ссылку дал на кишки драйвера к MSSQL.
Это я уже молчу о том, что половину типов по той ссылке можно было сделать value-type.
По-факту там ад и ужас, нагрузка на GC на ровном месте.
Классическое "сделано на отгребись".


S>поверх которого генерируется специфический для возвращаемого типа код.


Не особо специфический — просто набор сгенерённых акцессоров к полям объекта (это "статические" объекты, т.е. для одного целевого entity создаются лишь однажды для каждого его поля), замапленный на колонки конкретного рекордсета. Если маппер достаточно умный, то при надобности вставит свои конвертеры, например где надо из Int16 в Int32, чтобы чтение уже скопированных и закешированных данных обходилось без боксинга/анбоксинга, как оно есть сейчас.

Т.е., в общем случае 3 вложенных виртуальных ф-ии на одно чтение уже закешированных данных.
А нет, это еще не чтение, это всё еще продолжается заполнение "модели" данными.
Чтение клиентским кодом будет когда-нибудь потом.

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

Сравнить с нейтивом, в метаинформации маппинга которого три указателя — смещение источника, смещение приёмника и адрес ф-ии преобразования (в простейшем случае — адрес простого копировщика на известное кол-во байт).

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

Да, в последней версии OLEDB "враппера" увидел похожу схему, с учётом испорченности дотнета — тоже примерно 4 вирутальных вызова на чтение одного поля.
Хотя, еще 2-3 года назад смотрел — был такой же мрак, как и в других провайдерах.


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


Разумеется, можем.
Если бы принципиально не могли, не было бы обсуждения этой темы.


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

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

Это для динамических противопоказано, а для снапшотных до фени.

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


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

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

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


S>Так и есть. Для обобщённой реализации — см. IDataRecord.


Я и смотрел, и ссылки тебе давал.


S>Для специфичной — делаем враппер, который достаёт данные согласно разметке.


Не так быстро.
Не "враппер", а полноценный драйвер.
Например, полностью своя подмена драйвера ODBC.

То бишь, самим реализовать всё семейство с 0-ля: IDbConnection, IDbTransaction, IDbCommand и еще пару десятков (если не больше) сущностей из модели ADO.Net.


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


Я тебя уже 3-й раз прошу дать координаты этого "нового стиля".
Исходники же дотнета открыты, какие проблемы?

Вот тебе их ODBC "враппер":
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Data.Odbc/src/System/Data/Odbc/DbDataRecord.cs#L43
На Линухах у разработчика будет задействован он с вероятностью 99%.

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

OLEDB, смотрю, действительно пару лет назад причесали.
Жаль, в линухах его нет.


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

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

Зачем "скопировать"?
StreamReader поверх сокета был всегда.
А потом еще и асинхронный давно.


V>>Таких отображений в этом "первом приложении" (которое магазин какой-нить) аж одно на каждую группу товаров.

S>Что такое "аж одно"? У тебя на N колонок 2N вариантов order by

Не, сортировка в магазинах по ограниченному кол-ву критериев: по цене, популярности, рейтингу.
И почти никогда нет последовательной сортировки, т.е. сортировка практически во всех популярных (проходных) онлайн-магазинах по одному из предоставленных критериев в одну из сторон, на этом с сортировкой всё.


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

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

ЧТД.
Почему база передаёт NULL-поля в виде битового вектора, и почему я не могу поступать так же?


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

S> Ну вот и я о том же. Будет выполнен тормозной запрос с большим количеством join-ов

Без джоинов.
Онлайн-магазины — это почти всегда розница.
Там карточный учёт, а не средневзвешенный.
Т.е. карточка целиком содержит информацию о товаре.

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


S>а потом половина вытащенных данных будет дропнута перед показом.


Я бы хотел посмотреть на живой пример, где это так.


S>И вы ещё спрашиваете меня, почему я считаю белых неэффективными.


Я считаю, что ты надумываешь условия под ответ.
Предлагаю поступать наоборот.


V>>Всего 4 запроса.

V>>Или два — 0 0 и 1 1, т.е., если есть хоть один бит — формируем маску целиком.
S>Отличная идея, коллега. Вы только что заставили движок вашей базы всегда делать table scan.

Если бы.

Когда партиции булевские, то на десятках-сотнях этих индексов от булевых полей ничего не выигрывается.
Индексам для пользы от них требуется хоть какая-то "ширина" и самих индексов желательно немного.
Т.е., если простая сумма всех уникальных значений всех индексов сравнимо с кол-вом данных — выигрыша от индекса не будет.
(не считая partition key, а он наверняка будет суррогатный ID карточки какой-нить, т.е. не играет рояли в рассматриваемых запросах).

Тут наоборот, все биты надо собрать в одно-два поля и по ним сделать индекс.
И тогда будет столько прыжков по индексу, сколько значащих бит в маске.


S>Потому, что другого способа сделать where (flags & @flags) > 0 не существует.


1. Только надо на не равно нулю проверять.
2. Ставлю на то, что ты заблуждаешься.
3. Десятки-сотни индексов по булевым или совсем "узким" данным всё-равно ничего не дадут в плане эффективности.

Можно поэкспериментировать с планами и посмотреть.


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


И опять эта демагогия.
Во-первых, предложенный тобой способ всё-равно медленне/затратнее.
Во-вторых, вытесняет кеш скомпиллированных запросов из базы, т.к. каждая из 2^N комбинация уникальная.
В-третьих, онлайн-базы базы на жёстких дисках уже много лет как не живут, а обращения к флеш-накопителю, да еще всегда через рейд — это не сильно медленее, чем из оперативы.

И да, кол-во товаров всегда конечное.
"Бесконечные" движения по ним, но в движениях уже никаких 2^N комбинаций изобретать не надо.
Как делать "универсальный движитель" по складу я тебе как-то показывал.


S>Ну, и пока у вас набор всех значений всех атрибутов не очень велик — что позволяет уложить их в битики.


Не у меня, а у всех их немного.
Я тебе дал ссылку на самый продвинутый магазин именно в плане фильтрации-поиска товаров.
На wildberries заходят не за чем-то конкретным, а "просто посмотреть, что в мире эдакого есть", он заточен именно под такой сценарий.
Т.е. вряд ли ты найдёшь магазин с большим кол-вом управляемых признаков выборки.

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


V>>Показал.

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

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


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

V>>Судя по интонации подаваемого тобой — "просто нравится".
S>Быстрее получается тогда, когда мы не пишем внутри нашей хранимки код типа

Не-не-не, способ общения с базой ортогонален способу формирования условий на клиенте.
Клиент всё-равно пошлёт по своему внутреннему протоколу серверу приложений всё эти флаги-ограничения для поиска товаров, а как сервак эти флаги преобразует в запрос — про это клиент и знать не будет.
(например, речь об андроидном приложении вайлдберриз)


S>
S>... inner join customer c on c.id = o.customerID
S>where (c.Name like '%'&@customerName&'%' or (@customerName is null))
S>


Я имел ввиду, что если запрос формируется не через Linq, а непосредственной конкатенацией строк, почему это должно работать медленнее?


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

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

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

А я тут пока упражняюсь в дисциплине задействования хранимки для вывода конкретно экрана обзора/выбора товаров, потому что это самый что ни на есть жирный пример против хранимки.
Это ж вызов! ))
Вот и упражняюсь.
На других экранах: корзина, доставка/трекинг, история покупок, платежей, общений с персоналом и т.д. и т.п. всё проще, нифига не вызов.

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


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

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

Хреновый драйвер, значит, или специально для тебя в текст отрендерили, чтобы тебе было понятней.
ODBC и OLEDB умеют передавать именно команды на вызовы процедур с аргументами по своему протоколу.
Да еще с такими, которые в текст не так-то просто перевести, например, binary.


S>И экономия достигается не за счёт константности, а за счёт того, что используется готовый план запроса из кэша.


Вооот.
Но там на всём экономия.
И на сервере сразу понятно, что это вызов хранимки, и на клиенте формировать команду проще.


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

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

Я все-равно не понял, почему у меня не может быть несколько хранимок?


S>Встречный вопрос: что помешало почитать хоть что-то про устройство СУБД?


Да, так что помешало?


S>Ну, то есть помимо понимания, какой именно SQL написать для получения результата, разобраться, как именно этот SQL исполняется?


Ага, особенно когда у нас значения комбинируются по OR по десяткам индексов.
Ты эта, поиграй с планами на досуге.


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

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

В случае OR по M признакам будет O(log(N)*M)
При том что N нифига к бесконечности не стремится, т.к. у нас в работе всегда некая одна группа товаров.

Вот так я и борюсь, не теоретизируя о жестких дисках и бесконечном N, а проверяя/сравнивая варианты организации данных.
Ты планы-то покрути.


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

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

Примерно три в лучшем случае.


S>А если реализовать интерфейс в struct типе, то можно избавиться и от вызовов совсем.


И где можно на такую реализацию посмотреть?


S>То есть при перекладывании данных из провайдера в JSON будет напрямую StringBuilder.Append(и вот тут заинлайненный код обрашения к _dataBufferPtr + currentRecordOffset + _fieldOffset).


Да я ж не против, я ж сам всякие трюки парой сообщений выше показывал.
Я про текущее состояние отрасли спрашиваю.

Про то, откуда взялось "если обращаемся к базе, то экономить на чём бы то ни было бессмысленно".
И ведь верное утверждение для текущего состояния.


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

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

Возможно.
Но при разогреве такие вылизанные сервера пока мест уделывают дотнетные.
Кроме наших, конечно. ))

Нам сейчас мешает то, что на серверной стороне дотнету пока доверия нет в плане надёжности.
И это не от нас зависит.
Зато клиентские либы раскупаются неплохо.
Т.е., .Net Core, таки, "выстрелил".


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


Ес-но. И код пишется под джит и тщательнейшим образом профилируется, благо в джаве репорты от джита подробные и вменяемые, а в дотнете джит — чёрный ящик.
И я хорошо понимаю — почему.
Стоит отлить из бетона какой-нить публичный АПИ к джиту, и эту гирю на ногах так просто потом не сбросишь.
В общем, на системном уровне дотнет пока мест находится в стадии поиска себя.


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

V>>И даже высмеивается.
V>>Но чем громче высмеивается, тем нам лучше.
S>Понятия не имею, кем и что высмеивается.

Да хотя бы на этом форуме.
Не приветствуется вылизывание, подвергается остракизму.


S>В дотнете вылизывать очень долго было нельзя. Сейчас появилась возможность; и люди ей пользуются.


Да можно было.
Реинтерпретация памяти и до Span-времён работала неплохо.
Просто теперь к ней подключили реинтерпретацию управляемой памяти.
Раньше в этих сценариях тупо пинили.
Но всё-равно в дотнете всегда всё пинится.
Создаётся копия строки (именно копия), или строка копируется в буфер — пинятся оба конца.
Преобразуется из UTF16 в UTF8 — тоже пинятся источник и приёмник.
Под капотом там обычный unsafe и указатели.
Именно поэтому теперь моя либа по конверсии строк стала не нужна. ))


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

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

Не уверен, что стоит публично это делать, в той области know how — понятие очень нервное.
Если прям сильно интересно — стукни в личку, дам под честное слово.