Комментарии 15
Приходилось несколько раз переписывать статью, потому что находил еще какую-нибудь "ну точно последнюю" микрооптимизацию.
В итоге большая часть функционала работает быстрее, чем у ближайшего "конкурента", хотя имплементация сего чуда заняла буквально несколько дней (+ допиливание)
Была задача сделать generic-тензоры для C#, и да, они используются в другом проекте, который тоже на C#.
Числовые тензоры наверное лучше делать на всяких cpp, cuda и вот это все (хотя и такие делают на шарпе). Но здесь именно для кастомного типа, для generic, так что по-другому бы не получилось
2-5 раз это очень абстрактное утверждение, если честно. Может быть вы правы, я не уверен на 100%.
Я думаю на C# можно писать не менее производительный код, как на C++, но это нужно очень, очень хорошо знать низкоуровневый C#, тот, что человек изучает в C++ как нормальный. Тут есть тебе и стековая аллокация (кроме классов), и указатели, и simd, и даже общение с управлением памяти и байт-кодом, который транслируется в ассемблер до того, как начинает исполняться.
А про F# не знаю, к сожалению.
Если сравнивать с C#, то производительность программы может быть лучше или хуже, чем в итеративных вариантах — зависит от конкретного алгоритма. К тому же, F# позволяет писать в итеративном стиле, также как и С# позволяет писать в функциональном.
Если сравнивать с хорошо написанной программой на C/C++, то, конечно, производительность программы будет ниже, но при этом производительность программиста в скорости реализации задач будет в разы (а может и на порядок) выше, чем при написании на C/C++, т.к. программисту не нужно тратить время на низкоуровневые проблемы.
Всегда несколько смущали заявления в стиле
производительность программиста в скорости реализации задач будет в разы выше
Особенно часто слышу это для питона.
При этом Вы выше говорите о
хорошо написанную программу
На мой взгляд, человек, хорошо знающий плюсы будет писать не менее быстро, чем человек знающий питон. Другое дело, что, действительно, первый человек куда более редкий и, вдобавок, таким сложнее стать. Однако это другой вопрос, как мне кажется.
Что касается
программисту не нужно тратить время на низкоуровневые проблемы.
С этим не могу согласиться. Послушайте интревью Страуструпа Фридману, он, в том числе, об этом там говорит: проблема в том, что люди считают, что низкоуровневые вставки сделают их код быстрее, но это не так. Такие случаи есть, но как правило люди не хотят тратить время на изучение стандартной библиотеки и пихают макросы и прочии сишности без разбора. Сам Страуструп говорит, что он против такого и за встроенные механизмы абстракции языка плюс стандартную библиотеку.
Я писал ниже про то, что использую фреймворк для анализа данных от церна root. И довольно много провел времени за изучением его исходников. Так вот там очень хорошо видно, что низкоуровневые вставки скорее от незнания stdlib, а в некоторых случаях от того, что stdlib просто не было когда root начинали писать.
Однако в целом я согласен с тем, что по производительности c# в умелых руках весьма хорош. Опять же писал ниже про примеры в геймдеве и в приложениях.
… Однако это другой вопрос, как мне кажется.В реалиях этот вопрос оказывается ключевым. Вы, похоже, очень хорошо разбираетесь как в программировании, так и в предметной области для которой пишете программы сами либо посредством продуктивного взаимодействия с другими программистами. Проблема в том, что таких как вы мало, и ваши труд стоит очень дорого. Поэтому приходится прибегать к менее продуктивным инструментам с богатыми возможностями, либо использовать продуктивные инструменты непродуктивным образом.
… Особенно часто слышу это для питона.
Дак потому что почти все уже украдено, простите, написано до нас и остается только связать между собой готовые процедуры, что Питон позволяет сделать с особой легкостью и внутренним цинизмом. Проблемы начинаются, когда этих процедур не хватает. Простите за банальности.
Добрый день.
Спасибо за статью.
upd.
Честно говоря, все пока не прочитал, хотя интересно. Несколько коробит отождествление тензора и n-мерной матрицы. Все-таки тензор хоть и описывает матрицу, но тоже транспонирование, ~~выражающиеся для тензора в замене ковариантности на контрвариантность делается через метрический тензор ~~ Прошу прощения, для тензоров есть транспонирование, однако, оно не совпадает с транспонированием в смысле матрицы. Однако это лишь мои жалкие знания из использования тензоров в электродинамике и сто, возможно, в тензорном анализе действительно есть более общие ситуации.
Я хотел бы касательно комментария по поводу применимости Шарпа к мат. вычислениям добавить:
На самом деле, хотя и есть некоторые соображения в пользу относительной закрытости .net в прошлом, я не очень понимаю, почему его не используют.
Так случилось, что я разрабатываю на шарпе и параллельно занимаюсь анализом данных. Так, например, церновский root, а точнее новый интерфейс rdataframe становится сильно похож на linq:
Также для параллельных вычислений у них есть Task…
Безусловно, gc вносит вклад и, используя linq, легко получить множественные вызовы gc и дикую просадку по производительности. Однако тот же гейм дев вроде бы нормально существует на шарпе, хотя, насколько я знаю, с табу на linq. Хamarin тоже может быть примером за.
В целом я с большой надеждой смотрю на .net core,ml.net, Microsoft.Data.Analysis и f#…
Спасибо за хороший комментарий. Транспонирование в значении изменение порядка осей я взял из других библиотек, к примеру, так делают NumPy и PyTorch. Кажется, что это все равно будет самым частым использованием и пониманием, опять же, для тех, кто знаком с NumPy.
С другой стороны, с математической точки зрения возможно стоит это дело переименовать, не знаю.
По поводу анализа данных, наверное, без GPU тут не обойтись, разве нет? В моем случае поддержки GPU и SIMD никогда не будет именно потому, что я поддерживаю произвольный тип.
Гейм-дев… насколько я знаю, Unity нехило помогает GC в управлении памяти, хотя могу ошибаться
В Unity используется очень простой Boehm GC, в котором нет даже деления на поколения. Что больше всего помогает GC в управлении памятью в Unity ― это не генерировать тонны мусора в каждом кадре.
С версии Unity 2019 сборщику мусора добавили incremental-режим, чтобы не тормозить мир надолго, а размазывать одну относительно большую паузу по нескольким соседним кадрам.
https://blogs.unity3d.com/2018/11/26/feature-preview-incremental-garbage-collection/
var c = default(TWrapper).Addition(a, b);
Не очень понятен смысл этой жуткой конструкции. Вы хотите обобщенную арифметику? Я писал об этом кривую статью некоторое время назад.
После советов с более опытными разработчиками выяснилось, что JIT отлично соптимизирует код вида
if(typeof(T) == typeof(int)) return Unsafe.As<int, T> (Unsafe.As<T, int>(a) + Unsafe.As<T, int>(b));
Или что-то типа этого (у вас в ссылках есть касты через object
). По скорости чуть-чуть отстает от прямого использования +
, который превращается в три IL
инструкции.
Теперь, в 2к20, можно вообще не выпендр"иваться и вооружиться source generators, которые за вас весь бойлерплейт на каждый тип и сгенерят. Ну или упороться с IL
с помощью чудесного InlineIL.Fody
.
Далее, хотя кажется, что логично в индексаторе использовать params, то есть как-то так:
Зачем вам params
когда можно создать свой собственный тип? Есть же Index
/Range
для линейного индексирования, заведите свой TensorIndex
. Можно ручками прописать множество конструкторов/или операторов преобразования, можно добавить поддержку кортежей (tuples, генератор исходников в помощь). Можно даже пропихнуть System.Runtime.CompilerServices.ITuple
в качестве аргумента аксессора this
, что позволит писать код вида var x = tensor[(5, 6, 7)]
, разумеется, ценой боксинга (вряд ли сильно лучше массива).
Попробуйте принимать ReadOnySpan<uint>
в качестве индекса, и тогда вам будет доступен код вида var y = tensor[stackalloc uint[] {5, 6, 7}]
, что конечно чуть длиннее, зато allocation-free (вот пример).
P.S.: Я честно считал что в Math.NET
есть тензоры, но их там не оказалось. Я видел ссылки на System.Numerics.Tensors
от MS ради поддержки ML
-задач, но эти тензоры вроде должны были стать частью BCL
, но
похоже испарились.
if(typeof(T) == typeof(int))
Я уже очень много раз сказал, что должен быть кастомный тип. Это значит не пять встроенных типов, а любой.
касты через object
В моем коде не должно быть такого ужаса
вооружиться source generators, которые за вас весь бойлерплейт на каждый тип и сгенерят.
Ага, чтобы вместо скачивания через NuGet пользователю пришлось билдить под себя. Это уже слово из трех букв: c++.
Ну а про tuple на стеке — в принципе да, хотя я не уверен, что оно столько же времени будет работать, но вероятно. С другой стороны, есть цикл, и придется безусловно итерироваться по длине этого tuple-а, это может быть чуть дороже, чем хотелось
А еще я получается жертвую удобством пользователя библиотеки ради удобства себя любимого
Тензоры для C#. И матрицы, и векторы, и кастомный тип, и сравнительно быстро