Результаты для 10млн итераций (тест на машине Pentium III, 850мгц):
С++ - 1692 ms (+/- 10) С# — 2433 ms (+/- 50)
При этом GC в С# тестах не срабатывал. А в С++ в это время срабатывал лишний delete.
Если изменить политику переаллоцирования буфера в C++ (см ниже)
c _allocated *= 2; на, например, _allocated *= 3;
То цифры такие: С++ - 1422 ms (+/- 10)
Вот.
Содокладчики:
Со стороны С++ — c-smile
Со стороны С# — iLYA
C#
using System;
using System.Collections;
using System.Text;
using System.IO;
namespace Benchmark_CSharp
{
class BenchmarkCSharp
{
static DateTime startTime;
static DateTime stopTime;
static TimeSpan elapsedTime;
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("Start C# benchmark");
long scTime = (long)sc2( 10000000 );
Console.WriteLine("End C# benchmark");
}
public static long sc2(int N)
{
long elapsedMilliseconds;
startTime = DateTime.Now;
if(N < 1) N = 1;
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
for (int i=0; i<N; i++)
{
if((i & 1) == 0)
sb1.Append("hello");
else
sb2.Append("hello!");
}
stopTime = DateTime.Now;
elapsedTime = stopTime.Subtract(startTime);
elapsedMilliseconds = (int)elapsedTime.TotalMilliseconds;
Console.WriteLine("String Concat. (fixed) elapsed time: " + elapsedMilliseconds + " ms");
return elapsedMilliseconds;
}
}
}
Здравствуйте, c-smile, Вы писали:
CS>Результаты для 10млн итераций (тест на машине Pentium III, 850мгц):
CS>С++ - 1692 ms (+/- 10) CS>С# — 2433 ms (+/- 50)
StringBuilder sb1 = new StringBuilder(50000000);
StringBuilder sb2 = new StringBuilder(50000000);
String Concat. (fixed) elapsed time: 1211 ms
Pentium III 1000mhz
) CS>И вербально по месту с iLYA.
CS>Два примера С# и С++ делающие следюущее:
...
А std::wstring не проверял? Кстати, компилятор надо бы поновее взять. Еще вопрос: этот примерчик можно заоптимизировать до смерти, но какой в том смысл?
Здравствуйте, alexkro, Вы писали:
A>Здравствуйте, c-smile, Вы писали:
A>А std::wstring не проверял? Кстати, компилятор надо бы поновее взять. Еще вопрос: этот примерчик можно заоптимизировать до смерти, но какой в том смысл?
std::wstring как и String в С# — immutable classes — не предназначены для динамического склеивания строк.
Собственно оптимизирования никакого не проводилось. Такая задача не стояла.
Наоборот — работать только штатными стандартными средствами платформы.
wchar_t_buffer — исп. классичекую имплементацию динамического буфера в C++.
Это не делалось через std::vector, например, по причине наличия разных альтернативных имплементаций std::
Хочу еще раз подчеркнуть что данное исследование не ставит целью ответить на глупый вопрос типа "кто лучше — папа или мама?"
А просто понять какие классы задач луше делать на каждой из платформ.
Здравствуйте, c-smile, Вы писали:
CS>>>С++ - 590 ms (+/- 0) CS>>>С# — 1205 ms (+/- 5)
CS>Хочу еще раз подчеркнуть что данное исследование не ставит целью ответить на глупый вопрос типа "кто лучше — папа или мама?" CS>А просто понять какие классы задач луше делать на каждой из платформ.
Ты считаешь, что различия в быстродействии в 1.5 — 2 раза критично для значительного числа задач?
Ну, что же... отрадно, что ты перешел от заявлений к измерениям. Пускай не все получается, но все же это уже прогресс.
Итак, для начала определимся, что конкретно мы сравниваем. Сравнивать C++ и C# довольно бессмысленно, так как это всего лишь языки. Всегда можно найти плохой компилятор С++ который проиграет C#-у. Согласен?
Значит сравнивать нам нужно все же нечто иное. Я бы охарактеризовал бы это как ".NET vs. Оптимизирующие компиляторы". В качестве эталонного компилятора возьмем реализацию VC7-8 (обладающие лучшими характеристиками на сегодня). ОК? С C#-ом все еще проще, на сегодня есть только реализации от МС. Их доступно три: 1.0, 1.1 и 1.2 (Whidbey).
Теперь о том, как мы сравниваем. Сравнивать специализированную реализацию и универсальную не корректно. Если уж мы хотим получить ответ о качестве генерации кода.
Стало быть разберем оба примера по отдельности. Сначала С++-ый. Итак я создал прокт, поместил в него твой С++-код и скомпилировал его в двух вариантах: 1) для Win32 (Release), и 2) тоже с ключом /clr. Результаты оказались следующими (у меня Atlon 2100+):
Unmanaged-вариант: 751
Managed-вариант: 811
Таким образом, разница составила 7% и ее можно лекто списать на Interop возникающий при вызове unmanaged-функции memcpy (которая к тому же в unmanaged-варианте VC вообще превращается в ассемблерные инструкции).
Теперь зайдем с другого конца. Возьмем тест на C#-е и попытаемся переписать его на С++ без ручных оптимизаций. Как бы поступил средний программист встань пред ним подобная задача? Да очень просто... он воспользовался бы готовым классом. B VC предоставляет нам такой класс — это CString. Его ATL-версия как раз имеет вариант CStringW (работающий с Юникодом). Переписываем тест:
Запускаем... дожидаемся, окончания работы программы... и делаем выводы.
Выводы не утешительны. С++ вообще не пригоден для программирования.
Обман? Несомненно!
PS
Какой же вывод можно сделать из всего этого? Да очень простой. Разница в коде порождаемом JIT-ом .NET-а/Явы и кодом порождаемом оптимизирующим компилятором С++ ничтожна. Она конечно есть, и в большинстве случаев она не в пользу .NET-а, но намного больше на работу конечного приложения влияет правильность выбора алгоритма и грамотность реализации этого алгоритма.
Намек ясен?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>но намного больше на работу конечного приложения влияет правильность выбора алгоритма и грамотность реализации этого алгоритма. VD>Намек ясен?
Говорил бы уж сразу — от радиуса кривизны
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, VladD2, Вы писали:
VD>Теперь зайдем с другого конца. Возьмем тест на C#-е и попытаемся переписать его на С++ без ручных оптимизаций. Как бы поступил средний программист встань пред ним подобная задача? Да очень просто... он воспользовался бы готовым классом. B VC предоставляет нам такой класс — это CString. Его ATL-версия как раз имеет вариант CStringW (работающий с Юникодом). Переписываем тест:...
Для тех кто в танке повторяю. class NET.String is immutable. То же самое и в stl
StringBuilder же это то что называется динамический буфер оптимизированный в первую очередь для append.
Использовать для этой цели CString/String/std::string — это даже не "средний программист", это хуже.
Или ты хочешь сказать что C# только для средних программистов?
Т.е. не надо передергивать. Работаем с динамическим буфером.
Вот исходники C#::StringBuilder::Append
Пример на С++ повторяет это с точностью до политики переаллоцирования
newCapacity = ( currentString.Capacity)*2; // To force a predicatable growth of 160,320 etc. for testing purposes
Кстати судя по скобкам там была другая формула изначально.
// Appends a copy of this string at the end of this string builder.
/// <include file='doc\StringBuilder.uex' path='docs/doc[@for="StringBuilder.Append2"]/*' />public StringBuilder Append(String value) {
//If the value being added is null, eat the null
//and return.if (value==null) {
return this;
}
int tid;
// hand inlining of GetThreadSafeString
String currentString = m_StringValue;
tid = InternalGetCurrentThread();
if (m_currentThread != tid)
currentString = String.GetStringForStringBuilder(currentString, currentString.Capacity);
int currentLength = currentString.Length;
int requiredLength = currentLength + value.Length;
if (NeedsAllocation(currentString,requiredLength)) {
String newString = GetNewString(currentString,requiredLength);
newString.AppendInPlace(value,currentLength);
ReplaceString(tid,newString);
} else {
currentString.AppendInPlace(value,currentLength);
ReplaceString(tid,currentString);
}
return this;
}
private String GetNewString(String currentString, int requiredLength) {
int newCapacity;
requiredLength++; //Include the terminating null.if (requiredLength > m_MaxCapacity) {
throw new ArgumentOutOfRangeException(Environment.GetResourceString("ArgumentOutOfRange_NegativeCapacity"),
"requiredLength");
}
newCapacity = ( currentString.Capacity)*2; // To force a predicatable growth of 160,320 etc. for testing purposesif (newCapacity<requiredLength) {
newCapacity = requiredLength;
}
if (newCapacity> m_MaxCapacity) {
newCapacity = m_MaxCapacity;
}
if (newCapacity<=0) {
throw new ArgumentOutOfRangeException(Environment.GetResourceString("ArgumentOutOfRange_NegativeCapacity"));
}
return String.GetStringForStringBuilder( currentString, newCapacity);
}
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, alexkro, Вы писали:
A>>Здравствуйте, c-smile, Вы писали:
A>>А std::wstring не проверял? Кстати, компилятор надо бы поновее взять. Еще вопрос: этот примерчик можно заоптимизировать до смерти, но какой в том смысл?
CS>std::wstring как и String в С# — immutable classes — не предназначены для динамического склеивания строк.
Кто тебе сказал, что wstring is immutable? Ссылочку на стандарт пожалуйте.
CS>Собственно оптимизирования никакого не проводилось. Такая задача не стояла. CS>Наоборот — работать только штатными стандартными средствами платформы.
Зачем тогда свой наворот на C++ писать? Штатное стандартное средство C++ — wstring.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, VladD2, Вы писали:
VD>>но намного больше на работу конечного приложения влияет правильность выбора алгоритма и грамотность реализации этого алгоритма. VD>>Намек ясен?
IT>Говорил бы уж сразу — от радиуса кривизны
Здравствуйте, c-smile, Вы писали:
CS>StringBuilder же это то что называется динамический буфер оптимизированный в первую очередь для append. CS>Использовать для этой цели CString/String/std::string — это даже не "средний программист", это хуже.
CStringT конечно-же sucks для этой цели, если посмотреть на реализацию. Но вот насчет std::string ты ошибся.
CS>Или ты хочешь сказать что C# только для средних программистов?
Я почти уверен, что и C# версию можно достаточно соптимизировать. Кстати, вопрос на засыпку: чем определяется предел оптимизации для данного примерчика?
Здравствуйте, c-smile, Вы писали:
CS>Для тех кто в танке повторяю.
То есть для тебя самого, что ли?
CS> class NET.String is immutable.
Видимо System.String или просто string. Ну, так про него речь и идет.
CS> То же самое и в stl
Про него тоже.
CS>StringBuilder же это то что называется динамический буфер оптимизированный в первую очередь для append. CS>Использовать для этой цели CString/String/std::string — это даже не "средний программист", это хуже.
Как раз CString для этого подходит. На малых объемах он довольно эффективен.
CS>Или ты хочешь сказать что C# только для средних программистов?
На С++ тоже не боги работают. И писать свой класс из-за того что нужо сканкатирировать строки большинство людей не будет. Так что рельное приложение на С++ с большой долей вероятности окажется менее эффективной.
CS>Вот исходники C#::StringBuilder::Append
CS>Пример на С++ повторяет это с точностью до политики переаллоцирования
Ничего он не повторяет. Код не иквивалентен. В таких быстрых алгоритмах даже пара лишних инструкций или выравнивание кода уже резко влияет на резултат.
Тебе уже показали, что если твой код перекомпиляровать с оцией /clr (т.е. в сделать его менеджед), то разница получается незначительная.
PS
Итак, давай попробуем еще раз подумать насклько справедливы твои слова о том, что на дотнете нельзя создать конкурентно-спосбоного кода? Ведь даже если взять самые мрачные прогнозы (отстование дотнетного кода в два раза) особых проблем как-то не видно.
... << RSDN@Home 1.1.3 beta 2 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, alexkro, Вы писали:
CS>>Собственно оптимизирования никакого не проводилось. Такая задача не стояла. CS>>Наоборот — работать только штатными стандартными средствами платформы.
A>Зачем тогда свой наворот на C++ писать? Штатное стандартное средство C++ — .
Изменил в этотм тесте класс на wstring. Результат получился 1478. Против 1520 у C#.
... << RSDN@Home 1.1.3 beta 2 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
. Все остальное работает очень быстро особенно с Валуе типами.
По поводу приведенных тестов то они не корректны так как компилятор оптимизирует такие тесты до нельзя, и сравнение с компонентами Net тоже не очень. Например свои аналоги могут работать в 1.5 — 2 раза быстрее на том же C#. Скажем так нетовские компоненты не очень, усреднены, но для массового использования вполне пригодны.
Но никто не учитывает фрагментацию памяти итд.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, alexkro, Вы писали:
A>Я почти уверен, что и C# версию можно достаточно соптимизировать. Кстати, вопрос на засыпку: чем определяется предел оптимизации для данного примерчика?
Специализацией алгоритма и качеством инлайнинга методов. Остальные оптимизации у Шарпа и VC практически одинаковы.
... << RSDN@Home 1.1.3 beta 2 >>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, alexkro, Вы писали:
A>>Я почти уверен, что и C# версию можно достаточно соптимизировать. Кстати, вопрос на засыпку: чем определяется предел оптимизации для данного примерчика?
VD>Специализацией алгоритма и качеством инлайнинга методов. Остальные оптимизации у Шарпа и VC практически одинаковы.
Если даже применить все теоретически доступные оптимизации, то останется только одно ограничение — скорость доступа к памяти. Учитывая это, можно оценить насколько реализации использующие только стандартные компоненты далеки от идеально оптимизированного варианта. На моей машине P4 3GHz с памятью PC3200 максимальное, что я смог достичь, — это 0.45 сек. Есть основания думать, что используя SSE для прямой работы с кэшем (правильно используя на данном конкретном CPU), можно уменьшить время на 30%, что дает 0.3 сек. Теперь стандартные компоненты: std::wstring — 0.9, StringBuilder — 1.1. Отсюда вывод — стандартные комноненты достаточно хороши для данной задачи, чтобы не заботиться об специальной оптимизации (пока, конечно, это не станет основным bottleneck'ом в программе).