Pull to refresh
Comments 23
Да и дженерики в отличие от шаблонов код не генерируют и не могут производить оптимизации частных случаев.

Для каждого value-типа JIT генерирует отдельную реализацию. Реализации переиспользуются только для reference-типов (то есть там, где всё равно будет указатель).


А вот миллионы лишних переключений и вызовов функций выкинуть некому.

Вообще-то JIT инлайнить умеет.


Так же не ясно, что именно имелось ввиду под "переключениями между контекстами", поскольку в классическом понимании переключение контекста происходит при переключениях в ядро ОС и обратно, прерываниях и смене текущего потока при использовании кооперативной многозадачности (вызов longjmp, например). На производительность итераторов в языке вышеописанное влиять не может.


Главная же проблема с производительностью в том, что JIT не может тратить минуты на компиляцию MSIL в нативный код, а потому применяемый набор оптимизаций ограничен. Некоторые вещи оптимизируются самим компилятором, например замена foreach на for, если тот был использован на массиве.


Так же следует понимать, что yield return генерирует не самую оптимальную реализацию итератора, в частности, из-за этого в методах из состава System.Linq.Enumerable используются итераторы, написанные вручную.

Согласен, про контексты не корректно выразился. Я имею в виду yeild. Это по сути аналог переключения контекстов потоков, только для корутин. Дешевле, но не бесплатно.
Там всего лишь генерируется стейт машина. Всё работает в одном потоке и кроме лишних джампов, на постоянный вызов функции итератора, там нет.
Основная претензия к производительности итераторов в C# заключалась в том что на каждый вызов приходится создавать объект итератора (который к тому же часто ещё и боксится).
И если ваш код делает некоторые быстрые вычисления очень много раз, то накладные расходы будут заметны. В то время как при итерировании объектов БД у вас скорее всего такой проблемы не возникнет.
Вы привели отличный пример, для чего и были созданы итераторы в шарпе: тяжёлые асинхронные операции. В них мелочи вроде косвенного вызова или даже боксинг совсем незаметны. Но если нам нужна всего лишь ленивая конкатенация, то накладные расходы становятся больше простого взятия символа из строки.
В C# итераторы более развитые и у них нет проблемы целостности.

С этого места поподробнее, пожалуйста.

Здесь имеется в виду проблема С++ итераторов, описанная параграфом выше. Итератор не может сказать, валиден ли он. Для итерирования всегда нужна пара, иначе не остановиться. В С# такого нет, в нём один объект итератора полностью отвечает и за взятие следующего, и за остановку. Буду рад узнать название лучше, чем целостность.

Это не проблема, а преимущество. Это значит, что в плюсах итераторы правильно декомпозированы, и не хранят ничего лишнего. Например, алгоритму std::copy_n не требуется информация о конце итераторов.


А если нужно обязательно знать конец — make_iterator_range, и в путь.

Огромное преимущество: следить за валидностью итераторов вручную.
Если в std::copy_n передать число больше, чем количество оставшихся элементов, то как он себя поведет?
Огромное преимущество: следить за валидностью итераторов вручную

Что значит "вручную"? Сформулируйте развёрнуто.


Если в std::copy_n передать число больше, чем количество оставшихся элементов, то как он себя поведет?

А зачем так делать?

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

Как производится эта проверка?


А как узнать сколько осталось до конца?

Где конкретно требуется эта информация?


надо остаток сохранить

Вот этого не понял. Что и где нужно сохранить?

Как производится эта проверка?
Enumerator.MoveNext() вернет false
Отсутствие целосности итераторов в С++ является причиной появления всех этих copy_n, copy_if, remove_if и так далее. Половинчатый характер итератора не дает, собственно, декомпозировать алгоритмы. Как следствие имеем комбинаторный взрыв количества функций. Вот как реализуется copy_n для range:

copy(source | take(n), destination)

Итератор — он должен итерировать. А итерировать без знания когда нужно закончить — практически нецелесообразно. Я не согласен что в С++ мы имеем пример верной декомпозиции.
copy(source | take(n), destination)

Прекрасно, я только за.
Кстати, а переменная destination здесь — это что?

Это так называемый OutputRange — объект реализующий метод put, принимающий значения соответствующего типа.

Очень хорошо. Тогда следующий вопрос: зачем ему быть диапазоном, если достаточно одного итератора?

Зачем ему быть итератором, если ему достаточно реализовать лишь один единственный метод? :-)


import std.stdio;
import std.outbuffer;
import std.conv;
import std.range;
import std.algorithm;

struct Producer
{
    int i;
    int count;
    this( int count )
    {
        this.count = count;
    }
    auto empty()
    {
        return i >= count;
    }
    auto front()
    {
        return i;
    }
    auto popFront()
    {
        i++;
    }
}

struct Consumer
{
    void put( int i )
    {
        write( i );
    }
}

void main()
{
    Producer( 10 ).take( 5 ).copy( Consumer() ); // 01234

    Producer( 5 ).take( 10 ).copy( Consumer() ); // 01234

    Producer( 5 ).map!( to!string ).copy( stdout.lockingTextWriter ); // 01234

    Producer( 5 ).map!( to!string ).joiner.write; // 01234

    5.iota.map!( to!string ).joiner.write; // 01234

    5.iota.each!write; // 01234
}

Замечательно. А теперь я хочу указатель использовать.

Не буду утверждать со стопрцентной уверенностью, но скорее всего это влияние стандарта С++11. Поддержка многопоточности сломала некоторые оптимизации в стандартной библиотеке, например, copy on write. Скорее всего в конкатенации тоже что-то поменялось. Я постараюсь сегодня повторить бенчмарки, тогда можно будет сравнить дизасм.
Only those users with full accounts are able to leave comments. Log in, please.