static void AssertState(bool condition, string message)
{
if (!condition)
{
throw new InvalidOperationException(message);
}
}
static void AssertState(bool condition, IFormattable message)
{
if (!condition)
{
throw new InvalidOperationException(message.ToString());
}
}
static void AssertStateMessage(bool condition, string message)
{
if (!condition)
{
throw new ArgumentException(message);
}
}
static void AssertStateMessage(bool condition, string messageFormat, params object[] args)
{
if (!condition)
{
throw new ArgumentException(string.Format(messageFormat, args));
}
}
static void AssertStateFunc(bool condition, Func<string> messageCallback)
{
if (!condition)
{
throw new InvalidOperationException(messageCallback());
}
}
static void Main(string[] args)
{
const int Count = 10 * 1000 * 1000;
Measure("Concatenation", () =>
{
for (int i = 0; i < Count; i++)
{
AssertState(true, "Message '" + i + "': " + i + "." + i + "!");
}
return Count;
});
Measure("Interpolation", () =>
{
for (int i = 0; i < Count; i++)
{
AssertState(true, $"Message '{i}':{i}.{i}!");
}
return Count;
});
Measure("String", () =>
{
for (int i = 0; i < Count; i++)
{
AssertState(true, "Message #0");
}
return Count;
});
Measure("String.Format", () =>
{
for (int i = 0; i < Count; i++)
{
AssertStateMessage(true, "Message '{0}':{0}.{0}!", i);
}
return Count;
});
Console.WriteLine();
Console.WriteLine("Done.");
}
static void Measure(string name, Func<long> callback)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var mem = GC.GetTotalMemory(true);
var gc00 = GC.CollectionCount(0);
var gc01 = GC.CollectionCount(1);
var gc02 = GC.CollectionCount(2);
var sw = System.Diagnostics.Stopwatch.StartNew();
var result = callback();
sw.Stop();
var mem2 = GC.GetTotalMemory(false);
var gc10 = GC.CollectionCount(0);
var gc11 = GC.CollectionCount(1);
var gc12 = GC.CollectionCount(2);
var memDelta = (mem2 - mem) / 1024.0;
var gcDelta0 = gc10 - gc00;
var gcDelta1 = gc11 - gc01;
var gcDelta2 = gc12 - gc02;
Console.WriteLine(
"{0,20}: {1,5}ms, ips: {2,22:N} | Mem: {3,9:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
}
Всё почти как в первоисточнике, разница лишь в том, что я делаю конкатенацию/форматирование с несколькими элементами, не с одним. Но результаты у меня получаются совершенно другие:
Интерполяция, получается, медленнее даже простой конкатенации. Проверял, собирая под разные фреймворки, от 4.0 до 4.6.2, плюс dotnet core, результаты примерно одинаковые.
Что я делаю не так?
Здравствуйте, Artem Korneev, Вы писали:
AK>Интерполяция, получается, медленнее даже простой конкатенации. Проверял, собирая под разные фреймворке, от 4.0 до 4.6.2, плюс dotnet core, результаты примерно одинаковые. AK>Что я делаю не так?
Да всё так, интерполяция ~= string.Format().
В исходном посте проблема в другом. Цитата:
В чём проблема: рекомендации решарпера заменяют второй вариант на Code.AssertState(someCondition, $"Message #{123}");
, компилятор превращает эту строчку в Code.AssertState(someCondition, string.Format("Message #{0}", 123));
т.е. форматирование строки происходит всегда.
Все остальные методы (кроме явного concat) делают форматирование лениво. Как пример:
static void AssertStateMessage(bool condition, string messageFormat, params object[] args)
{
if (!condition)
{
throw new ArgumentException(string.Format(messageFormat, args));
}
}
Во всех вызовах condition == true, т.е. форматирование пропускается. остаются только накладные расходы на передачу параметров (в примере — массива args).
Re[2]: Производительность работы со строками. Что я делаю не
Здравствуйте, Sinix, Вы писали:
S>Да всё так, интерполяция ~= string.Format(). S>В исходном посте проблема в другом.
Да, это я помню, но меня сейчас интересовало именно сравнение производительности разных вариантов работы со строками.
S>Во всех вызовах condition == true, т.е. форматирование пропускается.
Во! Вот тут я и ошибся. Потому и получалось, что форматирование у меня быстрее работало — оно ж просто пропускалось.
Но теперь у меня получается, что простая конкатенация быстрее, чем любое форматирование, хоть и жрёт чуть больше памяти, чем интерполяция (но меньше, чем форматирование):
Здравствуйте, Artem Korneev, Вы писали:
AK>Но теперь у меня получается, что простая конкатенация быстрее, чем любое форматирование
Оно так и должно быть. Конкатенация вызывает string.Concat, дальше под капотом или частный случай (до четырёх аргументов), или магия с StringBuilderCache.
См https://referencesource.microsoft.com/#mscorlib/system/string.cs,3017
StringFormat и ко заметно сложнее и предполагает как минимум разбор строки форматирования.
UPD: на самом деле c string.Concat всё немного сложнее, вчера лень было расписывать. Если коротко — есть две группы перегрузок string.Concat:
* с 2..4 аргументами типа string + перегрузка, которая принимает массив string
* с 2..3 аргументами типа object + перегрузка, которая принимает массив object
В теории любое сложение с пятью строками или больше приводит к аллокации массива,
любое сложение 4 (и больше) произвольных значений, одно из которых строка — то же самое + возможный boxing слагаемых-структур (custom operators не рассматриваем).
На практике всё немножко сложнее, например:
string s = " ";
int i = 0;
char c = ' ';
string r;
r = "Hello," + " " + "world" + "!"; // a = "Hello, world!"
r = "Hello," + s + "world" + "!"; // a = string.Concat<string>("Hello,", s, " world!")
r = "Hello," + s + "world" + s; // a = string.Concat<string>("Hello,", s, " world", s)
r = "Hello," + c + "world" + "!"; // a = string.Concat<string>("Hello,", c.ToStirng(), " world!")
r = "Hello," + c + "world" + c; // a = string.Concat<string>("Hello,", c.ToStirng(), " world!", c.ToStirng())
r = "Hello," + i + "world" + "!"; // a = string.Concat<object>("Hello,", i, " world!")
r = "Hello," + i + "world" + i; // a = string.Concat<object>(new object[] { "Hello,", i, " world", i })
Console.WriteLine(r);
компилятор не стесняется объединять литералы-константы и использовать более эффективный вариант с строками как минимум для char.
Здравствуйте, Sinix, Вы писали:
S> r = "Hello," + i + "world" + "!"; // a = string.Concat<object>("Hello,", i, " world!")
Вот здесь момент заинтересовал: с какого перепоя целая переменная (для которой, очевидно, должна быть своя эффективная конвертация в строку) сначала боксится, затем передаётся бог знает куда, там разбоксивается, узнаётся, что это целое, затем только конвертируется в строку и после этого конкатенатится! Получается слишком много бестолковой работы ради единственной цели — вызывать для любых типов Concat<object>(). Я считаю это безобразнейшей реализацией компилятора.
Конкатенация (раз уж была выведена на уровень языка) обязана проверять типы аргументов и макимально эффективно строить вызов:
var s = "Hello, " + i + "th warrior!"; // => Concat("Hello, ", i.ToString(), "th warrior!")
На мой взгляд, это самое логичное и эффективное решение.
Re[5]: Производительность работы со строками. Что я делаю не
Здравствуйте, Kolesiki, Вы писали:
K>Получается слишком много бестолковой работы ради единственной цели — вызывать для любых типов Concat<object>(). Я считаю это безобразнейшей реализацией компилятора.
Тёмное наследие первого шарпа. Предложения поправить есть, но будет это не в ближайшем релизе, т.к. не особенно актуально.
Если интересно — могу накидать ссылок про конкретные варианты.
K>Конкатенация (раз уж была выведена на уровень языка) обязана проверять типы аргументов и макимально эффективно строить вызов: K>На мой взгляд, это самое логичное и эффективное решение.
Угу, это один из них