Почему OfType <> быстрее, чем Cast <>?

В ответ на следующий вопрос: Как преобразовать MatchCollection в строковый массив

Учитывая два выражения Linq:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .OfType<Match>() //OfType
    .Select(m => m.Groups[0].Value)
    .ToArray();

и

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .Cast<Match>() //Cast
    .Select(m => m.Groups[0].Value)
    .ToArray();

OfType < > был оценен пользователем Alex немного быстрее (и подтвержден мной).

Это кажется мне неинтересным, поскольку я бы подумал, что OfType < > должен был бы выполнить сравнение "is" и листинг (T).

Любое просветление будет оценено по поводу того, почему это так:)

Ответ 1

Мой бенчмаркинг не согласуется с вашим бенчмаркингом.

Я провел идентичный тест Алексу и получил противоположный результат. Затем я несколько раз оценил этот показатель и снова наблюдал Cast быстрее, чем OfType.

Там не так много, но я считаю, что Cast имеет край, как и должно, потому что его итератор проще. (Нет is check.)

Edit: На самом деле после некоторой дополнительной настройки мне удалось получить Cast на 50 раз быстрее, чем OfType.

Ниже приведен код теста, который дает самое большое несоответствие, которое я нашел до сих пор:

Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();

var ma = Enumerable.Range(1, 100000).Select(i => i.ToString()).ToArray();

var x = ma.OfType<string>().ToArray();
var y = ma.Cast<string>().ToArray();

for (int i = 0; i < 1000; i++)
{
    if (i%2 == 0)
    {
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
    }
    else
    {
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
    }
}
Console.WriteLine("OfType: " + sw1.ElapsedMilliseconds.ToString());
Console.WriteLine("Cast: " + sw2.ElapsedMilliseconds.ToString());
Console.ReadLine();

Твики, которые я сделал:

  • Выполнять "сгенерировать список строк" ​​один раз, в начале и "кристаллизовать" его.
  • Выполните одну из каждой операции перед началом отсчета времени - я не уверен, что это необходимо, но я думаю, что это означает, что JITTER генерирует код заранее, а не время, когда мы синхронизированы?
  • Выполнять каждую операцию несколько раз, а не только один раз.
  • Измените порядок, если это имеет значение.

На моей машине это приводит к ~ 350 мс для Cast и ~ 18000мс для OfType.

Я думаю, что самое большое различие заключается в том, что мы больше не будем считать, сколько времени займет MatchCollection, чтобы найти следующий матч. (Или, в моем коде, как долго int.ToString() принимает.) Это резко снижает отношение сигнал/шум.

Изменить: Как указано в шестизначных переменных, причиной такого огромного различия является то, что Cast будет замыкаться на короткое замыкание и не беспокоить литье отдельных элементов, если оно может отличить целую IEnumerable. Когда я переключился с использования Regex.Matches на массив, чтобы избежать измерения времени обработки регулярных выражений, я также переключился на использование чего-то, сместимого на IEnumerable<string>, и таким образом активировал это короткое замыкание. Когда я изменил свой тест, чтобы отключить это короткое замыкание, я получаю преимущество незначительное для Cast, а не массивное.

Ответ 2

OfType() должен быть медленнее, так как безопасный тип is проверяет перед фактической явной операцией литья, в то же время Cast() делает только явный приведение.

Теоретически OfType woudl будет быстрее в случае многих элементов с "неправильным типом", поэтому цикл перечисляет далее сразу после проверки is, в случае Cast() в той же коллекции вы попадете в InvalidCastException для каждого элемента "неправильного типа", поэтому это будет относительно медленнее.

Исходный код, извлеченный с использованием ILSpy:

// System.Linq.Enumerable
private static IEnumerable<TResult> OfType<TResult>(IEnumerable source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }

    foreach (object current in source)
    {
        // **Type check**
        if (current is TResult)
        {
            // **Explicit cast**
            yield return (TResult)current;
        }
    }
    yield break;
}

// System.Linq.Enumerable
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    if (enumerable != null)
    {
        return enumerable;
    }
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }

    foreach (object current in source)
    {
        // **Explicit cast only**
        yield return (TResult)current;
    }
    yield break;
}

Ответ 3

Просто отмените порядок OfType и Cast в своем методе, и вы заметите, что нет никакой разницы. Первый всегда работает быстрее второго. Это случай плохого микрообъекта.

Обертка вашего кода в цикле для их запуска в произвольном порядке:

OfType: 1224
Cast: 2815
Cast: 2961
OfType: 3010
OfType: 3027
Cast: 2987
...

И снова:

Cast: 1207
OfType: 2781
Cast: 2930
OfType: 2964
OfType: 2964
OfType: 2987
...

Извлеките Regex.Matches, что вызывает проблему:

Cast: 1247
OfType: 210
OfType: 170
Cast: 171
...

и

OfType: 1225
Cast: 202
OfType: 171
Cast: 192
Cast: 415

Итак, нет. OfType не быстрее, чем Cast. И нет, Cast не быстрее, чем OfType.

Ответ 4

Фактически isof() сначала проверяет тип, а затем отбрасывает его, когда cast() просто выполняет вторую часть. Очевидно, что isof() будет медленнее, чем прямое литье

http://codenets.blogspot.in/2010/06/cast-vs-oftype.html