Производительность при поиске символа в строке C#
От: mr Pink  
Дата: 31.03.15 16:27
Оценка: 15 (1) :))) :))) :)))
Имею три банальных варианта подсчёта символа в строке на C#.
Помогите понять, почему третий, самый "тупой" способ "в лоб" работает быстрее всего.

        result = inputString.Count(x => x == selector);


    inputString.AsParallel().ForAll(x =>
    {
        if (x == selector)
            lock (lockObj)
            {
                result++;
            }
    });


    for (var i = 0; i < inputString.Length; i++)
    {
        if (inputString[i] == selector)
        lock (lockObj)
        {
            result++;
        }
    }
c# .net
Re: Производительность при поиске символа в строке C#
От: tlp  
Дата: 31.03.15 17:04
Оценка:
Здравствуйте, mr Pink, Вы писали:

MP>Имею три банальных варианта подсчёта символа в строке на C#.

MP>Помогите понять, почему третий, самый "тупой" способ "в лоб" работает быстрее всего.
MP>[code]

Как меряли скорость?

P.S. Если вы уберете lock, он будет работать еще быстрее
Re[2]: Производительность при поиске символа в строке C#
От: mr Pink  
Дата: 31.03.15 17:10
Оценка: :)
Здравствуйте, tlp, Вы писали:

tlp>Как меряли скорость?

Через System.Diagnostics.Stopwatch

tlp>P.S. Если вы уберете lock, он будет работать еще быстрее

А смысл?
Re[3]: Производительность при поиске символа в строке C#
От: tlp  
Дата: 31.03.15 17:22
Оценка:
Здравствуйте, mr Pink, Вы писали:

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


tlp>>Как меряли скорость?

MP>Через System.Diagnostics.Stopwatch
покажите, пожалуйста, весь код для тестирования скорости.

tlp>>P.S. Если вы уберете lock, он будет работать еще быстрее

MP>А смысл?
Вам не нужно, чтобы быстрее?
Re[4]: Производительность при поиске символа в строке C#
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 31.03.15 17:33
Оценка:
Здравствуйте, tlp, Вы писали:

tlp>Вам не нужно, чтобы быстрее?


Главное вместе с водой ребёнка не выплеснуть В смысле заменить ++ на соответствующую атомарную операцию.
[КУ] оккупировала армия.
Re[5]: Производительность при поиске символа в строке C#
От: Jack128  
Дата: 31.03.15 17:51
Оценка:
Здравствуйте, koandrew, Вы писали:

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


tlp>>Вам не нужно, чтобы быстрее?


K>Главное вместе с водой ребёнка не выплеснуть В смысле заменить ++ на соответствующую атомарную операцию.

а в третьем примере к result еще из других потоков обращаются?? Из кода не видно. А если не обращаются, то и лок не нужен
Re[6]: Производительность при поиске символа в строке C#
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 31.03.15 18:10
Оценка:
Здравствуйте, Jack128, Вы писали:

J>а в третьем примере к result еще из других потоков обращаются?? Из кода не видно. А если не обращаются, то и лок не нужен


Ну я исхожу из того, что он там не для красоты стоит. Хотя при длинной входной строке думаю второй вариант быстрее будет, и там либо лок, либо атомарный ++ необходим.
[КУ] оккупировала армия.
Re[7]: Производительность при поиске символа в строке C#
От: Jack128  
Дата: 31.03.15 18:28
Оценка:
Здравствуйте, koandrew, Вы писали:

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


J>>а в третьем примере к result еще из других потоков обращаются?? Из кода не видно. А если не обращаются, то и лок не нужен


K>Ну я исхожу из того, что он там не для красоты стоит.

Ну если не для красоты, то для чего???

K>Хотя при длинной входной строке думаю второй вариант быстрее будет, и там либо лок, либо атомарный ++ необходим.

выделенное очень спорно. я бы вообще сказал, что на любых строках второй вариант будет медленнее третьего (без лока)
Re[6]: Производительность при поиске символа в строке C#
От: mr Pink  
Дата: 31.03.15 18:29
Оценка:
Здравствуйте, Jack128, Вы писали:

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


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


Так. Друзья, вы на свои вопрос отвечаете.
Вопрос чисто академический и попытка разобраться на базовом примере "как это работает"

var resultList = new List<string>();
int result=0;
var lockObj = new object();
var timer = new System.Diagnostics.Stopwatch();
var inputString = @"<длинная строчка>";
for (var i = 0; i < 20; i++)
{
    timer.Start();
        <метод>
    timer.Stop();
    resultList.Add(result.ToString()+" - "+timer.Elapsed.TotalMilliseconds);
    timer.Reset();
    result = 0;
}
resultList.ForEach(x => Console.WriteLine(x));
Console.ReadKey();
Re[8]: Производительность при поиске символа в строке C#
От: mr Pink  
Дата: 31.03.15 18:41
Оценка:
Здравствуйте, Jack128, Вы писали:

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


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


J>>>а в третьем примере к result еще из других потоков обращаются?? Из кода не видно. А если не обращаются, то и лок не нужен


K>>Ну я исхожу из того, что он там не для красоты стоит.

J>Ну если не для красоты, то для чего???

K>>Хотя при длинной входной строке думаю второй вариант быстрее будет, и там либо лок, либо атомарный ++ необходим.

J>выделенное очень спорно. я бы вообще сказал, что на любых строках второй вариант будет медленнее третьего (без лока)

Уберите локи, чтоб не заострять на них внимание. Всё равно распределение inputString.AsParallel > inputString.Count > цикл
Re[8]: Производительность при поиске символа в строке C#
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 31.03.15 18:43
Оценка:
Здравствуйте, Jack128, Вы писали:

J>выделенное очень спорно. я бы вообще сказал, что на любых строках второй вариант будет медленнее третьего (без лока)

Да, вы правы. Я только что проверил:
  Код
class Program
    {

        private static int Method1(string str, char symbol)
        {
            var result = 0;
            str.AsParallel().ForAll(x =>
            {
                if (x == symbol)
                {
                    Interlocked.Add(ref result, 1);
                    //result++;
                }
            });
            return result;
        }

        private static int Method2(string str, char symbol)
        {
            var result = 0;
            Action<char> lambda = x =>
            {
                if (x == symbol)
                {
                    //Interlocked.Add(ref result, 1);
                    result++;
                }
            };
            foreach (var @char in str)
            {
                lambda(@char);
            }
            return result;
        }

        static void Main(string[] args)
        {
            string str = File.ReadAllText("Толстой Лев. Война и мир. Том 1 - royallib.ru.txt");
            var res1 = Method1(str, 'a');
            var res2 = Method2(str, 'a');
            var sw = new Stopwatch();

            sw.Restart();
            res1 = Method1(str, 'a');
            sw.Stop();
            Console.WriteLine("Result {0}, time {1}", res1, sw.ElapsedMilliseconds);
            sw.Restart();
            res2 = Method2(str, 'a');
            sw.Stop();
            Console.WriteLine("Result {0}, time {1}", res1, sw.ElapsedMilliseconds);
            //1.ToString();
        }
    }

Result 1920, time 9
Result 1920, time 2


Заодно выяснил, что в тексте первого тома "Войны и мир" английская буква "а" встречается ровно 1920 раз
[КУ] оккупировала армия.
Отредактировано 31.03.2015 18:46 koandrew . Предыдущая версия .
Re: Производительность при поиске символа в строке C#
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 31.03.15 18:48
Оценка: 6 (1) +2
Здравствуйте, mr Pink, Вы писали:

MP>Имею три банальных варианта подсчёта символа в строке на C#.

MP>Помогите понять, почему третий, самый "тупой" способ "в лоб" работает быстрее всего.

Welcome to the real world, Luke!

Этот вопрос отлично демонстрирует, что надо очень хорошо понимать ЧТО стоит за модными конструкциями.

И, та-ки, первый вариант можно сделать абсолютно равным по перфомансу третьему (по модулю lock'а) написав нужный extension
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[2]: Производительность при поиске символа в строке C#
От: mr Pink  
Дата: 31.03.15 19:02
Оценка:
Здравствуйте, xvost, Вы писали:

X>Здравствуйте, mr Pink, Вы писали:


MP>>Имею три банальных варианта подсчёта символа в строке на C#.

MP>>Помогите понять, почему третий, самый "тупой" способ "в лоб" работает быстрее всего.

X>Welcome to the real world, Luke!


X>Этот вопрос отлично демонстрирует, что надо очень хорошо понимать ЧТО стоит за модными конструкциями.


X>И, та-ки, первый вариант можно сделать абсолютно равным по перфомансу третьему (по модулю lock'а) написав нужный extension


Хм. Я нашёл, как сравнять второй и третий (AsParallel и простой цикл). Нужно только привести строку в ToList заранее, ведь это делает AsPаrallel на каждой итерации.

Но вот над Count нужно колдовать как-то иначе. Все равно проигрывает примерно в 10 раз. Какой Extension?
Re: Производительность при поиске символа в строке C#
От: Evgeny.Panasyuk Россия  
Дата: 31.03.15 19:06
Оценка: 22 (4)
Если хочется параллельно, то не нужно делать синхронизацию на каждой итерации (пусть даже если это atomic) — в каждом потоке пусть будет свой счётчик, а в конце сложить их в один. Причём эти счётчики нужно разнести по разным cache-line, чтобы избежать false-sharing.
Думаю для C# должен быть уже готовый parallel reduce/accumulate, который сделает всё это автоматом.

Что же касается сильного abstraction penalty — то это же C#, от этого penalty никуда не деться. Если же хочется использовать высокоуровневые конструкции, и при этом получать код аналогичный по скорости низкоуровневым "ручным циклам" — то нужно брать C++
Re[7]: Производительность при поиске символа в строке C#
От: tlp  
Дата: 31.03.15 19:42
Оценка:
Здравствуйте, mr Pink, Вы писали:

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


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


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


MP>Так. Друзья, вы на свои вопрос отвечаете.

MP>Вопрос чисто академический и попытка разобраться на базовом примере "как это работает"

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

.Count работает медленнее скорее всего потому что оптимизатор не смог достаточно хорошо его оптимизировать и он развернулся в создание списков из строки и вызов дополнтелньых функций вместо их инлайнинга.

Трретий пример лишен всех перечисленых недостатков
Re[3]: Производительность при поиске символа в строке C#
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 31.03.15 22:09
Оценка:
Здравствуйте, mr Pink, Вы писали:

MP>Но вот над Count нужно колдовать как-то иначе. Все равно проигрывает примерно в 10 раз. Какой Extension?


public static int Count (this string str, Predicate<char> condition) {....}

Сорри за синтаксис, 3 года на C# не писал
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[9]: Производительность при поиске символа в строке C#
От: Sinix  
Дата: 01.04.15 06:23
Оценка: :)
Здравствуйте, koandrew, Вы писали:

K>Да, вы правы. Я только что проверил:


Благородные доны пытаются обогнать vs string.IndexOf()? (с вычеркнутым не прав, см ниже)
           For:   111ms, ips:  26 999 662,50 | Mem: 8,00 kb, GC0: 0 => 3000000
       IndexOf:    89ms, ips:  33 609 303,06 | Mem: 8,00 kb, GC0: 0 => 3000000

      ForShort:    21ms, ips: 472 929 514,59 | Mem: 8,00 kb, GC0: 0 => 10000000
  IndexOfShort:    63ms, ips: 157 367 803,18 | Mem: 8,00 kb, GC0: 0 => 10000000
Done.

  код
    public static void Main(string[] args)
    {
        const int RepeatCount = 100 * 1000;
        const int RepeatCountShort = 10 * 1000 * 1000;
        string longString = string.Join("", Enumerable.Range(0, 100).Select(i => Path.GetRandomFileName()));
        string shortString = "a";

        Measure("For", () =>
        {
            long result = 0;
            for (int i = 0; i < RepeatCount; i++)
            {
                result += Count(longString, 'a');
            }
            return result;
        });
        Measure("IndexOf", () =>
        {
            long result = 0;
            for (int i = 0; i < RepeatCount; i++)
            {
                result += CountFast(longString, 'a');
            }
            return result;
        });
        Console.WriteLine();
        Measure("ForShort", () =>
        {
            long result = 0;
            for (int i = 0; i < RepeatCountShort; i++)
            {
                result += Count(shortString, 'a');
            }
            return result;
        });
        Measure("IndexOfShort", () =>
        {
            long result = 0;
            for (int i = 0; i < RepeatCountShort; i++)
            {
                result += CountFast(shortString, 'a');
            }
            return result;
        });

        Console.WriteLine("Done.");
        Console.ReadKey();
    }

    static int Count(string inputString, char selector)
    {
        int result = 0;
        for (var i = 0; i < inputString.Length; i++)
        {
            if (inputString[i] == selector)
                result++;
        }
        return result;
    }

    static int CountFast(string inputString, char selector)
    {
        int result = 0;
        int index = 0;
        while (index < inputString.Length)
        {
            index = inputString.IndexOf(selector, index);
            if (index < 0)
            {
                break;
            }
            else
            {
                result++;
                index++;
            }
        }
        return result;
    }

    static void Measure(string name, Func<long> callback)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var mem = GC.GetTotalMemory(true);
        var gc = GC.CollectionCount(0);

        var sw = Stopwatch.StartNew();
        var result = callback();
        sw.Stop();

        var mem2 = GC.GetTotalMemory(false);
        var gc2 = GC.CollectionCount(0);

        var memDelta = (mem2 - mem) / 1024.0;
        var gcDelta = gc2 - gc;

        Console.WriteLine(
            "{0,14}: {1,5}ms, ips: {2,14:N} | Mem: {3,4:N2} kb, GC0: {4} => {5,6}",
            name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta, result);
    }


UPD Умудрился померять debug-версию В релизной разрыв значительно меньше, что логично. И на коротких строках IndexOf проигрывает всухую.

Мораль: сначала проснуться, затем мерять.
Отредактировано 01.04.2015 6:55 Sinix . Предыдущая версия .
Re[9]: Производительность при поиске символа в строке C#
От: icWasya  
Дата: 01.04.15 07:20
Оценка:
Здравствуйте, koandrew, Вы писали:

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


J>>выделенное очень спорно. я бы вообще сказал, что на любых строках второй вариант будет медленнее третьего (без лока)

K>Да, вы правы. Я только что проверил:
K>
  Код
K>
class Program
K>    {

K>        private static int Method1(string str, char symbol)
K>        {
K>            var result = 0;
K>            str.AsParallel().ForAll(x =>
K>            {
K>                if (x == symbol)
K>                {
K>                    Interlocked.Add(ref result, 1);
K>                    //result++;
K>                }
K>            });
K>            return result;
K>        }

K>        private static int Method2(string str, char symbol)
K>        {
K>            var result = 0;
K>            Action<char> lambda = x =>
K>            {
K>                if (x == symbol)
K>                {
K>                    //Interlocked.Add(ref result, 1);
K>                    result++;
K>                }
K>            };
K>            foreach (var @char in str)
K>            {
K>                lambda(@char);
K>            }
K>            return result;
K>        }

K>        static void Main(string[] args)
K>        {
K>            string str = File.ReadAllText("Толстой Лев. Война и мир. Том 1 - royallib.ru.txt");
K>            var res1 = Method1(str, 'a');
K>            var res2 = Method2(str, 'a');
K>            var sw = new Stopwatch();

K>            sw.Restart();
K>            res1 = Method1(str, 'a');
K>            sw.Stop();
K>            Console.WriteLine("Result {0}, time {1}", res1, sw.ElapsedMilliseconds);
K>            sw.Restart();
K>            res2 = Method2(str, 'a');
K>            sw.Stop();
K>            Console.WriteLine("Result {0}, time {1}", res1, sw.ElapsedMilliseconds);
K>            //1.ToString();
K>        }
K>    }
K>

K>

Result 1920, time 9
K>Result 1920, time 2


K>Заодно выяснил, что в тексте первого тома "Войны и мир" английская буква "а" встречается ровно 1920 раз

Только там не английская, а французская
Re[10]: Производительность при поиске символа в строке C#
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 01.04.15 11:56
Оценка:
Здравствуйте, icWasya, Вы писали:

W>Только там не английская, а французская


Они одинаковые
[КУ] оккупировала армия.
Re[2]: Производительность при поиске символа в строке C#
От: Doom100500 Израиль  
Дата: 02.04.15 07:04
Оценка:
Здравствуйте, xvost, Вы писали:

X>Здравствуйте, mr Pink, Вы писали:


MP>>Имею три банальных варианта подсчёта символа в строке на C#.

MP>>Помогите понять, почему третий, самый "тупой" способ "в лоб" работает быстрее всего.

X>Welcome to the real world, Luke!


X>Этот вопрос отлично демонстрирует, что надо очень хорошо понимать ЧТО стоит за модными конструкциями.


Можно, я буду тебя цитировать?
Спасибо за внимание
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.