Re[16]: Замкнутая бинарная операция
От: Qbit86 Кипр
Дата: 09.06.20 09:52
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>А вам обычно мультипликативный моноид и не нужен. Вам обычно нужно умение вызывать встроенный оператор *.


Всё верно, для многих алгоритмов нужна не абстракция «моноид», а только абстракция «замкнутая бинарная операция».

S>Ну, вот как в примере про умножение матриц — там нафиг не нужна единица, нужно собственно умножение, сложение, и zero, он же default.


Так вроде и zero не нужен? Когда у тебя коллекции непустые (здесь коллекции это строки и столбцы матрицы), то начальный элемент задавать не нужно (reduce вместо fold).
Глаза у меня добрые, но рубашка — смирительная!
Re[9]: IEqualityComparer<T>
От: hi_octane Беларусь  
Дата: 09.06.20 09:56
Оценка:
Q>А я объяснил, почему твоё объяснение некорректное. Про straw man не только к AlexRK относилось, но и к тебе в первую очередь; почитай, пожалуйста.
Корректное. Просто тебе не нравится сама мысль о том что кто-то может прекрасно знать M-words и при этом весьма скептически на них смотреть. Я в первом же утверждении написал "людям сначала нравится элегантность и единообразность монад, моноидов и этого всего". И продолжаю придерживаться этого тезиса. Для меня эти конструкции одинаково "заражают" код. Решают местечковую проблемку, но порождают другую, которая на дистанции только растёт.

_>>Сейчас ты решил что всё понял.

Q>Да, я всё понял: ты слышал звон, но не знаешь, где он. M-word — эта сложна! Нипанятна!
Ну вот пруф. Ты решил что мне именно "сложна и нипанятна". Представить что кому-то кроме достоинств понятны и недостатки — пока никак.

Q>А, речь не про этот M-word?.. Всё равно от лукавого, буквы-то те же! Нафиг не нужон моноид ваш!

Это то что ты понял. А то что там было написано: "эти абстракции порождают похожие проблемы в большом проекте".

Q>>>Внезапно, код с моноидом в стиле как на видео будет быстрее, чем стандартная лапша с делегатами : )

Q>Хоть по этому вопросу возражений нет.
Ты пропустил замечание что для сложения и умножения чаще всего вообще не стоит лепить лапшу с делегатами.

Q>Если при использовании IMonoid<T> непременно такие сложности возникнут, то они же неизбежно должны возникнуть и при использовании интерфейса IEqualityComparer<T>? Сценарии использования у них ведь строго одинаковые.

Разные. Компаратор очень ограничен в возможностях. Предел комбинирования для компараторов — библиотечка OrderBy...ThenBy, и всё. Новые экземпляры T компаратор не порождает. Моноиды гибче и в терминальной стадии распространения могут заменять собой не то что IEqualityComparer, а даже просто IComparer.
Re[10]: IEnumerable<T>
От: Qbit86 Кипр
Дата: 09.06.20 10:11
Оценка:
Здравствуйте, hi_octane, Вы писали:

_>Ты пропустил замечание что для сложения и умножения чаще всего вообще не стоит лепить лапшу с делегатами.


Хорошо. Что тогда нужно лепить вместо моноида в этом алгоритме? Как его переписать без еретического моноида?
internal static T Reduce<T, TMonoid>(this IEnumerable<T> items, TMonoid monoid)
    where TMonoid : IMonoid<T>
{
    if (items is null)
        throw new ArgumentNullException(nameof(items));

    T result = monoid.Identity;
    foreach (T item in items)
        result = monoid.Combine(result, item);

    return result;
}


Q>>Если при использовании IMonoid<T> непременно такие сложности возникнут, то они же неизбежно должны возникнуть и при использовании интерфейса IEqualityComparer<T>? Сценарии использования у них ведь строго одинаковые.

_>Разные. Компаратор очень ограничен в возможностях.
_>Новые экземпляры T компаратор не порождает.

Хорошо а IEnumerable<T> — это тоже харам? Он же порождает; ты его тоже избегаешь? Потому что на энумераторах можно много чего наворотить.
Глаза у меня добрые, но рубашка — смирительная!
Re[6]: default(TMonoid)
От: Qbit86 Кипр
Дата: 09.06.20 13:37
Оценка: 7 (1)
Здравствуйте, Sinclair, Вы писали:

Q>>Даже конструктор не нужен (его констрейнт и вызов), достаточно default(M) с констрейнтом struct. В той статье «Concept C#: Type Classes for the Masses» именно такой подход рассматривался.

S>Тот же конструктор, вид в профиль — построен на автоматической доступности default constructor для структур.

Интуитивно кажется, что это не так, и «default(TMonoid)» должен быть эффективнее, чем «new TMonoid()» для создания экземпляров value-типов вроде decimal в generic-коде. Хотя бы из-за Activator.CreateInstance().
Но точно не знаю, так что побенчмаркал. В .NET Core < 3.0 действительно new TMonoid() заметно медленнее. Но в .NET Core 3.0+ уже разницы нет. Activator.CreateInstance() по-прежнему есть в IL, но, видимо, JIT как-то лучше оптимизирует.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: default(TMonoid)
От: rameel https://github.com/rsdn/CodeJam
Дата: 09.06.20 14:05
Оценка: 8 (2)
Здравствуйте, Qbit86, Вы писали:

Q>Интуитивно кажется, что это не так, и «default(TMonoid)» должен быть эффективнее, чем «new TMonoid()» для создания экземпляров value-типов вроде decimal в generic-коде. Хотя бы из-за Activator.CreateInstance().

Q>Но точно не знаю, так что побенчмаркал. В .NET Core < 3.0 действительно new TMonoid() заметно медленнее. Но в .NET Core 3.0+ уже разницы нет. Activator.CreateInstance() по-прежнему есть в IL, но, видимо, JIT как-то лучше оптимизирует.

Да, джит устраняет Activator.CreateInstance<T>() если T — это value type Github: Optimize Activator.CreateInstance
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[15]: Baseline
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.06.20 15:23
Оценка: +1
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Sinclair, Вы писали:


S>>Надо не IEnumerable, а int[] items. Там 50% времени — это вызовы в GetNext.


Q>Эта уловка называется «moving the goalspots» [1] [2]

Нет. Если внимательно посмотреть в мой ответ про перформанс, то окажется, что там приведён вот такой код:
public int AddIntegers(int[] ints)
{
  int result=0;
  foreach(var i in ints)
    result+=i;
  return result
}

Это вы всё время стараетесь искусственно замедлить забег Ахилла

Q>Алгоритмы-то всё-таки пишутся не только над массивами, а над произвольными структурами (деревья, графы общего вида, etc).

Это как раз понятно; но обычной проблемой обобщённых алгоритмов такого вида является то, что они работают плохо и на массивах.

Q>Но ладно! Начав писать бенчмарки, сложно остановиться.


Q>
internal static T Reduce<T, TMonoid>(this T[] items, TMonoid monoid)
Q>    where TMonoid : IMonoid<T>
Q>{
Q>    if (items is null)
Q>        throw new ArgumentNullException(nameof(items));

Q>    T result = monoid.Identity;
Q>    for (int i = 0; i != items.Length; ++i)
Q>        result = monoid.Combine(result, items[i]);

Q>    return result;
Q>}

Q>internal static int ReduceInt32(this int[] items)
Q>{
Q>    if (items is null)
Q>        throw new ArgumentNullException(nameof(items));

Q>    int result = 0;
Q>    for (int i = 0; i != items.Length; ++i)
Q>        result += items[i];

Q>    return result;
Q>}


Q>
Q>|                 Method |       Mean |     Error |    StdDev | Ratio | RatioSD | Code Size |
Q>|----------------------- |-----------:|----------:|----------:|------:|--------:|----------:|
Q>| ReduceIntegersBaseline |   7.737 ns | 0.4087 ns | 0.0224 ns |  1.00 |    0.00 |     139 B |
Q>|         ReduceIntegers |   7.409 ns | 0.4154 ns | 0.0228 ns |  0.96 |    0.00 |     162 B |
Q>|      AggregateIntegers |  85.019 ns | 4.5889 ns | 0.2515 ns | 10.99 |    0.04 |     419 B |
Q>|         ReduceDecimals | 132.324 ns | 9.0149 ns | 0.4941 ns | 17.10 |    0.07 |     314 B |
Q>|      AggregateDecimals | 171.322 ns | 4.9471 ns | 0.2712 ns | 22.14 |    0.07 |     595 B |
Q>

Во-первых, давайте замерим хотя бы на десятке тысяч интов. Для десяти целых почти любой способ будет приемлемым, даже если складывать их передавая в javascript.
Во-вторых, использование i != items.Length вместо i < items.Length добавляет range check к каждой итерации. Интересно, что для линейного массива он вроде бы не сказывается на быстродействии. Для 2d массивов замедление весьма существенное — может быть,
В-третьих, удивительно, но факт: JIT всё же порождает разный код для generic случая и для специализации. Если починить проблему с !=, то generic версия начинает работать вот так:
| Special                                  | Generic  
+------------------------------------------+-------------------------------------------
| 7FF85A3A41A7 movsxd    r9,edx            | 7FF85A3B3ED7 movsxd    r9,edx
|                                          | 7FF85A3B3EDA mov       r9d,[rcx+r9*4+10]
| 7FF85A3A41AA add       eax,[rcx+r9*4+10] | 7FF85A3B3EDF add       eax,r9d
| 7FF85A3A41AF inc       edx               | 7FF85A3B3EE2 inc       edx



Вот эта лишнаяя инструкция ест 7% времени:
|                 Method |      Mean |     Error |    StdDev | Ratio | RatioSD | Code Size |                                                                                                                        |----------------------- |----------:|----------:|----------:|------:|--------:|----------:|
| ReduceIntegersBaseline |  6.846 us |  1.775 us | 0.0973 us |  1.00 |    0.00 |     128 B |
|       ReduceIntegersRC |  6.787 us |  2.507 us | 0.1374 us |  0.99 |    0.01 |     139 B |
|         ReduceIntegers |  7.309 us | 10.276 us | 0.5632 us |  1.07 |    0.10 |     151 B |
|      AggregateIntegers | 69.800 us | 78.744 us | 4.3162 us | 10.20 |    0.73 |     419 B |
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Roles in C# 9. Нужно?
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.06.20 15:28
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>Берем QuikSort и ничего там переопределять вообще не нужно.

Да, только в C# не работает.
S>И куча алгоритмов для арифметических типов переопределять не надо. Написал для инта проверил, и все остальное для всех алгеброических типов идет.
Коллега, вы сецчас пишете о чём-то своём.
S>Суть то ролей в том, что бы ты использовал текущую перегрузку опрераторов. Не хочешь действуй как и раньше только вместо
Ничего подобного в ролях нету.
S> В твоем примере можно использовать текущие перегрузки операторов.
Нельзя.
Мэдс же показал код — чтобы обобщённый код подхватил операторы типа +, нужно, чтобы либо генерик-параметр реализовал IMonoid<T> (ну, или там IRing<T>), либо руками выписывать роль, в которой IMonoid<int>.operator+ мапится на int.operator+.
Чтобы этот же код заработал не с int, а с Complex, надо опять руками выписывать роль ComplexAddMonoid и руками же скармливать её в генерик-параметр метода.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: Замкнутая бинарная операция
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.06.20 15:33
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Всё верно, для многих алгоритмов нужна не абстракция «моноид», а только абстракция «замкнутая бинарная операция».

Ещё чаще нам нужно просто вызвать у типа operator +, а сделать это невозможно потому, что язык вплоть до нынешней версии устроен так, что это невозможно.
Трюк с instance monoid вы применяете не потому, что вам реально нужна возможность менять тип замкнутой операциии на лету, а чтобы обойти вот это ограничение, которое в С++ работает благодаря compile-time специализации.
То есть руками объясняете обобщённому коду, что плюс — это плюс, и он порождает почти такой же бинарь, как настоящий специализированный код.

S>>Ну, вот как в примере про умножение матриц — там нафиг не нужна единица, нужно собственно умножение, сложение, и zero, он же default.


Q>Так вроде и zero не нужен? Когда у тебя коллекции непустые (здесь коллекции это строки и столбцы матрицы), то начальный элемент задавать не нужно (reduce вместо fold).

Технически — да. С т.з. простоты кода удобнее писать с default/zero.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: Roles in C# 9. Нужно?
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 09.06.20 16:09
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Serginio1, Вы писали:


S>>Здравствуйте, Sinclair, Вы писали:


S>>Берем QuikSort и ничего там переопределять вообще не нужно.

S>Да, только в C# не работает.
Ну так роли для этого и сделаны.
S>>И куча алгоритмов для арифметических типов переопределять не надо. Написал для инта проверил, и все остальное для всех алгеброических типов идет.
S>Коллега, вы сецчас пишете о чём-то своём.
S>>Суть то ролей в том, что бы ты использовал текущую перегрузку опрераторов. Не хочешь действуй как и раньше только вместо
S>Ничего подобного в ролях нету.
S>> В твоем примере можно использовать текущие перегрузки операторов.
S>Нельзя.
S>Мэдс же показал код — чтобы обобщённый код подхватил операторы типа +, нужно, чтобы либо генерик-параметр реализовал IMonoid<T> (ну, или там IRing<T>), либо руками выписывать роль, в которой IMonoid<int>.operator+ мапится на int.operator+.
S>Чтобы этот же код заработал не с int, а с Complex, надо опять руками выписывать роль ComplexAddMonoid и руками же скармливать её в генерик-параметр метода.
Там

Смотрим старый вброс на шейпах

https://www.c-sharpcorner.com/article/candidate-features-for-c-sharp-9/

Original Source


public shape SGroup<T>      
{      
 static T operator +(T t1, T t2);      
 static T Zero {get;}       
}

This declaration says that a type can be an SGroup<T> if it implements a+ operator over T, and a Zero static property.



public extension IntGroup of int: SGroup<int>    
{    
 public static int Zero => 0;    
}

And the extension.



public static AddAll<T>(T[] ts) where T: SGroup<T> // shape used as constraint    
{    
 var result = T.Zero; // Making use of the shape's Zero property    
 foreach (var t in ts) { result += t; } // Making use of the shape's + operator    
 return result;    
}

Let us call the AddAll method with some ints,


int[] numbers = { 5, 1, 9, 2, 3, 10, 8, 4, 7, 6 };     
  
WriteLine(AddAll(numbers)); // infers T = int


Я так понял, что вместо
 public shape SGroup<T>


можно использовать интерфейс
а вместо
public extension IntGroup of int: SGroup<int>


можно использовать

role IntGroup  extednds int


И в примере Мэдс все тоже самое. Ты переопределяешь только
public static int Zero => 0;



И разница в том, что int[] нужно привести к IntGroup[]

В любом случае определить можно сделать один интерфейс для всех перегрузок операторов алгеброических типов и прописать для них роли
и использовать эти роли хоть откуда как Func<> Action итд.
и солнце б утром не вставало, когда бы не было меня
Отредактировано 09.06.2020 16:23 Serginio1 . Предыдущая версия . Еще …
Отредактировано 09.06.2020 16:15 Serginio1 . Предыдущая версия .
Re[8]: Оптимизация производительности
От: Sharov Россия  
Дата: 09.06.20 20:57
Оценка: :)
Здравствуйте, Sinclair, Вы писали:


S>
S>public int AddIntegers(int[] ints)
S>{
S>  int result=0;
S>  foreach(var i in ints)
S>    result+=i;
S>  return result
S>}
S>

.
S>Я могу написать код, который делает обобщённый static T Reduce<T, TMonoid>(this IEnumerable<T> items, TMonoid monoid) быстрее, чем AddIntegers выше (без учёта времени прогрева), но это упраженение, которое я бы не стал заставлять делать каждого гражданского разработчика.

Будте добры, а то не верится что будет быстрее. Уж больно T Reduce<T, TMonoid>(this IEnumerable<T> items, TMonoid monoid) абстрактный, со всеми косвенностями.
Кодом людям нужно помогать!
Re[16]: mov
От: Qbit86 Кипр
Дата: 09.06.20 22:36
Оценка: 118 (2)
Здравствуйте, Sinclair, Вы писали:

S>Нет. Если внимательно посмотреть в мой ответ про перформанс, то окажется, что там приведён вот такой код:


А если внимательно посмотреть родительский комментарий, то там приведён другой код.

S>Это вы всё время стараетесь искусственно замедлить забег Ахилла


А ты пытаешься навязать мне какую-то другую задачу. Хочешь итерироваться по массиву? Окей, тогда сначала материализуй исходный IEnumerable<T> в массив, и ходи по нему. Например, рёбра остовного дерева не в массиве приходят, а генерируются в процессе обхода графа.

Q>>Алгоритмы-то всё-таки пишутся не только над массивами, а над произвольными структурами (деревья, графы общего вида, etc).


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

S>обычной проблемой обобщённых алгоритмов такого вида является то, что они работают плохо и на массивах.


Насколько я вижу, отлично работают. Лучше, чем можно было бы ожидать. И компилятор в CIL, и JIT из CIL.

S>Во-первых, давайте замерим хотя бы на десятке тысяч интов.


Окей, исправил в бенчмарке на 10 тысяч. Хоть это и не так важно, имхо; если нужна большая точность и статистическая значимость, то можно просто RunMode.Short исправить на RunMode.Long.

S>Во-вторых, использование i != items.Length вместо i < items.Length добавляет range check к каждой итерации. Интересно, что для линейного массива он вроде бы не сказывается на быстродействии.


Окей, даже хоть и не сказывается, заменим на foreach, как в твоём варианте.

S>В-третьих, удивительно, но факт: JIT всё же порождает разный код для generic случая и для специализации. Если починить проблему с !=, то generic версия начинает работать вот так:

S>
S>| Special                                  | Generic  
S>+------------------------------------------+-------------------------------------------
S>| 7FF85A3A41A7 movsxd    r9,edx            | 7FF85A3B3ED7 movsxd    r9,edx
S>|                                          | 7FF85A3B3EDA mov       r9d,[rcx+r9*4+10]
S>| 7FF85A3A41AA add       eax,[rcx+r9*4+10] | 7FF85A3B3EDF add       eax,r9d
S>| 7FF85A3A41AF inc       edx               | 7FF85A3B3EE2 inc       edx
S>


У меня разницы нет; эта промежуточная инструкция есть в обоих вариантах 🤷 Такое окружение:
.NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT


S>Вот эта лишнаяя инструкция ест 7% времени:


У меня на RunMode.Long такой результат:
|                 Method |       Mean |     Error |    StdDev |     Median | Ratio | RatioSD | Code Size |
|----------------------- |-----------:|----------:|----------:|-----------:|------:|--------:|----------:|
| ReduceIntegersBaseline |   4.879 us | 0.0074 us | 0.0385 us |   4.879 us |  1.00 |    0.00 |     131 B |
|         ReduceIntegers |   4.851 us | 0.0121 us | 0.0621 us |   4.876 us |  0.99 |    0.02 |     151 B |
|      AggregateIntegers |  74.457 us | 0.1019 us | 0.5166 us |  74.364 us | 15.26 |    0.15 |     419 B |
Глаза у меня добрые, но рубашка — смирительная!
Re[18]: Constraints
От: Qbit86 Кипр
Дата: 09.06.20 23:11
Оценка:
Здравствуйте, Sinclair, Вы писали:

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


В C++ можно вызвать «operator +», да. А ещё позвать оператор %, оператор запятую и вообще любую дичь а-ля Perl, которую только сможет натопать по клавиатуре кот — компилятор-то шаблоны не проверяет. Нет constraint'ов как в C# и других языках здорового человека.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: default(TMonoid)
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.20 02:43
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Интуитивно кажется, что это не так, и «default(TMonoid)» должен быть эффективнее, чем «new TMonoid()» для создания экземпляров value-типов вроде decimal в generic-коде.

Мы говорим об одной операции на вызов. Это дорого только в каких-то сценариях типа динамической композиции.
Если мы про массовую обработку, то можно себе позволить вообще на лету компилировать специализированный код всего цикла.
Вы больше теряете из-за того, что используете != вместо < при обходе массивов

Q>Но точно не знаю, так что побенчмаркал. В .NET Core < 3.0 действительно new TMonoid() заметно медленнее. Но в .NET Core 3.0+ уже разницы нет. Activator.CreateInstance() по-прежнему есть в IL, но, видимо, JIT как-то лучше оптимизирует.

Да, можно глянуть в финальный x86 — для тривиальных конструкторов там скорее всего не сильно хуже, чем тупо инкремент указателя кучи.
Но вы, конечно же, правы: если мы жмём такты, то interface over struct as generic parameter — то, что доктор прописал.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Roles in C# 9. Нужно?
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.20 02:47
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Смотрим старый вброс на шейпах


S>https://www.c-sharpcorner.com/article/candidate-features-for-c-sharp-9/


S>Original Source



S>
S>public shape SGroup<T>      
S>{      
S> static T operator +(T t1, T t2);      
S> static T Zero {get;}       
S>}  
S>

S>

S>This declaration says that a type can be an SGroup<T> if it implements a+ operator over T, and a Zero static property.



S>
S>public extension IntGroup of int: SGroup<int>    
S>{    
S> public static int Zero => 0;    
S>}   
S>

S>

S>And the extension.



S>
S>public static AddAll<T>(T[] ts) where T: SGroup<T> // shape used as constraint    
S>{    
S> var result = T.Zero; // Making use of the shape's Zero property    
S> foreach (var t in ts) { result += t; } // Making use of the shape's + operator    
S> return result;    
S>}   
S>

S>Let us call the AddAll method with some ints,


S>
S>int[] numbers = { 5, 1, 9, 2, 3, 10, 8, 4, 7, 6 };     
  
S>WriteLine(AddAll(numbers)); // infers T = int   
S>


S> Я так понял, что вместо

S>
S> public shape SGroup<T>
S>


S>можно использовать интерфейс

S> а вместо
S>
S>public extension IntGroup of int: SGroup<int> 
S>


S>можно использовать


S>
S>role IntGroup  extednds int
S>


S> И в примере Мэдс все тоже самое. Ты переопределяешь только

S>
S>public static int Zero => 0; 
S>

Да, точно, операторы таки подхватываются.

S>И разница в том, что int[] нужно привести к IntGroup[]

Вот именно. extension в этом смысле лучше, чем роли, т.к. работает автоматика приведения.

S>В любом случае определить можно сделать один интерфейс для всех перегрузок операторов алгеброических типов и прописать для них роли

S>и использовать эти роли хоть откуда как Func<> Action итд.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[19]: Constraints
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.20 02:53
Оценка:
Здравствуйте, Qbit86, Вы писали:
Q>В C++ можно вызвать «operator +», да. А ещё позвать оператор %, оператор запятую и вообще любую дичь а-ля Perl, которую только сможет натопать по клавиатуре кот — компилятор-то шаблоны не проверяет. Нет constraint'ов как в C# и других языках здорового человека.
Об этом и речь. Собственно, с моей точки зрения нужно продолжать фокусироваться над проблемой "выразить наличие статических мемберов типа в ограничениях дженериков", и сопутствующей ей проблемой "как теперь сделать так, чтобы встроенные типы удовлетворяли этим ограничениям без переделки CLR".
extensions выглядят вполне вменяемым решением этой проблемы — я могу скомбинировать свой обобщённый алгоритм с набором екстеншнов для всех встроенных типов, и пользователи смогут им пользоваться без единой лишней строчки кода.
Роли выглядят решением какой-то другой проблемы. Вот ваш Reduce со struct моноидом уже вполне себе прекрасен. Введение ролей его никак не улучшит ни со стороны пользователя, ни со стороны автора — ну сможете вы заменить Combine на += — код от этого не станет более читаемым.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: mov
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.20 02:57
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>А ты пытаешься навязать мне какую-то другую задачу. Хочешь итерироваться по массиву? Окей, тогда сначала материализуй исходный IEnumerable<T> в массив, и ходи по нему. Например, рёбра остовного дерева не в массиве приходят, а генерируются в процессе обхода графа.


Q>Но даже если массивы. В обобщённом коде отстать от мономорфного всего на одну инструкцию промежуточного копирования — отличный результат, уже на порядок (десятичный) лучше протаскивания делегата в аналогичный Linq-метод.



Q>Окей, даже хоть и не сказывается, заменим на foreach, как в твоём варианте.


S>>В-третьих, удивительно, но факт: JIT всё же порождает разный код для generic случая и для специализации. Если починить проблему с !=, то generic версия начинает работать вот так:

S>>
S>>| Special                                  | Generic  
S>>+------------------------------------------+-------------------------------------------
S>>| 7FF85A3A41A7 movsxd    r9,edx            | 7FF85A3B3ED7 movsxd    r9,edx
S>>|                                          | 7FF85A3B3EDA mov       r9d,[rcx+r9*4+10]
S>>| 7FF85A3A41AA add       eax,[rcx+r9*4+10] | 7FF85A3B3EDF add       eax,r9d
S>>| 7FF85A3A41AF inc       edx               | 7FF85A3B3EE2 inc       edx
S>>


Q>У меня разницы нет; эта промежуточная инструкция есть в обоих вариантах 🤷 Такое окружение:

Q>
.NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT

Причуды джита

// * Summary * BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.836 (1909/November2018Update/19H2) Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores .NET Core SDK=3.1.300 [Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT Job-SGTOAB : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT


Q>У меня на RunMode.Long такой результат:

Q>
Q>|                 Method |       Mean |     Error |    StdDev |     Median | Ratio | RatioSD | Code Size |
Q>|----------------------- |-----------:|----------:|----------:|-----------:|------:|--------:|----------:|
Q>| ReduceIntegersBaseline |   4.879 us | 0.0074 us | 0.0385 us |   4.879 us |  1.00 |    0.00 |     131 B |
Q>|         ReduceIntegers |   4.851 us | 0.0121 us | 0.0621 us |   4.876 us |  0.99 |    0.02 |     151 B |
Q>|      AggregateIntegers |  74.457 us | 0.1019 us | 0.5166 us |  74.364 us | 15.26 |    0.15 |     419 B |
Q>

Ну что ж, поздравляю с отличным результатом!
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Оптимизация производительности
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.20 03:03
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Будте добры, а то не верится что будет быстрее. Уж больно T Reduce<T, TMonoid>(this IEnumerable<T> items, TMonoid monoid) абстрактный, со всеми косвенностями.

В управляемом мире ничего абстрактного нет. Удобнее, конечно, начинать не с TMonoid, а с Expression<Func<T, T, T>>, но в принципе ничего военного нет и при работе через интерфейс — вынимаем из него MSIL, и проверяем несколько типовых случаев. Если выполнены сразу все условия — items представляет собой T[]/Span<T>/ReadonlySpan<T>, и код Combine попадает в один из десятка паттернов — то мы переключаемся на SIMD. А если нет — то делаем fallback на обычный код, обработанный JIT-ом — как показал коллега QBit86, он совпадает по быстродействию с ручным кодом итерирования.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Классы типов для бедных
От: Qbit86 Кипр
Дата: 10.06.20 09:06
Оценка: 130 (3) +4
Здравствуйте, varenikAA, Вы писали:

AA>Roles in C# 9. Нужно?


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

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

Стратегии — это как стратегии в книжке «Gang of Four». Кондовый ООП джава-стайл. Например, IEqualityComparer<TKey> в конструкторе Dictionary<TKey, TValue> — это стратегия.

Политики — это примерно как политики в книжке Александреску про C++. Как стратегии, но времени компиляции. В случае словаря это была бы передача в конструктор и захват «TKeyComparer where TKeyComparer : IEqualityComparer<TKey>» вместо IEqualityComparer<TKey>.

Безотносительно ролей и шейпов, такой дизайн стандартной коллекции дал бы следующий профит.

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

Во-вторых, тип компаратора явно становится частью типа словаря вместо тобы, чтобы быть стёртым и спрятанным за ширмой интерфейса IEqualityComparer<TKey>. Это позволяет избежать класса ошибок вроде: мы мерджим два словаря на строках, но, оказывается, первый словарь использует внутри компаратор InvariantCulture, а второй IgnoreCase, а снаружи это никак не видно.

Если у нас такой стиль API, то внедрение шейпов/ролей теоретически могло бы немного снизить синтаксическую нагрузку. Но скорее даже не с пользователей API, а с автора. А автору это не так критично, кмк.
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: Классы типов для бедных
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 10.06.20 12:16
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, varenikAA, Вы писали:


AA>>Roles in C# 9. Нужно?


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

Все очень просто. В большей степени это нужно для алгеброических типов.
C++ шаблоны используют перегрузку методов напрополую. Правда там кодогенерация.
Для ролей же можно генерировать инлайн код из дженерика при Jit е

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

Думаю в большинсте случаев это нужно для скорости, но и берешь кучу алгоритмов и используешь операторы, вместо интерфейсов, делегатов.
Удобно!
и солнце б утром не вставало, когда бы не было меня
Отредактировано 10.06.2020 17:44 Serginio1 . Предыдущая версия .
Re[11]: IEnumerable<T>
От: hi_octane Беларусь  
Дата: 10.06.20 14:31
Оценка:
_>>Ты пропустил замечание что для сложения и умножения чаще всего вообще не стоит лепить лапшу с делегатами.
Q>Хорошо. Что тогда нужно лепить вместо моноида в этом алгоритме? Как его переписать без еретического моноида?
Алгоритма-то нету — сумматор на моноиде и всё. Если ничего сложнее в коде нет, то и не надо переписывать. Задуматься что "свернули не туда" надо если потребитель твоего кода вынужден описывать и передавать пяток моноидов для разных целей, или если они у тебя или у пользователя поштучно и группами кочуют из метода в метод.

Q>Хорошо а IEnumerable<T> — это тоже харам? Он же порождает; ты его тоже избегаешь? Потому что на энумераторах можно много чего наворотить.

IEnumerable уже часть языка, избегать не обязательно. Но с шаблоном "тут последовательность данных, значит IEnumerable" закопать проект вполне можно.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.