Сообщение Re[37]: MS забило на дотнет. Питону - да, сишарпу - нет? от 30.08.2021 13:00
Изменено 31.08.2021 19:13 vdimas
Re[37]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Еще раз, медленно — интероп в C# медленный.
V>>Требуется сокращать его до минимума.
S>Эмм, а чего там медленного?
Много предварительных телодвижений перед вызовом.
В дотнете вызов не несет с собой никакого контекста текущей VM, в отличии от интеропа в Node.js, где контекст подаётся прямым образом в аргументах.
В дотнете это позволяет вызывать нейтивные точки входа непосредственно в нейтивной сигнатуре, но требует сохранения контекста в стеке и способа разметки стека для возможности работы GC, даже если еще не было возврата из вызванной нейтивной процедуры и даже через всю черезполосицу вызовов, когда из нейтива вызывается опять дотнет и т.д.
И насколько я понимаю, нода и браузеры собирают мусор только после возврата из колбэков, т.е. когда JS-код не работает, то бишь, у них нет JS-стека и нет проблем с нейтивными вызовами.
S>Мне чисто так — в абстрактном смысле интересно. Я всегда полагал, что интероп в дотнете один из самых лучших на рынке. Особенно с версии 5.
В новых версиях языка добавилось много unsafe-фич, но unsafe и interop друг другу немного перпендикулярны.
Или имелось ввиду появление указателей на ф-ии?
Первым делом измерил, там в точности то же быстродействие получилось, что и через [DllImport("Lib")].
Т.е., вызов ф-ии по managed указателю мгновенный, а по unamanged — медленный, разница примерно в 17 раз.
Тут можно пробежаться глазами, что происходит при вызове нейтивной ф-ии через указатель:
https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/coreclr/vm/amd64/PInvokeStubs.asm#L29
Здесь немного результатов бенчмарков из списка:
http://www.rsdn.org/forum/flame.comp/8063452.1
Ключевое — BaseLine надо вычитать из других результатов, а не смотреть отношение к нему.
Подкорректированная сводка стоимости вызовов в попугаях при использовании:
Managed func ptr — 35;
делегат — 128;
интерфейс — 172;
DllImport/GetProcAddr — 586;
Обратный вызов через GetFunctionPointerForDelegate — 1180;
Указатель на управляемую ф-ию, преобразованный к неуправляемому и вызванный затем как вызов неуправляемой ф-ии — 7000-8000.
Итого, новый указатель на ф-ию для управляемого кода — киллер фича.
Быстрый, легковесный (в отличие от происходящего при создании экземпляра делегата), и самое главное — позволяет отвязаться от типа делегата, т.к. теперь функциональный тип определяется только сигнатурой.
(это о чём я говорил еще с первой версии дотнета, что вот здесь у них коровья лепёшка для разрабов)
И да, сейчас получить указатель непосредственным образом можно только на статические методы.
Ниже в сырцах можно подсмотреть, как получить указатель на экземплярный метод и как вызывать затем.
Например, экземплярный делегат для каждого экземпляра объекта надо создавать каждый раз новый.
А тут достаточно в некоей своей структуре сохранить пару — указатель на метод и ссылку на экземпляр.
В дотнете для борьбы с таким сценарием обычно создают статический прокси-метод.
(К сожалению, сей простой паттерн не задействовали для машинки async-метода, на каждый вызов создаётся унутре делегат — тяжелое наследение .Net Framework, проклятая индустрией индюшатина... )) )
S>И как вы ухитряетесь его сократить при помощи СompilerServices.Unsafe?
Часть нейтивной функциональности переношу в дотнет, избавляясь от интеропа.
СompilerServices.Unsafe позволяет получать управляемые ссылки из, грубо, void*, чего до него не было.
Или же приводить одну управляемую ссылку в другую, например, ссылку на byte на ссылку на SomeStruct.
Кароч, позволяет реинтерпретировать память по моему усмотрению.
V>>Я именно тебе приводил не так давно тесты вызова interop или ф-ий через unsafe-указатели — результаты катастрофические.
V>>В обоих случаях.
S>Что-то сходу не могу найти тех тестов.
Так я и не тебе приводил.
Ссылку дал выше.
S>И, главное, как нода-то ухитряется сделать интероп быстрее?
Никак.
"Ополовинить" имелось ввиду отрезать/снизить почти вдвое надобность в интеропе.
V>>Еще раз, медленно — интероп в C# медленный.
V>>Требуется сокращать его до минимума.
S>Эмм, а чего там медленного?
Много предварительных телодвижений перед вызовом.
В дотнете вызов не несет с собой никакого контекста текущей VM, в отличии от интеропа в Node.js, где контекст подаётся прямым образом в аргументах.
В дотнете это позволяет вызывать нейтивные точки входа непосредственно в нейтивной сигнатуре, но требует сохранения контекста в стеке и способа разметки стека для возможности работы GC, даже если еще не было возврата из вызванной нейтивной процедуры и даже через всю черезполосицу вызовов, когда из нейтива вызывается опять дотнет и т.д.
И насколько я понимаю, нода и браузеры собирают мусор только после возврата из колбэков, т.е. когда JS-код не работает, то бишь, у них нет JS-стека и нет проблем с нейтивными вызовами.
S>Мне чисто так — в абстрактном смысле интересно. Я всегда полагал, что интероп в дотнете один из самых лучших на рынке. Особенно с версии 5.
В новых версиях языка добавилось много unsafe-фич, но unsafe и interop друг другу немного перпендикулярны.
Или имелось ввиду появление указателей на ф-ии?
Первым делом измерил, там в точности то же быстродействие получилось, что и через [DllImport("Lib")].
Т.е., вызов ф-ии по managed указателю мгновенный, а по unamanged — медленный, разница примерно в 17 раз.
managedPtr(bar, ref tmp);
00007FFB821B7D5A mov rcx,qword ptr [rbp+10h]
00007FFB821B7D5E mov qword ptr [rbp-10h],rcx
00007FFB821B7D62 mov rcx,2215AF02C80h
00007FFB821B7D6C mov rcx,qword ptr [rcx]
00007FFB821B7D6F lea rdx,[rbp-8]
00007FFB821B7D73 mov rax,qword ptr [rbp-10h]
00007FFB821B7D77 call rax
unmanagedPtr(bar, ref tmp);
00007FFB821B7D79 mov rcx,qword ptr [rbp+18h]
00007FFB821B7D7D mov qword ptr [rbp-18h],rcx
00007FFB821B7D81 mov rcx,2215AF02C80h
00007FFB821B7D8B mov rcx,qword ptr [rcx]
00007FFB821B7D8E lea rdx,[rbp-8]
00007FFB821B7D92 mov r10,qword ptr [rbp-18h]
00007FFB821B7D96 mov r11,22163442FA0h
00007FFB821B7DA0 call GenericPInvokeCalliHelper (07FFBE1D0A8D0h)
Тут можно пробежаться глазами, что происходит при вызове нейтивной ф-ии через указатель:
https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/coreclr/vm/amd64/PInvokeStubs.asm#L29
Здесь немного результатов бенчмарков из списка:
http://www.rsdn.org/forum/flame.comp/8063452.1
Ключевое — BaseLine надо вычитать из других результатов, а не смотреть отношение к нему.
Подкорректированная сводка стоимости вызовов в попугаях при использовании:
Managed func ptr — 35;
делегат — 128;
интерфейс — 172;
DllImport/GetProcAddr — 586;
Обратный вызов через GetFunctionPointerForDelegate — 1180;
Указатель на управляемую ф-ию, преобразованный к неуправляемому и вызванный затем как вызов неуправляемой ф-ии — 7000-8000.
Итого, новый указатель на ф-ию для управляемого кода — киллер фича.
Быстрый, легковесный (в отличие от происходящего при создании экземпляра делегата), и самое главное — позволяет отвязаться от типа делегата, т.к. теперь функциональный тип определяется только сигнатурой.
(это о чём я говорил еще с первой версии дотнета, что вот здесь у них коровья лепёшка для разрабов)
И да, сейчас получить указатель непосредственным образом можно только на статические методы.
Ниже в сырцах можно подсмотреть, как получить указатель на экземплярный метод и как вызывать затем.
Например, экземплярный делегат для каждого экземпляра объекта надо создавать каждый раз новый.
А тут достаточно в некоей своей структуре сохранить пару — указатель на метод и ссылку на экземпляр.
В дотнете для борьбы с таким сценарием обычно создают статический прокси-метод.
(К сожалению, сей простой паттерн не задействовали для машинки async-метода, на каждый вызов создаётся унутре делегат — тяжелое наследение .Net Framework, проклятая индустрией индюшатина... )) )
Сырцы бенчмарка, каждый файл образует свой проект | |
| |
S>И как вы ухитряетесь его сократить при помощи СompilerServices.Unsafe?
Часть нейтивной функциональности переношу в дотнет, избавляясь от интеропа.
СompilerServices.Unsafe позволяет получать управляемые ссылки из, грубо, void*, чего до него не было.
Или же приводить одну управляемую ссылку в другую, например, ссылку на byte на ссылку на SomeStruct.
Кароч, позволяет реинтерпретировать память по моему усмотрению.
V>>Я именно тебе приводил не так давно тесты вызова interop или ф-ий через unsafe-указатели — результаты катастрофические.
V>>В обоих случаях.
S>Что-то сходу не могу найти тех тестов.
Так я и не тебе приводил.
Ссылку дал выше.
S>И, главное, как нода-то ухитряется сделать интероп быстрее?
Никак.
"Ополовинить" имелось ввиду отрезать/снизить почти вдвое надобность в интеропе.
Re[37]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:
V>>Еще раз, медленно — интероп в C# медленный.
V>>Требуется сокращать его до минимума.
S>Эмм, а чего там медленного?
Много предварительных телодвижений перед вызовом.
В дотнете вызов не несет с собой никакого контекста текущей VM, в отличии от интеропа в Node.js, где контекст подаётся прямым образом в аргументах.
В дотнете это позволяет вызывать нейтивные точки входа непосредственно в нейтивной сигнатуре, но требует сохранения контекста в стеке и способа разметки стека для возможности работы GC, даже если еще не было возврата из вызванной нейтивной процедуры и даже через всю черезполосицу вызовов, когда из нейтива вызывается опять дотнет и т.д.
И насколько я понимаю, нода и браузеры собирают мусор только после возврата из колбэков, т.е. когда JS-код не работает, то бишь, у них нет JS-стека и нет проблем с нейтивными вызовами.
S>Мне чисто так — в абстрактном смысле интересно. Я всегда полагал, что интероп в дотнете один из самых лучших на рынке. Особенно с версии 5.
В новых версиях языка добавилось много unsafe-фич, но unsafe и interop друг другу немного перпендикулярны.
Или имелось ввиду появление указателей на ф-ии?
Первым делом измерил, там в точности то же быстродействие получилось, что и через [DllImport("Lib")].
Т.е., вызов ф-ии по managed указателю мгновенный, а по unamanged — медленный, разница примерно в 17 раз.
Тут можно пробежаться глазами, что происходит при вызове нейтивной ф-ии через указатель:
https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/coreclr/vm/amd64/PInvokeStubs.asm#L29
Здесь немного результатов бенчмарков из списка:
http://www.rsdn.org/forum/flame.comp/8063452.1
Ключевое — BaseLine надо вычитать из других результатов, а не смотреть отношение к нему.
Подкорректированная сводка стоимости вызовов в попугаях при использовании:
Managed func ptr — 35;
делегат — 128;
интерфейс — 172;
DllImport/GetProcAddr — 586;
Обратный вызов через GetFunctionPointerForDelegate — 1180;
Указатель на управляемую ф-ию, преобразованный к неуправляемому и вызванный затем как вызов неуправляемой ф-ии — 7000-8000.
Итого, новый указатель на ф-ию для управляемого кода — киллер фича.
Быстрый, легковесный (в отличие от происходящего при создании экземпляра делегата), и самое главное — позволяет отвязаться от типа делегата, т.к. теперь функциональный тип определяется только сигнатурой.
(это о чём я говорил еще с первой версии дотнета, что вот здесь у них коровья лепёшка для разрабов)
И да, сейчас получить указатель непосредственным образом можно только на статические методы.
Ниже в сырцах можно подсмотреть, как получить указатель на экземплярный метод и как вызывать затем.
Например, экземплярный делегат для каждого экземпляра объекта надо создавать каждый раз новый.
А тут достаточно в некоей своей структуре сохранить пару — указатель на метод и ссылку на экземпляр.
В дотнете для борьбы с таким сценарием обычно создают статический прокси-метод.
(К сожалению, сей простой паттерн не задействовали для машинки async-метода, на каждый вызов создаётся унутре делегат — тяжелое наследение .Net Framework, проклятая индустрией индюшатина... )) )
S>И как вы ухитряетесь его сократить при помощи СompilerServices.Unsafe?
Часть нейтивной функциональности переношу в дотнет, избавляясь от интеропа.
СompilerServices.Unsafe позволяет получать управляемые ссылки из, грубо, void*, чего до него не было.
Или же приводить одну управляемую ссылку в другую, например, ссылку на byte на ссылку на SomeStruct.
Кароч, позволяет реинтерпретировать память по моему усмотрению.
V>>Я именно тебе приводил не так давно тесты вызова interop или ф-ий через unsafe-указатели — результаты катастрофические.
V>>В обоих случаях.
S>Что-то сходу не могу найти тех тестов.
Так я и не тебе приводил.
Ссылку дал выше.
S>И, главное, как нода-то ухитряется сделать интероп быстрее?
Ноде не надо заботиться о фреймах стека для GC, поэтому, вызывает нейтивные ф-ии напрямую.
"Ополовинить интероп" в C# имелось ввиду отрезать/снизить почти вдвое надобность в интеропе.
V>>Еще раз, медленно — интероп в C# медленный.
V>>Требуется сокращать его до минимума.
S>Эмм, а чего там медленного?
Много предварительных телодвижений перед вызовом.
В дотнете вызов не несет с собой никакого контекста текущей VM, в отличии от интеропа в Node.js, где контекст подаётся прямым образом в аргументах.
В дотнете это позволяет вызывать нейтивные точки входа непосредственно в нейтивной сигнатуре, но требует сохранения контекста в стеке и способа разметки стека для возможности работы GC, даже если еще не было возврата из вызванной нейтивной процедуры и даже через всю черезполосицу вызовов, когда из нейтива вызывается опять дотнет и т.д.
И насколько я понимаю, нода и браузеры собирают мусор только после возврата из колбэков, т.е. когда JS-код не работает, то бишь, у них нет JS-стека и нет проблем с нейтивными вызовами.
S>Мне чисто так — в абстрактном смысле интересно. Я всегда полагал, что интероп в дотнете один из самых лучших на рынке. Особенно с версии 5.
В новых версиях языка добавилось много unsafe-фич, но unsafe и interop друг другу немного перпендикулярны.
Или имелось ввиду появление указателей на ф-ии?
Первым делом измерил, там в точности то же быстродействие получилось, что и через [DllImport("Lib")].
Т.е., вызов ф-ии по managed указателю мгновенный, а по unamanged — медленный, разница примерно в 17 раз.
managedPtr(bar, ref tmp);
00007FFB821B7D5A mov rcx,qword ptr [rbp+10h]
00007FFB821B7D5E mov qword ptr [rbp-10h],rcx
00007FFB821B7D62 mov rcx,2215AF02C80h
00007FFB821B7D6C mov rcx,qword ptr [rcx]
00007FFB821B7D6F lea rdx,[rbp-8]
00007FFB821B7D73 mov rax,qword ptr [rbp-10h]
00007FFB821B7D77 call rax
unmanagedPtr(bar, ref tmp);
00007FFB821B7D79 mov rcx,qword ptr [rbp+18h]
00007FFB821B7D7D mov qword ptr [rbp-18h],rcx
00007FFB821B7D81 mov rcx,2215AF02C80h
00007FFB821B7D8B mov rcx,qword ptr [rcx]
00007FFB821B7D8E lea rdx,[rbp-8]
00007FFB821B7D92 mov r10,qword ptr [rbp-18h]
00007FFB821B7D96 mov r11,22163442FA0h
00007FFB821B7DA0 call GenericPInvokeCalliHelper (07FFBE1D0A8D0h)
Тут можно пробежаться глазами, что происходит при вызове нейтивной ф-ии через указатель:
https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/coreclr/vm/amd64/PInvokeStubs.asm#L29
Здесь немного результатов бенчмарков из списка:
http://www.rsdn.org/forum/flame.comp/8063452.1
Ключевое — BaseLine надо вычитать из других результатов, а не смотреть отношение к нему.
Подкорректированная сводка стоимости вызовов в попугаях при использовании:
Managed func ptr — 35;
делегат — 128;
интерфейс — 172;
DllImport/GetProcAddr — 586;
Обратный вызов через GetFunctionPointerForDelegate — 1180;
Указатель на управляемую ф-ию, преобразованный к неуправляемому и вызванный затем как вызов неуправляемой ф-ии — 7000-8000.
Итого, новый указатель на ф-ию для управляемого кода — киллер фича.
Быстрый, легковесный (в отличие от происходящего при создании экземпляра делегата), и самое главное — позволяет отвязаться от типа делегата, т.к. теперь функциональный тип определяется только сигнатурой.
(это о чём я говорил еще с первой версии дотнета, что вот здесь у них коровья лепёшка для разрабов)
И да, сейчас получить указатель непосредственным образом можно только на статические методы.
Ниже в сырцах можно подсмотреть, как получить указатель на экземплярный метод и как вызывать затем.
Например, экземплярный делегат для каждого экземпляра объекта надо создавать каждый раз новый.
А тут достаточно в некоей своей структуре сохранить пару — указатель на метод и ссылку на экземпляр.
В дотнете для борьбы с таким сценарием обычно создают статический прокси-метод.
(К сожалению, сей простой паттерн не задействовали для машинки async-метода, на каждый вызов создаётся унутре делегат — тяжелое наследение .Net Framework, проклятая индустрией индюшатина... )) )
Сырцы бенчмарка, каждый файл образует свой проект | |
| |
S>И как вы ухитряетесь его сократить при помощи СompilerServices.Unsafe?
Часть нейтивной функциональности переношу в дотнет, избавляясь от интеропа.
СompilerServices.Unsafe позволяет получать управляемые ссылки из, грубо, void*, чего до него не было.
Или же приводить одну управляемую ссылку в другую, например, ссылку на byte на ссылку на SomeStruct.
Кароч, позволяет реинтерпретировать память по моему усмотрению.
V>>Я именно тебе приводил не так давно тесты вызова interop или ф-ий через unsafe-указатели — результаты катастрофические.
V>>В обоих случаях.
S>Что-то сходу не могу найти тех тестов.
Так я и не тебе приводил.
Ссылку дал выше.
S>И, главное, как нода-то ухитряется сделать интероп быстрее?
Ноде не надо заботиться о фреймах стека для GC, поэтому, вызывает нейтивные ф-ии напрямую.
"Ополовинить интероп" в C# имелось ввиду отрезать/снизить почти вдвое надобность в интеропе.