В процессе переноса кода с FW 3.5 SP1 на FW 4+, заметил на некоторых фрагментах существенную просадку производительности.
Проблема выявилась в неожиданном месте, а именно на строковых операциях наподобие String.IndexOf, которые данный код активно использует.
Псевдо-код для демонстрации проблемы:
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
StrBench(100000);
}
static void StrBench(int arrSize)
{
const int cnt = 10;
List<string> arr = new List<string>(arrSize);
for (int j = 0; j < arrSize; j++)
arr.Add(string.Format("{0}{1}{2}", new string('x', 100), BuildKey(j), new string('x', 100)));
string key = BuildKey(arrSize - 1);
int c = 0;
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < cnt; i++)
c += arr.Count(s => s.IndexOf(key) > -1);
double ts = sw.Elapsed.TotalSeconds;
ulong total = (ulong)arrSize * (ulong)cnt;
Console.WriteLine("String ({3}) {0} op / {1:0.000} sec = {2:0}", total, ts, total / ts, arrSize);
}
static string BuildKey(int i)
{
return i.ToString().PadLeft(10, '0');
}
}
Билдим: Release (обязательно!)|Any CPU отдельно под FW 3.5 и FW 4 (я делал под 4.0 и 4.7.2)
т.е. производительность в 3.5 раза больше на 4-х ядрах (под FW 4+), и при этом всего в 1.3 раза, чем на одном ядре под FW 3.5 SP1.
Умозаключения и прочая философия
Вычислительная производительность одного ядра уже давно не сильно растет, если сравнить мою железку 5-ти летней давности (i5-6500) с железкой 10-ти летней давности (Core i3-540 на 3.06 Ггц) то получается, приблизительно в 1.5 раза в окрестностях почти равных частот.
На одном ядре профит (без учета увеличения частоты) обычно можно получить за счет более быстрого и емкого кэша и оптимизации конвейера.
В виду вышесказанного, всё это выглядит непредумышленной диверсией со стороны разработчиков FW, т.к. регресс производительности, некоторых часто-используемых операций при переходе на FW 4+ существенно превышает многолетний прогресс по IPC на одно ядро.
С уменьшением техпроцесса по-прежнему, увеличивается энергоэффективность и кол-во ядер, за счет чего можно повысить производительность, но не все ведь можно распараллелить, и не всегда это легко.
В данном случае код местами переписан, проблемы нет (плюс профит от распараллеливания средствами LINQ), но беспокоит сам факт, что пришлось это делать.
P.S. Под .NET пишу с 1-й беты (2001 год), до этого C/C++ под x86, а еще ранее ASM под 3 МГЦ-овые 8-ми битки — i8080 (Z80) ...
Re: Регресс производительности при переходе с FW 3.5 SP1 на FW 4+ (String.IndexO
Здравствуйте, Serginio1, Вы писали:
S>Ща .Net Core 3.1 и там говорят скорость выше. Кроме того можно еще попробовать .Net Native из UWP
Обновил VS 2019 до версии 16.5.5, сбилдил и запустил этот "тест" под Core 3.1 — результат аналогичен Core 2.1.
offtop:
Как это можно понимать, более 10-ка лет .NET FW "нашпиговывают" множеством сомнительных плюшек в плане повседневной практичности, при этом местами "отламывают" такую важную для повседневности вещь как производительность, причем в самых неожиданных местах.
Можно понять, когда в 2.0 мы получили параметризованные типы и анонимные делегаты, в 3.0 лямбда-выражения, это все было реально нужно, библиотеки и языки органично развивались по большей части в правильном направлении.
Конечно для обработки "офисных" документов (PDF/Excel/Word), различных конвертеров, компрессии, драйверов к различным БД, криптографии/ЭЦП, да и много другого приходится "тащить" сторонние библиотеки (т.к. либо чего-то просто нет, либо есть — но очень плохо), но я не замечал при постепенном переходе от 1.0 до 3.5 SP1 потери производительности ранее написанного кода.
В расчет не берем средства разработки, которые все меньше задумывались о трате ресурсов.
Ну а говорить про то, как безобразно выжирают вычислительный ресурс JavaScript (который уже лет 10 пытаются воткнуть везде где ему совсем не место) и прочие Python-ы даже не стоит.
Здравствуйте, 4058, Вы писали:
4>Здравствуйте, Serginio1, Вы писали:
S>>Ща .Net Core 3.1 и там говорят скорость выше. Кроме того можно еще попробовать .Net Native из UWP
4>Обновил VS 2019 до версии 16.5.5, сбилдил и запустил этот "тест" под Core 3.1 — результат аналогичен Core 2.1.
4>offtop: 4>Как это можно понимать, более 10-ка лет .NET FW "нашпиговывают" множеством сомнительных плюшек в плане повседневной практичности, при этом местами "отламывают" такую важную для повседневности вещь как производительность, причем в самых неожиданных местах.
Здравствуйте, 4058, Вы писали:
4>Доброго времени суток.
4>Краткое предисловие:
4>В процессе переноса кода с FW 3.5 SP1 на FW 4+, заметил на некоторых фрагментах существенную просадку производительности. 4>Проблема выявилась в неожиданном месте, а именно на строковых операциях наподобие String.IndexOf, которые данный код активно использует.
Все дело в используемой версии таблицы Unicode. В версии .NET3.5 использовался Unicode 5.0, c 4 — Unicode 5.1, с NET4.5 — Unicode 6.0 и далее, в .NET Core зависит от ОС.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[2]: Регресс производительности при переходе с FW 3.5 SP1
Здравствуйте, rameel, Вы писали:
R>Здравствуйте, 4058, Вы писали:
4>>Доброго времени суток.
4>>Краткое предисловие:
4>>В процессе переноса кода с FW 3.5 SP1 на FW 4+, заметил на некоторых фрагментах существенную просадку производительности. 4>>Проблема выявилась в неожиданном месте, а именно на строковых операциях наподобие String.IndexOf, которые данный код активно использует.
R>Все дело в используемой версии таблицы Unicode. В версии .NET3.5 использовался Unicode 5.0, c 4 — Unicode 5.1, с NET4.5 — Unicode 6.0 и далее, в .NET Core зависит от ОС.
Интересно почему тогда string.Contains дает аналогичные результаты на 3.5 и 4+, может локале-ориентированный компаратор сильно "фонит":
c += arr.Count(s => s.Contains(key));
3.5: String (100000) 1000000 op / 0,248 sec = 4030540
4.0: String (100000) 1000000 op / 0,237 sec = 4205221
Также аналогичные результаты можно получить, если явно в качестве компаратора указать StringComparison.Ordinal:
c += arr.Count(s => s.IndexOf(key, StringComparison.Ordinal) > -1);
3.5: String (100000) 1000000 op / 0,246 sec = 4053168
4.0: String (100000) 1000000 op / 0,239 sec = 4181658
Понять их конечно можно, поскольку Core пытаются пристроить на мобилки и НЕ window-ые сервера, то для лучшего впечатления производительность лучше подтянуть.
Здравствуйте, 4058, Вы писали:
4>Интересно почему тогда string.Contains дает аналогичные результаты на 3.5 и 4+, может локале-ориентированный компаратор сильно "фонит":
Здравствуйте, rameel, Вы писали:
R>Все дело в используемой версии таблицы Unicode. В версии .NET3.5 использовался Unicode 5.0, c 4 — Unicode 5.1, с NET4.5 — Unicode 6.0 и далее, в .NET Core зависит от ОС.
4>... Contains/Ordinal
R>Потому что не используется локаль
Из этого следует, что переход с Unicode 5.0 (FW 3.5) на 5.1 (FW 4.0) выдаёт регресс производительности для локалезависимых операций в 2.7 раза?
Здравствуйте, 4058, Вы писали:
4>Здравствуйте, Serginio1, Вы писали:
S>> При этом есть куча примеров, где .Net Core и .Net Native обгоняют в 5 и более раз. S>>https://devblogs.microsoft.com/dotnet/regex-performance-improvements-in-net-5/
4>Понять их конечно можно, поскольку Core пытаются пристроить на мобилки и НЕ window-ые сервера, то для лучшего впечатления производительность лучше подтянуть.
Здравствуйте, 4058, Вы писали:
4>P.S. Под .NET пишу с 1-й беты (2001 год), до этого C/C++ под x86, а еще ранее ASM под 3 МГЦ-овые 8-ми битки — i8080 (Z80) ...
Тем более странно, что ты разницу в работе одной функции проецируешь на весь .Net 4.0.
Просто напиши свою версию этой функции. Если так важна производительность, сделай это в ансэйфе.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Регресс производительности при переходе с FW 3.5 SP1
Здравствуйте, Serginio1, Вы писали:
S> Кстати вот другие бенчмарки https://habr.com/ru/post/481558/ S>На интах IndexOf
S>Method Type .NET 4.8 Core 2.1 Core 3.1 S>IndexOf Int 1.00 0.99 0.43 S>IndexOf String 1.00 0.95 0.95
S>Возьми их тесты
Мы не обсуждаем IndexOf для коллекций/массивов, речь в исходном сообщении про обработку строк (indexOf внутри строки, а не коллекции).
Приведенные тобой результаты, из примера где они гоняют IndexOf на коллекции.
Re[3]: [R#] Merge sequential checks and compilation error
Здравствуйте, 4058, Вы писали:
4>Из этого следует, что переход с Unicode 5.0 (FW 3.5) на 5.1 (FW 4.0) выдаёт регресс производительности для локалезависимых операций в 2.7 раза?
Вполне возможно, я не в курсе, что поменялось в 5.1. Вопрос лучше задать непосредственно разработчикам https://github.com/dotnet/runtime
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[2]: Регресс производительности при переходе с FW 3.5 SP1 на FW 4+ (String.Ind
Здравствуйте, rameel, Вы писали:
R>Все дело в используемой версии таблицы Unicode. В версии .NET3.5 использовался Unicode 5.0, c 4 — Unicode 5.1, с NET4.5 — Unicode 6.0 и далее, в .NET Core зависит от ОС.
Начиная с .NET Framework 2.0 с пакетом обновления 1 (SP1) и до .NET Framework 4, каждая версия .NET Framework включает таблицы, которые содержат веса сортировки и данные по нормализации строк и основаны на конкретной версии Unicode. В .NET Framework 4.5 наличие этих таблиц зависит от операционной системы:
В Windows 7 и предыдущих версиях операционной системы Windows таблицы продолжают использоваться для сравнения и упорядочивания строк.
В Windows 8 .NET Framework делегирует операции сравнения строк и упорядочения операционной системе.
Следовательно, результат сравнения строк может зависеть не только от версии .NET Framework, но и от версии операционной системы, как показано в следующей таблице. Обратите внимание, что этот список поддерживаемых версий Unicode применяется только для сравнения и сортировки символов; это не относится к классификации символов Юникода по категориям.
и солнце б утром не вставало, когда бы не было меня
Re[2]: Регресс производительности при переходе с FW 3.5 SP1
Здравствуйте, VladD2, Вы писали:
VD>Тем более странно, что ты разницу в работе одной функции проецируешь на весь .Net 4.0.
Невнимательно читаешь, во первых речь идет конкретно о String.IndexOf (в названии темы даже присутствует), и тот кусок кода просто изобилировал его использованием, и "тупой" перенос с 3.5 на 4.0 на том участке дал заметный регресс производительности, что в результате привело к негативным последствиям.
VD>Просто напиши свою версию этой функции. Если так важна производительность, сделай это в ансэйфе.
Я ранее писал:
В данном случае код местами переписан, проблемы нет (плюс профит от распараллеливания средствами LINQ), но беспокоит сам факт, что пришлось это делать.
Проблема, именно в том, что пришлось внести изменения в код при подъеме версии FW, чего я например ни разу не делал с 1.0 по 3.5 SP1
Здравствуйте, 4058, Вы писали:
4>Здравствуйте, Serginio1, Вы писали:
S>> Кстати вот другие бенчмарки https://habr.com/ru/post/481558/ S>>На интах IndexOf
S>>Method Type .NET 4.8 Core 2.1 Core 3.1 S>>IndexOf Int 1.00 0.99 0.43 S>>IndexOf String 1.00 0.95 0.95
S>>Возьми их тесты
4>Мы не обсуждаем IndexOf для коллекций/массивов, речь в исходном сообщении про обработку строк (indexOf внутри строки, а не коллекции). 4>Приведенные тобой результаты, из примера где они гоняют IndexOf на коллекции.
Ты же утвеждаешь, что 3.5 быстрее! Проверь с интами.
Начиная с .NET Framework 2.0 с пакетом обновления 1 (SP1) и до .NET Framework 4, каждая версия .NET Framework включает таблицы, которые содержат веса сортировки и данные по нормализации строк и основаны на конкретной версии Unicode. В .NET Framework 4.5 наличие этих таблиц зависит от операционной системы:
В Windows 7 и предыдущих версиях операционной системы Windows таблицы продолжают использоваться для сравнения и упорядочивания строк.
В Windows 8 .NET Framework делегирует операции сравнения строк и упорядочения операционной системе.
Следовательно, результат сравнения строк может зависеть не только от версии .NET Framework, но и от версии операционной системы, как показано в следующей таблице. Обратите внимание, что этот список поддерживаемых версий Unicode применяется только для сравнения и сортировки символов; это не относится к классификации символов Юникода по категориям.
Вот веса и версии юникоде могут быть разными и может разниться от содержимого.
Попробуй сортировку где преобладает кириллица.
и солнце б утром не вставало, когда бы не было меня
Re[8]: Регресс производительности при переходе с FW 3.5 SP1
Здравствуйте, Serginio1, Вы писали:
S> Ты же утвеждаешь, что 3.5 быстрее! Проверь с интами.
Я не просто утверждаю, а демонстрирую результаты на конкретном примере, который можно при желании у себя воспроизвести.
Также я не очень понимаю, интереса померится поиском int-а в коллекции, т.к. поиск подстроки и поиск элемента в массиве это несколько разные вещи.
S> Вот веса и версии юникоде могут быть разными и может разниться от содержимого.
Не уверен, что это причина почему производительность снижается в 2.7 раза.
S>Попробуй сортировку где преобладает кириллица.
Мой пример просто генерит 100000 строк длиной чуть более 200 символов, вида:
"XXXXXXX0000000001XXXXXXX"
далее считаем строки (Count на коллекции), в которые входит строка: "0000099999", поиск подстроки через IndexOf, чтобы показать проблему.
Поэтому смысл чего-то мерить с использованием кириллицы я не вижу.
Re[9]: Регресс производительности при переходе с FW 3.5 SP1
Здравствуйте, 4058, Вы писали:
4>Здравствуйте, Serginio1, Вы писали:
S>> Ты же утвеждаешь, что 3.5 быстрее! Проверь с интами.
4>Я не просто утверждаю, а демонстрирую результаты на конкретном примере, который можно при желании у себя воспроизвести. 4>Также я не очень понимаю, интереса померится поиском int-а в коллекции, т.к. поиск подстроки и поиск элемента в массиве это несколько разные вещи.
Ну вот ты сравни и покажи результаты. Казалось бы разницы то быть не должно
Ты демонстрируешь на отдельной задаче. Ранее тебе говорили о разнице версий юникода от версий фреймворка.
S>> Вот веса и версии юникоде могут быть разными и может разниться от содержимого.
4>Не уверен, что это причина почему производительность снижается в 2.7 раза.
S>>Попробуй сортировку где преобладает кириллица.
4>Мой пример просто генерит 100000 строк длиной чуть более 200 символов, вида:
4>"XXXXXXX0000000001XXXXXXX"
Заметь, что это строки ASCII
Добавь юникодные символы раз используешь IndexOf
public int IndexOf(string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(this, value);
}
4>далее считаем строки (Count на коллекции), в которые входит строка: "0000099999", поиск подстроки через IndexOf, чтобы показать проблему. 4>Поэтому смысл чего-то мерить с использованием кириллицы я не вижу.
А ты попробуй, в чем проблема?
и солнце б утром не вставало, когда бы не было меня
Re: Регресс производительности при переходе с FW 3.5 SP1 на FW 4+ (String.IndexO
Здравствуйте, 4058, Вы писали:
4>Доброго времени суток.
4>Краткое предисловие:
4>В процессе переноса кода с FW 3.5 SP1 на FW 4+, заметил на некоторых фрагментах существенную просадку производительности. 4>Проблема выявилась в неожиданном месте, а именно на строковых операциях наподобие String.IndexOf, которые данный код активно использует.
4> for (int i = 0; i < cnt; i++) 4> c += arr.Count(s => s.IndexOf(key) > -1);
Здравствуйте, Serginio1, Вы писали:
S> Ну вот ты сравни и покажи результаты. Казалось бы разницы то быть не должно
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
IntBench(1000000);
}
static void IntBench(int arrSize)
{
const int cnt = 100;
List<int> arr = new List<int>(arrSize);
for (int j = 0; j < arrSize; j++)
arr.Add(j);
int key = arrSize - 1;
int c = 0;
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < cnt; i++)
c += arr.Count(s => s == key);
double ts = sw.Elapsed.TotalSeconds;
ulong total = (ulong)arrSize * (ulong)cnt;
Console.WriteLine("Int ({3}) {0} op / {1:0.000} sec = {2:0}", total, ts, total / ts, arrSize);
}
}
FW 3.5: Int (1000000) 100000000 op / 0,872 sec = 114699379
FW 4.0: Int (1000000) 100000000 op / 0,990 sec = 101008734
Core31: Int (1000000) 100000000 op / 0,893 sec = 111996104
4>>Поэтому смысл чего-то мерить с использованием кириллицы я не вижу.
S> А ты попробуй, в чем проблема?
Заменил "X" на "Ё", результаты те-же, не понимаю, что должно было изменится, строки в .NET исторически UTF-16 и один символ ASCII в UTF-16 занимает 2 байта, собственно как и буква "Ё".