Сообщений 69    Оценка 110        Оценить  
Система Orphus

Кто сегодня самый шустрый-3?

Автор: Владислав Чистяков
Источник: "Технология Клиент-Сервер"
Float-тест 2

Кто сегодня самый шустрый?

Кто сегодня самый шустрый-2?

Тестовые проекты - 90КБ

Итак, как и принято во всех «порядочных» сериалах (и почему только я их так ненавижу?), краткое содержание предыдущих серий. В прошлых двух номерах нашего журнала нами были протестированы основные средства разработки, а именно производительность создаваемого ими кода:

При этом практически все средства разработки показали себя довольно-таки достойно. Исключением может являться VB, но и его результат не был приговором (особенно если учесть обеспечиваемую им простоту разработки).

Какой же главный вывод можно сделать из этих тестов? Похоже, что главным выводом является то, что чисто синтетические тесты мало показательны. К таким тестам можно отнести «вызов метода», «доступ к членам класса» и Float-тест. Причем если первые два представляют чисто академический интерес, то последний довольно важен. Программы, работающие с 3D-графикой, и программы, выполняющие интенсивные инженерные расчеты, довольно сильно зависят от скорости вычислений с плавающей точкой.

Кроме этого, выяснилось, что в наш тест вкралась досадная ошибка (о ней чуть позже). В конце концов, мы пришли к мысли, что нужно создать еще один Float-тест, но на этот раз состоящий из осмысленных вычислений, которые можно проверить, а не бездумного набора операций. В качестве алгоритма мы выбрали алгоритм вычисления элементарной сплайновой кривой Catmull-Rom в трехмерном пространстве. Чтобы процессору (а у нас, если вы помните, в тесте применялся AMD Athlon 1400 с 512 Mb DDR памяти) не показалось мало, было решено вычислить 50 миллионов вершин. В качестве входных данных были взяты 4 опорные вершины. Координаты последней вычисленной вершины, которые должны быть равны x:1, y:-1, z:0, мы выводили в окно сообщения (или консоль), чтобы проверить правильность полученного результата.

StrangeAttractr(50000000);
...

double CCMFCDlg::StrangeAttractr(long iInitVal)
{
  m_spIUtility->TimerStart();
  // Опорные точки.
  double x1 = -1, y1 = -1, z1 = 0;
  double x2 = -1, y2 =  1, z2 = 0;
  double x3 =  1, y3 = -1, z3 = 0;
  double x4 =  1, y4 =  1, z4 = 0;
  // Промежуточные переменные
  double px1, py1, pz1;
  double px2, py2, pz2;
  double px3, py3, pz3;
  double px4, py4, pz4;

  double ansx, ansy, ansz;

  long iOptCnt = iInitVal;//количество рассчитываемых точек
  double t = 0;
  double dt = 1.0 / iOptCnt; // инкремент параметра t на каждой итерации
  double n0, n1, n2, n3; // Коэффициент сплайна

  for (long i = 0; i < iOptCnt; i++) 
  {
    
    n0 = (-t * ((1 - t) * (1 - t))) / 2;
    n1 = (2 -5 * t * t + 3 * t * t * t) / 2;
    n2 = (t / 2) * (1 + 4 * t - 3 * t * t);
    n3 = -(( t * t) / 2) * (1 - t);

    px1 = x1 * n0; py1 = y1 * n0; pz1 = z1 * n0;
    px2 = x2 * n1; py2 = y2 * n1; pz2 = z2 * n1;
    px3 = x3 * n2; py3 = y3 * n2; pz3 = z3 * n2;
    px4 = x4 * n3; py4 = y4 * n3; pz4 = z4 * n3;

    ansx = px1 + px2 + px3 + px4;
    ansy = py1 + py2 + py3 + py4;
    ansz = pz1 + pz2 + pz3 + pz4;

    t += dt;
  }
  m_spIUtility->TimerEnd(/*sbsInfo*/);
  
  CString ss;
  ss.Format("Result(Last Point) is x:%f, y:%f, z:%f", ansx, ansy, ansz);
  MessageBox(ss);
  return 0;
}

Прежде чем пойти дальше, стоит уточнить, в чем же была ошибка в предыдущих тестах и к чему она приводила? Переменная цикла I (в первом Float-тесте) инициализировалась нулем и на первой итерации цикла происходило деление на ноль. Интересно, что ни один из компиляторов, вошедших в первую часть нашего обзора, ни звука не издал по этому поводу. Более того, Intel C++ compiler умудрился даже воспользоваться этой ошибкой в личных целях и занял первое место с воистину ошеломляющим результатом (0.29 секунды против 12.24 у C#, занявшего тогда второе место), более чем на порядок опередив всех конкурентов. Когда проводилась вторая часть нашего тестирования, в компанию подопытных был взят BCC. Он оказался первым, и пока что единственным компилятором, который смог внятно обнаружить деление на ноль и рассказать об этом нам. Мы переделали тест, проинициализировав i единицей, что уменьшило цикл на одну итерацию. Ввиду того, что одна итерация никак не могла повлиять на скорость вычислений, мы не стали перепроверять все тесты, а зря. Оказалось что скорость компилятора Intel C++ от этого значительно снизилась, хотя результат вычислений оказался в точности таким же. Эффект настолько неожиданный, что впору рекомендовать программистам выполнять деление на ноль в (профилактических) целях поднятия производительности :o). Измененный тест компилятор Intel C++ выполнил за 4.73 секунды, что, хотя и было очень хорошим результатом, тем не менее свергло этот компилятор с первой ступени пьедестала почета. На первое место переместился GCC, который выполнил этот тест за 3.44 секунды. Но этот результат тоже был очень похож на жульничество. Мы произвели ряд смелых экспериментов с командной строкой этого компилятора и выяснили, что преимущество в скорости достигается не за счет лучшей оптимизации самих вычислений с плавающий точкой, а за счет «раскручивания» цикла. По всей видимости, раскрутка дала возможность привести часть расчетов к константным вычислениям, и таким образом смухлевать. Почему это обман? Да потому, что в реальной жизни алгоритм не будет бесцельно накручиваться в цикле, чтобы отъесть процессорное время. Итак, при компиляции без опций -funroll-loops и -funroll-all-loops GCC показал более чем скромный результат (14.46 секунды), опередив лишь BCC (его время было 15.12) и VB (который показал 15.13). Все это еще более затруднило выявление лидера и укрепило нас в мысли о необходимости создания еще одного менее синтетического (более осмысленного) теста.

К тому времени, когда мы занялись написанием этого теста (а вернее, отбросили около пяти вариантов из-за их непригодности) подоспели Borland C++ Builder 6.0 (BCB) и релиз VS.Net (VS7, т.е. входящие в него VC7 и C#). Естественно, что мы с радостью протестировали и их.

Ниже приведены их результаты и результаты выполнения нашего нового Float-теста.

Float-тест 2

КомпиляторВремя
VC71.62
bcc2.55
C#3.14
gcc с опцией –funroll-loops3.29
gcc3.43
Delphi63.72
vc64.75
bcb5.47
Java5.73
Intel C++7.34
VB613.28
Таблица 1. Результаты нового Float-теста.

Расклад сил в новом тесте кардинально изменился. Лидером на сей раз стал VC7. Второе место, и это можно считать сенсацией, занял bcc. Он традиционно отставал во всех тестах (в том числе и в первом Float-тесте), но в новом тесте вырвался на вторую позицию, причем с очень недурным результатом. На третьем месте – C#. Интересно, что в этом тесте он показал лучший результат, если не производилась прекомпиляция утилитой ngen! Мы произвели отдельное тестирование с и без использования этой утилиты, а также привели результаты, полученные на бете 2 (с ngen). Эти результаты можно увидеть в таблице 3. В любом случае столь высокий показатель «управляемого» компилятора говорит о большом потенциале платформы .Net, да и управляемых платформ (вроде Java) в целом.

Как мы и предполагали, раскрутка циклов в gcc (давшая большое преимущество в синтетическом тесте) практически не повлияла на результаты осмысленного алгоритма. Но, тем не менее, gcc занял четвертое место с очень неплохим результатом. Каждый из остальных компиляторов был лишь на несколько десятых секунды медленнее, но в итоге эти десятые складывались в секунды, и большая часть компиляторов довольно серьезно отстала от лидера.

Но все эти отставания не так интересны. Интересно другое. Во-первых, лидер первого Float-теста Intel C++ compiler скатился на предпоследнее место. Тяжело сказать, что это – случайность или закономерность, но это случившийся факт. Во-вторых, новичок нашего тестирования bcb показал обескураживающие результаты. В какой-то момент мы заметили, что bcb в отладочном режиме показывает лучшие результаты, чем в релизе! Подозрение сразу пало на оптимизатор. С выключенной оптимизацией bcb показал значительно более высокий результат (4.21 секунды), чем в режиме оптимизации по скорости (5.47 секунды). Мы перепроверили все несколько раз, но все было верно! Нужно также отметить, что выигрыш от оптимизации в других случаях был, но обычно он не превышал 10%. Для сравнения, даже VC7 при выключении оптимизации выдавал код, минимум вдвое более медленный, нежели с включенной оптимизацией.

Тестс оптимизациейбез оптимизацииразница
Member Access6.548.001.5
Method Call Test9.3510.080.7
Virtual Method Call10.7911.380.6
Quick sort10.2513.873.6
Buble sort5.0313.428.4
PI Computation6.827.740.9
Tree Test11.8712.050.2
String Concatenation34.43/12.48134.58/19.1110.1
Floating point Test115.1217.031.9
Floating point Test25.474.21-1.3
Таблица 2. Результаты тестов Borland C++ Builder

C#С ngenБез ngen"С ngen" -
"Без ngen"
Бета 2"С ngen" -
"Бета 2"
Member Access2.382.380.03.54-1.2
Method Call Test1.441.440.02.16-0.7
Virtual Method Call7.926.901.07.220.7
Quick sort9.749.400.39.170.6
Bubble sort5.285.280.05.290.0
PI Computation6.906.97-0.16.900.0
Tree Test18.2418.39-0.223.60-5.4
String Concatenation4.404.310.13.381.0
Floating point Test112.2412.240.012.240.0
Floating point Test23.223.140.1  
Таблица 3. Результаты тестов C#.

Забавно, но бета-версия C#, которая была победителем в строковом тесте, сдала свои позиции в релиз-версии.

ТестБета 2Релиз
Member Access1.441.43
Method Call Test00
Virtual Method Call5.775.76
Quik sort8.088.16
Bubble sort5.004.98
PI Computation3.773.76
Tree Test11.3412.41
String Concatenation4.114.02
Floating point Test112.2612.23
Floating point Test2-1.62
Таблица 4. Результаты тестов VC7.

Какие же выводы можно сделать? Первый – к любым тестам нужно относиться со здравой долей скептицизма. В разных условиях компиляторы могут выступать то в роли лидера, то в роли аутсайдера.

Второй – практически все компиляторы производят на свет довольно быстрый код. В этих условиях главным критерием повышения производительности становится правильный выбор алгоритмов и качественная их реализация. Ведь пузырьковая сортировка, оптимально написанная на ассемблере с использованием всех возможностей процессора, все равно уступит «быстрой» сортировке, написанной на VBScript.

Третье – новые платформы (Java и .Net), а вместе с ними и так любимая у нас Delphi, практически не уступают C++-компиляторам по скорости производимого кода, но принятые в них концепции, в частности рантайм-полиморфизм на базе виртуальных классов и нетипизированных Object-указателей замедляют конечный результат. Шаблоны, которые пока что доступны только в C++, дают преимущество в производительности. Конечно, можно вручную создавать контейнерные классы и алгоритмы для конкретных типов данных, но, из-за трудоемкости, этого попросту никто не делает, обходясь контейнерами, хранящими ссылки на виртуальные базовые классы (обычно тип Object).

Четвертое – мнение о том, что управляемые среды типа Java и .Net медленны и из-за этого не могут рассматриваться всерьез как универсальные языки, на которых можно создавать быстрое ПО, ошибочно. Конечно, эти платформы очень молоды и болезненно переживают контакты с внешним (для них) неуправляемым кодом. Но, тем не менее, они порождают быстрый код, учитывающий особенности аппаратного обеспечения. Основанием для плохого мнения об этих платформах, скорее всего, является низкая производительность графических библиотек. В Яве это старая болезнь, связанная с переносимостью, а в .Net – это наоборот, детская болезнь, связанная с тем, что в .Net в основном используется новая графическая библиотека GDI+. Она рассчитана на аппаратное ускорение, но первая ее версия является всего лишь оберткой для обычного GDI Windows. При этом все крутые операции типа градиентных заливок и сглаживания начертаний шрифтов выполняются за счет центрального процессора. Надеемся, что в будущих версиях появится аппаратная акселерация, и эти проблемы исчезнут.

Что же касается абсолютного победителя, то, скорее всего, на эту роль больше всех подходит VC7. Однако нет никакой гарантии, что завтра не найдется тест, в котором один из его конкурентов не вырвется вперед. :) Говорить же о преимуществе одного компилятора над другим еще более неверно, так как разные компиляторы одного и того же языка могут давать совершенно разные результаты. А появление платформ типа Ява и .Net позволит снять с компиляторов проблемы оптимизации генерируемого года, так, MC++, хотя и является полноценным компилятором C++, но, тем не менее, порождает в основном, MSIL (байт-код платформы .Net). На сегодня больше интересен другой вопрос... какую платформу выбрать? Продолжать ли создавать обычный «неуправляемый» код или выбрать, Яву или .Net? Так что выбор средства разработки переходит в разряд выбора платформы разработки или даже стиля жизни.


Впервые статья была опубликована в журнале <Технология Клиент-Сервер>.
Эту и множество других статей по программированию, разработке БД, многоуровневым технологиям (COM, CORBA, .Net, J2EE) и CASE-средствам вы можете найти на сайте www.optim.su и на страницах журнала.
    Сообщений 69    Оценка 110        Оценить