File copy circa 1975:
#include <stdio.h> int main() { int c; while ((c = getchar()) != EOF) putchar(c); return errno != 0; }
Fast forward 20 years, and...
#include <iostream> #include <algorithm> #include <iterator> #include <string> using namespace std; int main() { // (forgot the try/catch around main) copy(istream_iterator<string>(cin), istream_iterator<string>(), ostream_iterator<string>(cout,"\n")); }Something, somewhere, went terribly wrong.
Andrei Alexandrescu.
Я помню, как еще шесть с половиной лет назад, начиная изучать C++ я читал о потоках в STL и думал "Как это круто! Они так неожиданно и здорово перегрузили оператор <<". Однако время идет, а ореола очарования постепенно сошел на нет.
Первая проблема. They are too verbose! (В них чересчур много буков).
Пример из реально работающего кода.
std::cout << std::setw(2) << std::setfill('0') << (pTime->tm_mon + 1)
<< std::setw(2) << std::setfill('0') << pTime->tm_mday
<< std::setw(2) << std::setfill('0') << (pTime->tm_year + 1900)
<< "_" << std::setw(2) << std::setfill('0') << pTime->tm_hour
<< std::setw(2) << std::setfill('0') << pTime->tm_min
<< std::setw(2) << std::setfill('0') << pTime->tm_sec
<< std::setw(3) << std::setfill('0') << (mTimer->getMilliseconds() % 1000);
В C-стиле это будет выглядеть как
printf( "%02d%02d%02d_%02d%02d%02d%03d",
(pTime->tm_mon + 1),
pTime->tm_mday,
(pTime->tm_year + 1900),
pTime->tm_hour,
pTime->tm_min,
pTime->tm_sec,
(mTimer->getMilliseconds() % 1000
);
Как по мне, так второй код читается лучше, а пишется короче. Так почему же "something has gone wrong?"
Вторая причина. Работаем мы с open-source движком Ogre3D, таким большим, толстым с очень длинными именами классов навроде Ogre::HardwareVertexBufferSharedPtr и достаточно интенсивным использованием строк.
Делаем в проекте рендеринг огромных массивов растительности (бескрайний лес:)), используя PagedGeometry - плагин к огру; как раз подошла необходимость заняться оптимизацией. Проблема заключается в том, что у нас единовременно подгружаются достаточно большие участки леса и при этом наблюдается заметный лаг.
Включаю профайлер и что же я вижу? (Обратите внимание на Forests::BatchedGeometry::SubBatch::build/)
Функция которая должна вызываться не чаще, чем раз в 10 кадров пятая по времени среди "горячих точек"!
Копаем дальше! Это результаты еще одной сессии профилирования, где BatchedGeometry::build заняло в целом около 1.4 с. времени.
Ничего себе. В коде генерации деревьев больше половины времени занимают операции со строками!
String BatchedGeometry::getFormatString(SubEntity *ent)
{
StringUtil::StrStreamType str;
str << ent->getMaterialName() << "|";
str << ent->getSubMesh()->indexData->indexBuffer->getType() << "|";
const VertexDeclaration::VertexElementList &elemList = ent->getSubMesh()->vertexData->vertexDeclaration->getElements();
VertexDeclaration::VertexElementList::const_iterator i;
for (i = elemList.begin(); i != elemList.end(); ++i)
{
const VertexElement &element = *i;
str << element.getSource() << "|";
str << element.getSemantic() << "|";
str << element.getType() << "|";
}
return str.str();
}
был заменен на
String BatchedGeometry::getFormatString(SubEntity *ent)
{
const int BufSize = 1024;
char buf[BufSize];
int countWritten =
_snprintf_s( buf, BufSize,
"%s|%d",
ent->getMaterialName().c_str(),
ent->getSubMesh()->indexData->indexBuffer->getType()
);
const VertexDeclaration::VertexElementList &elemList = ent->getSubMesh()->vertexData->vertexDeclaration->getElements();
VertexDeclaration::VertexElementList::const_iterator i;
for (i = elemList.begin(); i != elemList.end(); ++i)
{
const VertexElement &element = *i;
countWritten += _snprintf_s( buf + countWritten, BufSize - countWritten, BufSize - countWritten,
"|%d|%d|%d",
element.getSource(),
element.getSemantic(),
element.getType()
);
}
return buf;
}
Время генерации деревьев сократилась в 2-2.2(!!!!!!) раза: вместо средних 4.8-5.6 мс на один вызов стало 2.2-2.8. Не знаю, ругаться на разработчиков PagedGeometry или на реализацию стримов у Майкрософта?
Такая низкая скорость работы объясняется тем, что
- сами потоки - объекты, которые любят выделять память, в частности на создании и удалении;
- реализация STL от майкрософта по умолчанию вставляет локи на работу с потоками, если проект собирается, как multithreaded.
Итак, они медленные.
Так что я буду по-прежнему использовать printf или класс StringFormatter и пусть меня убеждают и дальше о пользе некоторых абстракций STL. :)
P.S. Между прочим у Александреску есть отличная статья на тему типо-безопасного printf-а!



Комментариев нет:
Отправить комментарий