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

Сообщение Re[45]: MS забило на дотнет. Питону - да, сишарпу - нет? от 02.09.2021 17:09

Изменено 02.09.2021 17:15 vdimas

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>Воображаем примерно такую штуку:

Бесполезно, внутри данные сначала зачитываются в массив object[], т.е. боксируются (первое копирование, причём, затратное):
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Data.Common/src/System/Data/Common/DataRecordInternal.cs

А потом ты их читаешь через unboxing
https://github.com/dotnet/runtime/blob/6f68bbd78f0ba47baea7b9887f383b95b2ab3a0e/src/libraries/System.Data.Common/src/System/Data/Common/DataRecordInternal.cs#L260:
(второе копирование, еще более затратное)

Причём, независимо от технологии 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.

Любая локальная база предоставляет нейтивные свои драйвера/библиотеки доступа, которые самые эффективные.
Хотя, сервера тоже предоставляют нейтивные драйвера/библиотеки, через которую можно получить максимальные плюшки:
https://docs.microsoft.com/en-us/sql/relational-databases/native-client/features/sql-server-native-client-features?view=sql-server-ver15

Я же уже писал выше — границы абстракций DAL при таких раскладах выгодней делать чуть выше, т.е. не по OLEDB/ODBC/ADO и провайдерам/адаптерам диалектов SQL, а прямо по всему DAL.
Это отличается от принятой в дотнете практики.

С другой стороны, шаблонный код позволяет выглядеть "этому" так, будто границы абстракций проходят заметно ниже, чем есть в реальности.
Т.е., в любом случае трудоёмкость сравнимая, т.к. сравнимая степень переиспользования кода.
Просто приличная часть абстракций перетекает из runtime в compile-time, как оно принято в плюсах.


V>>А если общаются с сервером, то чаще на ответной стороне сервер приложений со своим кешированием, который отдаёт ответ на предопределённые запросы через предопределённые же типизированные наборы данных. И тоже задержки малость не те, к которым привыкли в дотнете.

S>Сервер приложений там на чём написан? На дотнете?

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


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

S>Эта технология строго ортогональнаа применяемой платформе.

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


S>Client-side join и кэширование можно строить хоть на дотнете, хоть на плюсах, хоть на жаве. Там скорее вопросы возникают к архитектуре в крупном масштабе — типа "а как мы этот кэш инвалидируем".


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

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

На принятые в дотнете мейнстримовые практики мне приходится смотреть не просто так, а с целью хорошо в них ориентироваться.
Потому что невозможно формулировать так: "нам нужна быстрая программа!", нужно уметь ответить на вопрос "насколько быстрая?"
Т.е., с чем будем сравнивать?
Где параметры, по достижении которых задачу можно будет считать решенной (хотя бы на очередной итерации)?
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>Воображаем примерно такую штуку:

Бесполезно, внутри данные сначала зачитываются в массив object[], т.е. боксируются (первое копирование, причём, затратное):
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Data.Common/src/System/Data/Common/DataRecordInternal.cs

А потом ты их читаешь через unboxing
https://github.com/dotnet/runtime/blob/6f68bbd78f0ba47baea7b9887f383b95b2ab3a0e/src/libraries/System.Data.Common/src/System/Data/Common/DataRecordInternal.cs#L260:
(второе копирование, еще более затратное)

Причём, независимо от технологии 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.

Любая локальная база предоставляет нейтивные свои драйвера/библиотеки доступа, которые самые эффективные.
Хотя, сервера тоже предоставляют нейтивные драйвера/библиотеки, через которую можно получить максимальные плюшки:
https://docs.microsoft.com/en-us/sql/relational-databases/native-client/features/sql-server-native-client-features?view=sql-server-ver15

Я же уже писал выше — границы абстракций DAL при таких раскладах выгодней делать чуть выше, т.е. не по OLEDB/ODBC/ADO и провайдерам/адаптерам диалектов SQL, а прямо по всему DAL.
Это отличается от принятой в дотнете практики.

С другой стороны, шаблонный код позволяет выглядеть "этому" так, будто границы абстракций проходят заметно ниже, чем есть в реальности.
Т.е., в любом случае трудоёмкость сравнимая, т.к. сравнимая степень переиспользования кода.
Просто приличная часть абстракций перетекает из runtime в compile-time, как оно принято в плюсах.


V>>А если общаются с сервером, то чаще на ответной стороне сервер приложений со своим кешированием, который отдаёт ответ на предопределённые запросы через предопределённые же типизированные наборы данных. И тоже задержки малость не те, к которым привыкли в дотнете.

S>Сервер приложений там на чём написан? На дотнете?

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


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

S>Эта технология строго ортогональнаа применяемой платформе.

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


S>Client-side join и кэширование можно строить хоть на дотнете, хоть на плюсах, хоть на жаве. Там скорее вопросы возникают к архитектуре в крупном масштабе — типа "а как мы этот кэш инвалидируем".


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

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

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