Несколько дней назад было опубликовано N статей, в которых говорилось, что c#/.net слишком медленный, и требовалось удалить некоторые функции c#/.net.
Независимо от этих статей, похоже, что в отрасли существует железное правило, согласно которому C#/.net работает медленно. Как бы все ни доказывали, что C#/.net ненамного медленнее, чем C++, производительность на уровне приложений все равно очень низкая. .
Так где же C#/.net медленный?
К сожалению, большинство программ на C# замедляются. С этим выводом нелегко согласиться, но он широко распространен.
Строковые операции
Почти во всех программах есть операции со строками, и по крайней мере в 90% из них необходимо игнорировать сравнения регистров. Проверьте код. По крайней мере половина из них имеет код, подобный этому:
если (str1.ToUpper() == str2.ToUpper())
Или версия ToLower, я даже видел, что есть Web HttpModule, который говорит:
for (int i = 0; i <strs.Count; i++)
если (value.ToUpper() == strs[i].ToUpper())
//...
Подумайте об этом, каждый раз, когда запрашивается страница, должен выполняться такой фрагмент кода для создания экземпляров строк в больших участках. Еще более преувеличено то, что некоторые люди говорят, что это замена пространства на время. . .
Тестирование производительности
Если этот метод считается медленным, некоторые люди могут не признать этого и подумать, что это лучший метод, поэтому здесь нам нужно использовать специальные тесты, чтобы доказать этот факт.
Сначала подготовьте метод для проверки производительности:
частный статический TResult MeasurePerformance<TArg, TResult>(Func<TArg, TResult> func, TArg arg, цикл int)
{
GC.Собрать();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
Результат TResult = по умолчанию (TResult);
Секундомер sw = Stopwatch.StartNew();
for (int я = 0; я <цикл; я++)
{
результат = функция (аргумент);
}
Console.WriteLine(sw.ElapsedMilliсекунды.ToString() + "мс");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
вернуть результат;
}
Затем подготовьте строку кучи:
частный статический список<строка> CreateStrings()
{
List<string> strs = новый List<string>(10000);
символ [] chs = новый символ [3];
для (int я = 0; я <10000; я++)
{
интервал j = я;
for (int k = 0; k < chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j/26;
}
strs.Add(новая строка(chs));
}
вернуть строки;
}
Затем давайте посмотрим на реализацию ToUpper:
частный статический bool ImplementByToUpper (List<string> strs, строковое значение)
{
for (int i = 0; i <strs.Count; i++)
если (value.ToUpper() == strs[i].ToUpper())
вернуть истину;
вернуть ложь;
}
Наконец подготовьте основной метод:
List<string> strs = CreateStrings();
логический результат;
Console.WriteLine("Использовать ImplementByToUpper");
result = MeasurePerformance(s => ImplementByToUpper(strs, s), "йЖ", 1000);
Console.WriteLine("Результат: " + result.ToString());
Консоль.ReadLine();
Посмотрим на результаты выполнения:
Используйте ImplementByToUpper
2192 мс
ГК 0:247
ГК 1:0
ГК 2:0
результат верный
Давайте проведем сравнительный тест и воспользуемся string.Equals для проверки:
Private static bool ImplementByStringEquals (List<string> strs, строковое значение)
{
for (int i = 0; i <strs.Count; i++)
if (string.Equals(value, strs[i], StringComparison.CurrentCultureIgnoreCase))
вернуть истину;
вернуть ложь;
}
Посмотрим на результаты выполнения:
Используйте ImplementByStringEquals.
1117 мс
ГК 0:0
ГК 1:0
ГК 2:0
результат верный
Для сравнения, использование ToUpper в два раза медленнее и имеет много объектов мусора поколения 0. Те, кто утверждает, что обменивают пространство на время, могут задуматься над этим. Что они получили в обмен на пространство? Негативное время?
Использование класса словаря
Продолжая рассматривать строковый сценарий, некоторые люди могут подумать об использовании хеш-таблиц и других подобных структур для ускорения. Да, это хорошая идея, но хеш-таблицы не всегда являются лучшим решением. Почему вы в это не верите? Давайте проведем тест:
Private static bool ImplementByHashSet (List<string> strs, строковое значение)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
вернуть set.Contains(значение);
}
Взгляните на результаты выполнения:
Используйте ImplementByHashSet.
5114 мс
ГК 0:38
ВБ 1:38
ВБ 2:38
результат верный
Удивительно, но скорость более чем в два раза медленнее, чем при использовании ToUpper, а сбор мусора второго поколения также собирается 38 раз (при выполнении сборки мусора второго поколения сборка мусора первого и нулевого поколений будет принудительной).
Однако идея использования Hash-таблицы или чего-то подобного для ускорения процесса — очень правильная идея, но предпосылка состоит в том, что саму Hash-таблицу можно кэшировать, например:
частная статическая Func<string, bool> ImplementByHashSet2(List<string> strs)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
вернуть набор. Содержит;
}
Затем измените основной метод так:
Console.WriteLine("Использовать ImplementByHashSet2");
результат = MeasurePerformance(s =>
{
вар f = ImplementByHashSet2 (strs);
bool ret = ложь;
для (int я = 0; я <1000; я++)
{
врет = е (ы);
}
вернуть возврат;
}, "йЖ", 1);
Console.WriteLine("Результат: " + result.ToString());
Консоль.ReadLine();
Давайте посмотрим на результаты:
Используйте ImplementByHashSet2
6 мс
ГК 0:0
ГК 1:0
ГК 2:0
результат верный
Производительность резко возросла.
Более
Что замедляет работу C#/.net? Проще говоря: ненужное создание объектов, ненужная синхронизация, неэффективные методы выполнения цикла (такие как рефлексия, которую раскритиковал firelong, но ms не позволяет использовать Invoke в цикле), использование неэффективных структур данных и алгоритма (см. на потрясающую производительность подобной структуры Хэш-таблицы в случае кэширования, и вы почувствуете разницу)
Низкий порог C#/.net действительно помогает в определенной степени привлечь больше программистов к C#/.net, но он также значительно снижает уровень кода всей программы C#/.net, что действительно впечатляет.
Наконец, не забывайте, что производительность системы определяется не самой эффективной ее частью, а самой худшей. Оснащенный 16 ГБ памяти, 100-гигабайтным жестким диском, а также первоклассной видеокартой, но без процессора 386, производительность этого компьютера равна производительности 386. Аналогично, независимо от того, насколько хорош C#/.net, если навыки программиста плохи, производительность написанной программы, естественно, будет низкой.