В продолжение темы предыдущего поста про философию программирования. Всем, кто "застрял" в императивном программировании, предлагаю расширить кругозор программированием декларативным.
В частности, можно почитать книгу по Хаскелю и посмотреть видео-лекции по Лиспу.
24 марта 2007
22 марта 2007
Языковой барьер
Очень правильная заметка - Языковой барьер.
Почему-то немногие при изучении нового языка уделяют внимание именно философии программирования на нем, больше заботясь об изучении синтаксиса и интерфейса стандартных библиотек.
Больше внимания нужно уделять именно "духу" языка. Не стоит писать на C++ как на "расширенном C". Следует проникнуться RAII, шаблонами, умными указателями и патернами.
Всем, кто не читал, рекомендую книги Александреску, Саттера и Мэйерса. Особенно Современное проектирование на С++.
Почему-то немногие при изучении нового языка уделяют внимание именно философии программирования на нем, больше заботясь об изучении синтаксиса и интерфейса стандартных библиотек.
Больше внимания нужно уделять именно "духу" языка. Не стоит писать на C++ как на "расширенном C". Следует проникнуться RAII, шаблонами, умными указателями и патернами.
Всем, кто не читал, рекомендую книги Александреску, Саттера и Мэйерса. Особенно Современное проектирование на С++.
06 марта 2007
clone()
Иногда есть "тяжеловесные" классы, для которых нежелательно допустить неявное копирование. Обычно это делается так:
Хорошее решение этого вопроса: использование функции clone(), как это сделано в Boost Pointer Container Library:
class FooОднако, возникает вопрос: как все-таки явно скопировать объекты такого типа?
{
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
};
Хорошее решение этого вопроса: использование функции clone(), как это сделано в Boost Pointer Container Library:
ptr_vector<T> v1, v2(v1.clone());Clone() создает свой клон на куче (да хоть через тот же приватный конструктор копирования) и возвращает auto_ptr на него, а соотвутствующий конструктор и оператор присваивания делают свое дело через swap():
v2 = v1.clone();
class Foo
{
public:
void swap(Foo&);
auto_ptr<Foo> clone() const;
Foo(auto_ptr<Foo> clone)
{
swap(*clone);
}
Foo& operator=(auto_ptr<Foo> clone)
{
swap(*clone);
return *this;
}
};
04 марта 2007
Bicycles and Weak pointers
Все больше убеждаюсь в том, что для успешной разработки софта нужно знать как можно больше изобретенных велосипедов и по возможности их использовать. От использования готовых велосипедов зависит не только скорость разработки, но и качество. Часто изобретенные кем-то давным-давно велосипеды уже отлажены и готовы к бою. А вот собственноручно написанные за пару дней или недель - могут развалиться при выезде на трассу.
У меня в проекте есть такой веселый код: некие объекты запоминают указатели на другие объекты, и прежде чем этими указателями воспользоваться - проверяют их на валидность. Как проверяют? Некий глобальный объект-менеждер ведет список указателей на живые объекты (при их уничтожении объекты его оповещают, и он удаляет соответствующий указатель из списка). И если у него спросить - есть ли такой-то указатель у него в списке - то можно сделать вывод, существует ли сейчас объект по этому указателю или нет. Так вот валидность указателей и проверяется.
А вот теперь представим ситуацию: один объект удалился, затем создался другой. Причем operator new() может спокойно выдать адрес, равный адресу старого объекта. И если предварительно мы запомнили адрес первого (удаленного) объекта, то менеджер (упомянутый ранее) скажет нам что указатель наш валиден, хотя на самом деле объект, на который мы ссылались - уничтожен.
А все могло бы быть и без этой головной боли. Если бы в качестве указателя на объект мы запомнили не простой указатель, а велосипед под названием weak pointer, и при обращении к объекту просто лочили его.
У меня в проекте есть такой веселый код: некие объекты запоминают указатели на другие объекты, и прежде чем этими указателями воспользоваться - проверяют их на валидность. Как проверяют? Некий глобальный объект-менеждер ведет список указателей на живые объекты (при их уничтожении объекты его оповещают, и он удаляет соответствующий указатель из списка). И если у него спросить - есть ли такой-то указатель у него в списке - то можно сделать вывод, существует ли сейчас объект по этому указателю или нет. Так вот валидность указателей и проверяется.
А вот теперь представим ситуацию: один объект удалился, затем создался другой. Причем operator new() может спокойно выдать адрес, равный адресу старого объекта. И если предварительно мы запомнили адрес первого (удаленного) объекта, то менеджер (упомянутый ранее) скажет нам что указатель наш валиден, хотя на самом деле объект, на который мы ссылались - уничтожен.
А все могло бы быть и без этой головной боли. Если бы в качестве указателя на объект мы запомнили не простой указатель, а велосипед под названием weak pointer, и при обращении к объекту просто лочили его.
27 февраля 2007
GetOpenFileName issues
MSDN пишет:
Однако под XP этот флаг великолепно работает. Интересно, что они имели ввиду под "ineffective"?
OFN_NOCHANGEDIR
Restores the current directory to its original value if the user changed the directory while searching for files.
Windows NT 4.0/2000/XP: This flag is ineffective for GetOpenFileName.
Однако под XP этот флаг великолепно работает. Интересно, что они имели ввиду под "ineffective"?
24 февраля 2007
IDispatch Wrapper
Работать с COM-объектами из pure C довольно неудобно. Тут одназначно нужно использовать разные C++ wrapper-ы и helper-ы. Некоторые из них доступны "из коробки": _com_ptr_t, _variant_t, директива #import. Но с IDispatch все же не особо удобно работать, как например в Visual Basic.
Немного погуглив обнаружил отличный wrapper для IDispatch by Mike Morearty. Позволяет намного упростить жизнь:
Надо будет еще в дополнение к нему написать wrapper для COM-коллекций в STL-совместимые контейнеры.
PS. Хотя в этом IDispatch wrapper-е явно не сказано про лицензию, но по почте Майк ответил "you're free to use the code, and to modify it."
Немного погуглив обнаружил отличный wrapper для IDispatch by Mike Morearty. Позволяет намного упростить жизнь:
CDispatchPtr htmldoc = ...;
_bstr_t html = htmldoc.Get("body").Get("innerHTML");
htmldoc.Put("title", "New Title");
htmldoc.Get("body").Get("firstChild").Invoke("insertAdjacentText", "afterBegin", "hello world");
Надо будет еще в дополнение к нему написать wrapper для COM-коллекций в STL-совместимые контейнеры.
PS. Хотя в этом IDispatch wrapper-е явно не сказано про лицензию, но по почте Майк ответил "you're free to use the code, and to modify it."
19 февраля 2007
The sence of Boost.Ref
Неприятная особенность boost::bind заключается в том, что все параметры передаются по значению. Тем самым происходит "обрезание" производного объекта до его базового класса в случае, если передается ссылка на базовый класс.
Выход - использование boost::reference_wrapper, который инкапсулирует в себя ссылку и уже далее передается по значению:
Выход - использование boost::reference_wrapper, который инкапсулирует в себя ссылку и уже далее передается по значению:
class base
{
public:
virtual std::string whoami() const { return "base"; }
virtual ~base() {};
};
class derived : public base
{
public:
virtual std::string whoami() const { return "derived"; }
};
void whoisthis(const base& obj)
{
std::cout << obj.whoami() << std::endl;
}
void FunctorsTest3()
{
derived d;
base& b = d;
whoisthis(d); // works ok
boost::bind(whoisthis, b)(); // cut derived to base
boost::bind(whoisthis, boost::ref(b))(); // works ok
}
15 февраля 2007
Loki::Functor vs boost::function
Исследовал пути решения простенькой задачи.
Задача
1. Есть иерархия классов
2. Есть функторы, принимающие ссылки на Base, Derived, etc...
3. Есть динамический массив функторов из п.2
Задача: обработать объект из иерархии Base функторами из массива п.3.
Исходные данные
Функторы, входящие в массив:
Вспомогательный "конвертер" функторов для приведения к их одному типу:
Loki
Вспомнив, что функторы есть в Loki, попробовал начать с этой библиотеки:
И тут началось...
Такой вод код работает:
А такой вот вылетает с ошибкой из-за проблем с auto_ptr внутри Loki::Functor:
Дальше - больше. Если объявить переменную
Boost
Boost тоже имеет реализацию функторов - boost::function:
Здесь все гораздо проще:
И главное - работает как часы (в отличие от Loki).
Задача
1. Есть иерархия классов
class Base {};
class Derived : public Base {};
...2. Есть функторы, принимающие ссылки на Base, Derived, etc...
3. Есть динамический массив функторов из п.2
Задача: обработать объект из иерархии Base функторами из массива п.3.
Исходные данные
Функторы, входящие в массив:
void Handler1(Base&);
void Handler2(Derived&);
Вспомогательный "конвертер" функторов для приведения к их одному типу:
template <typename T>
void HandlerConversion(void (*Handler)(T&), Base& c)
{
if (T* t = dynamic_cast<T*>(&c))
Handler(*t);
}
Loki
Вспомнив, что функторы есть в Loki, попробовал начать с этой библиотеки:
typedef Loki::Functor<void, LOKI_TYPELIST_1(Control&)> ControlFunctor;
typedef std::vector<ControlFunctor> FunctorVector;
И тут началось...
Такой вод код работает:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
Loki::Functor<void, LOKI_TYPELIST_2(void(*)(T&), Control&)> cf(HandlerConversion<T>);
v.push_back(Loki::BindFirst(cf, Handler));
}
А такой вот вылетает с ошибкой из-за проблем с auto_ptr внутри Loki::Functor:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
v.push_back(Loki::BindFirst(Loki::Functor<void, LOKI_TYPELIST_2(void(*)(T&), Control&)>(HandlerConversion<T>), Handler));
}
Дальше - больше. Если объявить переменную
FunctorVector v; внутри тестовой main(), то все работает отлично, а если как глобальную переменную - вылетает при выходе из main(). Помогает v.clear(); в конце main(). Жесть какая-то. Подобную веселую реализацию Loki::Functor не решился использовать в рабочем проекте.Boost
Boost тоже имеет реализацию функторов - boost::function:
typedef boost::function<void (Base&)> ControlFunctor;
typedef std::vector<ControlFunctor> FunctorVector;Здесь все гораздо проще:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
v.push_back(boost::bind(HandlerConversion<T>, Handler, _1));
}
И главное - работает как часы (в отличие от Loki).
17 января 2007
Painting of disabled icons
Не найдя стандартных путей рисования иконки в "disabled" состоянии, пришлось изобретать собственный велосипед. Windows предлагает сделать подобное либо имея static control с иконкой и задав ему EnableWindow(FALSE), либо имея ImageList и рисуя уже из него. Мне же нужен был простой способ отрисовать HICON на HDC не плодя при этом control-ов и ImageList-ов.
Велосипед довольно успешно был собран из подручных компонентов - функций WinAPI и небольшой приправы из цикла по перемалыванию байтов:
Велосипед довольно успешно был собран из подручных компонентов - функций WinAPI и небольшой приправы из цикла по перемалыванию байтов:
void DrawDisabledIcon(HDC DC, CRect& Rect, WTL::CIcon& Icon)Методика проста - отрисовываем иконку во временный буфер, переводим ее там в gray scale и выводим через AlphaBlend(), так как сама иконка содержит в себе alpha-канал. При AlphaBlend-е можно еще подправить ее прозрачность, задав SourceConstantAlpha.
{
WTL::CDC MemDC(CreateCompatibleDC(DC));
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = Rect.Width();
bmi.bmiHeader.biHeight = Rect.Height();
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = bmi.bmiHeader.biWidth * bmi.bmiHeader.biHeight * 4;
VOID *pvBits;
WTL::CBitmap Bitmap(::CreateDIBSection(MemDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0));
WTL::CBitmapHandle PrevBitmap(MemDC.SelectBitmap(Bitmap));
Icon.DrawIconEx(MemDC, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biWidth);
// convert to grayscale
for (unsigned char *p = (unsigned char*)pvBits, *end = p + bmi.bmiHeader.biSizeImage; p < end; p += 4)
{
// Gray = 0.3*R + 0.59*G + 0.11*B
p[0] = p[1] = p[2] =
(
static_cast<unsigned int>(p[2]) * 77 +
static_cast<unsigned int>(p[1]) * 151 +
static_cast<unsigned int>(p[0]) * 28
) >> 8;
}
BLENDFUNCTION BlendFunction;
BlendFunction.BlendOp = AC_SRC_OVER;
BlendFunction.BlendFlags = 0;
BlendFunction.SourceConstantAlpha = 0x60; // half transparent
BlendFunction.AlphaFormat = AC_SRC_ALPHA; // use bitmap alpha
AlphaBlend(DC, Rect.left, Rect.top, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight,
MemDC, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, BlendFunction);
MemDC.SelectBitmap(PrevBitmap);
}
08 января 2007
Case insensitive search performance
Сегодня оптимизировал одно из узких мест в проекте: case insensetive поиск подстроки в строке - то есть не различая регистр.
В системе часто ведется фильтрация массива строк по вхождению подстроки, поэтому такая оптимизация была актуальна.
Провел сравнение скорости разных способов проверки вхождения подстроки в строку.
За единицу скорости принял std::wstring::find() - она хотя не делает того, что нужно (не регистро-независимая), но в качестве эталона скорости - самое оно. Тестировал в VC++ 7.1 release mode с включенной оптимизацией по скорости.
Аналог std::wstring::find() из буста - boost::find_first() - оказался в 2,5 раза медленнее эталона.
Та функция из буста, которая делает то, что мне нужно - boost::ifind_first() - в 70 раз медленнее эталона. В коде проекта как раз она и использовалась - отсюда и дикие тормоза.
Первая моя вариация была такой: делаем копии строк, затем применяем к ним boost::to_upper(), затем - std::wstring::find(). Результат - в 51 раз медленнее эталона. Но это уже заметно быстрее boost::ifind_first().
Следующая версия - создание глобальной таблицы wchar_t ToUpper[0x10000], содержащая для каждого символа его upper case варианта. Используя эту таблицу внутри предиката для функции boost::first_finder(), получил версию, всего в 2 раза медленнее эталона!
При нежелании тратить 128Kb на подобную таблицу, ее можно сократить до 2Kb, просчитывая только ее начало и используя подобным образом:
Выбрана была последняя версия с буфером 2Kb, куда влезает вся латиница и кириллица. Дальнейшая оптимизация свелась к тому, что искомая подстрока переводилась в upper case всего один раз, а ToUpper[] применялась только к строкам в которых шел поиск. Этот финт, правда, почти не сказался на скорости.
В системе часто ведется фильтрация массива строк по вхождению подстроки, поэтому такая оптимизация была актуальна.
Провел сравнение скорости разных способов проверки вхождения подстроки в строку.
За единицу скорости принял std::wstring::find() - она хотя не делает того, что нужно (не регистро-независимая), но в качестве эталона скорости - самое оно. Тестировал в VC++ 7.1 release mode с включенной оптимизацией по скорости.
Аналог std::wstring::find() из буста - boost::find_first() - оказался в 2,5 раза медленнее эталона.
Та функция из буста, которая делает то, что мне нужно - boost::ifind_first() - в 70 раз медленнее эталона. В коде проекта как раз она и использовалась - отсюда и дикие тормоза.
Первая моя вариация была такой: делаем копии строк, затем применяем к ним boost::to_upper(), затем - std::wstring::find(). Результат - в 51 раз медленнее эталона. Но это уже заметно быстрее boost::ifind_first().
Следующая версия - создание глобальной таблицы wchar_t ToUpper[0x10000], содержащая для каждого символа его upper case варианта. Используя эту таблицу внутри предиката для функции boost::first_finder(), получил версию, всего в 2 раза медленнее эталона!
При нежелании тратить 128Kb на подобную таблицу, ее можно сократить до 2Kb, просчитывая только ее начало и используя подобным образом:
(c <= 0x451) ? ToUpper[c] : std::toupper(c, loc). В таком случае скорость получилась в 5 раз медленнее эталона.Выбрана была последняя версия с буфером 2Kb, куда влезает вся латиница и кириллица. Дальнейшая оптимизация свелась к тому, что искомая подстрока переводилась в upper case всего один раз, а ToUpper[] применялась только к строкам в которых шел поиск. Этот финт, правда, почти не сказался на скорости.
31 декабря 2006
Properties in C++
Замечательная штука есть в современных языках программирования - properties. Берешь и пишешь:
Все так просто! Visible - это свойство объекта "быть показанным на экране". Мы можем легко читать и изменять это свойство зная его имя.
В C++ для этого необходимо сделать accessor и mutator, то есть две функции: одна позволяющая получить значение, вторая - изменяющая значение (со всеми последствиями для объекта). Если accessor можно назвать именем свойства -
Более красивый вариант - прикрутить properties к C++. Загоревшись этой идеей набросал шаблон property:
Object.Visible = True и объект отображается на экране. Хочешь проверить, отображен ли объект - пишешь If Object.Visible Then...Все так просто! Visible - это свойство объекта "быть показанным на экране". Мы можем легко читать и изменять это свойство зная его имя.
В C++ для этого необходимо сделать accessor и mutator, то есть две функции: одна позволяющая получить значение, вторая - изменяющая значение (со всеми последствиями для объекта). Если accessor можно назвать именем свойства -
bool Visible() - то как быть с mutator-ом? Назвать его Visible(bool)? Не красиво. SetVisible(bool)? Не очень понятно. Show(bool)? А как запомнить что это mutator к свойству Visible?Более красивый вариант - прикрутить properties к C++. Загоревшись этой идеей набросал шаблон property:
template <typename T, class C>
class property
{
public:
typedef T (C::*accessor_t)() const;
typedef void (C::*mutator_t)(const T&);
property(C* object, accessor_t accessor, mutator_t mutator) :
_object(object), _accessor(accessor), _mutator(mutator) {}
operator T() const { return ((*_object).*(_accessor))(); }
property& operator=(const T& value) { ((*_object).*(_mutator))(value); return *this; }
private:
C* _object;
accessor_t _accessor;
mutator_t _mutator;
};
class range
{
public:
range(int begin, int end) : _begin(begin), _end(end) {}
int begin() const { return _begin; }
int end() const { return _end; }
public:
property<int, range> size() { return property<int, range>(this, size, setsize); }
int size() const { return _end - _begin; }
void setsize(const int& size) { _end = _begin + size; }
private:
int _begin, _end;
};
void TestProperties()
{
range r(1, 5);
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;
r.size() = 2;
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;
}
29 декабря 2006
Dynamicaly created SQL statements
Переписываю приложение с VBA на C++. В приложении во всю используются динамически создаваемые SQL-запросы:
Было:
Стало:
Определенно стало намного удобнее писать SQL-запросы.
Было:
Условие = ""
If VarType(Me![Начальная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] >= " & ДатаДляSQL(Me![Начальная дата])
If VarType(Me![Конечная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] <= " & ДатаДляSQL(Me![Конечная дата])
If VarType(Me![Тип]) > vbNull Then ДобавитьУсловие Условие, "[Тип] = " & Me![Тип]
If VarType(Me![Эмитент]) > vbNull Then ДобавитьУсловие Условие, "[Эмитент] = " & Me![Эмитент]
If VarType(Me![Структура]) > vbNull Then ДобавитьУсловие Условие, "[Структура] = " & Me![Структура]
Me![Список].RowSource = "SELECT * FROM [События - список]" & OptionalWhereStatement(Условие) & " ORDER BY [Дата], [Название общества]"
Стало:
<dataset name="Events">
Select
[Events].[id],
[Events].[Date],
[Persons].[Name] [Person Name],
[Events: Types].[Name] [Event Type Name],
[Events].[Notes]
From [Events]
Left Join [Persons] On [Persons].[id]=[Events].[Person]
Left Join [Events: Types] On [Events: Types].[id]=[Events].[Type]
Where (1=1)
<where expression="[Events].[Person]" condition="=" param="Person"/>
<where expression="[Events].[Type]" condition="=" param="Type"/>
<where expression="[Events].[Date]" condition="<=" param="Date Max"/>
<where expression="[Events].[Date]" condition=">=" param="Date Min"/>
<where expression="[Persons].[Financial Group]" condition="=" param="Financial Group"/>
Order By [Events].[Date]
</dataset>
Определенно стало намного удобнее писать SQL-запросы.
14 декабря 2006
Memory allocation by compiler
Получил занимательное сообщение от компилятора:
Fatal Error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit
Компилятору не хватило памяти и он предлагает мне вручную указать ему сколько памяти использовать. То ли я чего не понимаю, но вроде давно изобрели динамическое выделение памяти. Или он не хочет случайно в swap залезть... Да бред какой-то.
Fatal Error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit
Компилятору не хватило памяти и он предлагает мне вручную указать ему сколько памяти использовать. То ли я чего не понимаю, но вроде давно изобрели динамическое выделение памяти. Или он не хочет случайно в swap залезть... Да бред какой-то.
12 декабря 2006
Performance issues
Загрузил в свой проект реальные данные и ужаснулся - получил жуткие тормоза. Окно открывалось секунд 10. Нереально долго.
Вначале подумал что SQL запросы тормозят на большом количестве данных. Ан нет, летают. Собрал Release версию. Бегает намного быстрее, но все равно - окно открывает 2-3 секунды. Это конечно лучше чем 10 секунд у Debug версии, но мне 1) в бета-тестирование давать именно Debug версию, 2) самому отлаживат - Debug версию, 3) все равно очень долго.
Методом тыка (без профайлера) выяснил что больше всего тормозит lookup записей из таблиц. Причем в коде стоял простой
Пробежал код глазами - обнаружил интересный момент. При загрузке данных с SQL сервера идет преобразование из COM-овского _variant_t в проприетарный CVariant. Строки загружаются очень весело: у CVariant есть метод
Это другое место оказалось повторной загрузкой одних и тех же данных. То есть если какому-то объекту понадобились какие-то данные, то он их просто грузит с сервера. И так несколько объектов вытягивают одну и ту же большую таблицу.
Естественно, необходимо реализовать кэш. Да так и планировалось - создать кэш. Но проблема создания такого кэша - его же надо обновлять. В дальнейшем все запросы (в том числе на изменение данных) должны проходить через сервер приложений, и он должен присылать по подписке сообщения типа "обновите кэш". Но сейчас временно (до первой беты) сервера приложений не планируется. Так как если его делать сразу, то хрен знает когда начнется бета-тестирование. А без сервера приложений некому прислать сообщение "обновите кэш" - используемый SQL Server этого не умеет.
Видимо придется пока временно делать таймер, который будет периодически наведываться на SQL сервер и узнавать, не пора ли обновить кэш.
PS. Как в таких случаях жить без профайлера? VTune $700 стоит. Может что попроще и побесплатней есть?
Вначале подумал что SQL запросы тормозят на большом количестве данных. Ан нет, летают. Собрал Release версию. Бегает намного быстрее, но все равно - окно открывает 2-3 секунды. Это конечно лучше чем 10 секунд у Debug версии, но мне 1) в бета-тестирование давать именно Debug версию, 2) самому отлаживат - Debug версию, 3) все равно очень долго.
Методом тыка (без профайлера) выяснил что больше всего тормозит lookup записей из таблиц. Причем в коде стоял простой
std::find и комментарий "todo: использовать индексы для быстрого поиска". На тестовых данных все летало, а вот на реальных данных, когда есть 3000 записей к ним нужно lookup-ить 2000 записей получалось O(M*N) = 2000*3000 = 6000000. Реализовал индексирование - стало намного быстрее. Открытие окна - секунды 2. Причем и в debug и в release примерно одинаково.Пробежал код глазами - обнаружил интересный момент. При загрузке данных с SQL сервера идет преобразование из COM-овского _variant_t в проприетарный CVariant. Строки загружаются очень весело: у CVariant есть метод
SetString(const std::wstring&), а у _variant_t достать строку можно только сначала преобразовав его в _bstr_t (при этом строка копируется), затем в const wchar_t*. При передаче ее в SetString() сначала конструируется wstring (а это плюс еще одно копирование), затем идет непосредственно копирование строки внутри CVariant::SetString(). Вобщем куча копирований вместо одного. Придется вручную лезть внутрь _variant_t, благо он унаследован от tagVARIANT, который является структурой - то есть все внутренности - public. Но самое интересное - жуткие тормоза возникают совсем не из-за этого, а где-то в другом месте.Это другое место оказалось повторной загрузкой одних и тех же данных. То есть если какому-то объекту понадобились какие-то данные, то он их просто грузит с сервера. И так несколько объектов вытягивают одну и ту же большую таблицу.
Естественно, необходимо реализовать кэш. Да так и планировалось - создать кэш. Но проблема создания такого кэша - его же надо обновлять. В дальнейшем все запросы (в том числе на изменение данных) должны проходить через сервер приложений, и он должен присылать по подписке сообщения типа "обновите кэш". Но сейчас временно (до первой беты) сервера приложений не планируется. Так как если его делать сразу, то хрен знает когда начнется бета-тестирование. А без сервера приложений некому прислать сообщение "обновите кэш" - используемый SQL Server этого не умеет.
Видимо придется пока временно делать таймер, который будет периодически наведываться на SQL сервер и узнавать, не пора ли обновить кэш.
PS. Как в таких случаях жить без профайлера? VTune $700 стоит. Может что попроще и побесплатней есть?
08 декабря 2006
Last element in a container
Поймал себя на использовании конструкции
Ужаснулся, переписал все через
Container[Container.size()-1] для доступа к последнему элементу контейнера.Ужаснулся, переписал все через
*Container.rbegin() Container.back()
01 декабря 2006
22 ноября 2006
Build optimization
Решил поискать пути ускорения build-а не меняя аппаратную часть компьютера. Перенес includes из boost на RAM disk. Проект который раньше компилировался пять минут теперь компилируется четыре.
Убил RAM disk, перенес весь используемый boost и стандартную библиотеку в precompiled header. Компиляция того проекта укладывается уже в 2 минуты.
Убил RAM disk, перенес весь используемый boost и стандартную библиотеку в precompiled header. Компиляция того проекта укладывается уже в 2 минуты.
21 ноября 2006
Smart pointers rule
Набрел в сети на Process Explorer, побаловался с ним. Посмотрел сколько мое приложение ест памяти и т.п. Провел эксперимент - в приложении несколько раз открыл-закрыл довольно тяжелое по требованиям к памяти окно.
Ну меня это, естественно, сильно напрягло. Ведь означало это скорее всего только одно - утечки памяти. А я их жутко не люблю, что даже ввел в стандарт кодирования повсеместное использование умных указателей.
Но, спрашивается, если я везде использую умные указатели, то какого хрена получаю утечки памяти? Это напрягло меня вдвойне. Я никогда в жизни не искал утечки памяти - на C++ пишу чуть больше года, до этого писал на VBA с его COM-объектами и отсутствием заморочек на счет освобождения памяти.
Однажды я попробовал некую небольшую утилиту для поиска памяти - она встраивалась в Visual Studio и по завершении приложения выдавала список утечек. Так она мне их выдало такое немерянное количество в моем приложении, что я в это не поверил и снес ее нафиг.
После этого я зарекся при поиске утечек обойтись без утилит, а просто прописать в настройках проекта Force Includes некий хедер-файл, заменяющий malloc и free на их отладочные версии запоминающие кто сколько выделил и освободил ли потом.
Но сейчас мне совсем не хотелось с этим мучаться - я ведь просто игрался с Process Explorer. Но утечки покоя мне не давали.
В результате я просто добавил код, 2000 раз открывающий и закрывающий в приложении определенное окно и смотрел на прирост сожранной памяти в Process Explorer. Постепенно убирая функционал из кода, создающего окно, я добился того, что память перестала пожираться. Соответственно, последний убранный код был кодом, дающим утечки памяти.
Это был следующий код:
Так и оказалось - в документации к xmlGetProp значилось:
Returns: the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
А мой программист на xmlFree() забил.
Так как xmlGetProp() постоянно использовалась, пришлось написать для нее "smart pointer", чтобы не напрягать мозги на счет xmlFree(). Что-то типа такого:
Восстановил функциональность приложения, прогнал 2000 открытий-закрытий, перерасхода памяти не обнаружил.
Ну меня это, естественно, сильно напрягло. Ведь означало это скорее всего только одно - утечки памяти. А я их жутко не люблю, что даже ввел в стандарт кодирования повсеместное использование умных указателей.
Но, спрашивается, если я везде использую умные указатели, то какого хрена получаю утечки памяти? Это напрягло меня вдвойне. Я никогда в жизни не искал утечки памяти - на C++ пишу чуть больше года, до этого писал на VBA с его COM-объектами и отсутствием заморочек на счет освобождения памяти.
Однажды я попробовал некую небольшую утилиту для поиска памяти - она встраивалась в Visual Studio и по завершении приложения выдавала список утечек. Так она мне их выдало такое немерянное количество в моем приложении, что я в это не поверил и снес ее нафиг.
После этого я зарекся при поиске утечек обойтись без утилит, а просто прописать в настройках проекта Force Includes некий хедер-файл, заменяющий malloc и free на их отладочные версии запоминающие кто сколько выделил и освободил ли потом.
Но сейчас мне совсем не хотелось с этим мучаться - я ведь просто игрался с Process Explorer. Но утечки покоя мне не давали.
В результате я просто добавил код, 2000 раз открывающий и закрывающий в приложении определенное окно и смотрел на прирост сожранной памяти в Process Explorer. Постепенно убирая функционал из кода, создающего окно, я добился того, что память перестала пожираться. Соответственно, последний убранный код был кодом, дающим утечки памяти.
Это был следующий код:
std::wstring CXMLNode::OptionalAttrЭто был класс C++-оболочки libxml, написанный не мной, а программистом, работавшим на меня. Я конечно, порадовался, что код не мой ;) И сразу очевидно, утечку дает AttrStr = xmlGetProp(...), так как xmlGetProp() - функция из C-шной библиотеки (libxml), а там про умные указатели не знают.
(
const std::wstring& AttrName,
const std::wstring& DefaultValue
) const
{
assert( !IsNull() );
const xmlChar* const AttrStr =
xmlGetProp( _Node, WideStringToXMLChars( AttrName ).c_str() );
if ( AttrStr != NULL )
{
return XMLCharsToWideString( AttrStr );
}
else
{
return DefaultValue;
}
}
Так и оказалось - в документации к xmlGetProp значилось:
Returns: the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
А мой программист на xmlFree() забил.
Так как xmlGetProp() постоянно использовалась, пришлось написать для нее "smart pointer", чтобы не напрягать мозги на счет xmlFree(). Что-то типа такого:
// XMLAttr helper for automaticaly memory manage
class CXMLAttr
{
public:
CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName);
bool IsValid() const;
const std::wstring& Value() const;
private:
bool _IsValid;
std::wstring _Value;
};
inline CXMLAttr::CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName)
{
std::basic_stringName;
WideStringToXMLChars(AttributeName, Name);
xmlChar* const Attribute = xmlGetProp(Node, Name.c_str());
_IsValid = (Attribute != NULL);
if (Attribute)
{
XMLCharsToWideString(Attribute, _Value);
xmlFree(Attribute);
}
}
inline bool CXMLAttr::IsValid() const
{
return _IsValid;
}
inline const std::wstring& CXMLAttr::Value() const
{
return _Value;
}
Восстановил функциональность приложения, прогнал 2000 открытий-закрытий, перерасхода памяти не обнаружил.
09 ноября 2006
Вам тултипы какой версии?
Тестируя свое приложение под Windows 2000 обнаружил следующий баг: не работают тултипы. В XP работают, а в Win2k - нет.
Окзалось, засада ждала меня в совершенно безобидном операторе:
Объясню, почему там была засада. Для начала посмотрим как объявлен TOOLINFO (точнее tagTOOLINFOW, т.к. TOOLINFO - его алиас) в commctrl.h:
В параметрах компиляции у меня стоит _WIN32_WINNT = 0x0501, чтобы была возможность использовать все возможности WinXP, такие как XP themes и прочие. Как видно выше, при таких настройках я получаю sizeof(TOOLINFO), учитывающее lpReserved, про который Win2000 не знает. И встретив toolinfo.cbSize больше того размера TOOLINFO, которое известно Win2000, функции WinAPI отказываются работать.
Я, однако, ожидал, что WinAPI проглатывает такие структуры когда cbSize > известного текущей версии WinAPI размера структуры, а cbSize нужно наоборот, для того, чтобы более поздние версии WinAPI не пытались читать за границами структур, передаваемых старыми программами.
Ну да ладно, не проглатывает и пусть. Но как быть? Установить _WIN32_WINNT меньше 0x0501 я не могу. Остается "вручную" (вычесть указатель на tooltip из указателя на tooltip.lpReserved) считать размер TOOLINFO для Win2000 и использовать его. Здесь надо заметить, что сам я sizeof(TOOLINFO) не считаю, так как он за меня считается внутри WTL::CToolInfo::Init() ;-) Выход я, конечно найду (откажусь от WTL::CToolTipCtrlT или внесу в WTL::CToolInfo::Init() поправку), но осадок остается.
Окзалось, засада ждала меня в совершенно безобидном операторе:
toolinfo.cbSize = sizeof(TOOLINFO)Объясню, почему там была засада. Для начала посмотрим как объявлен TOOLINFO (точнее tagTOOLINFOW, т.к. TOOLINFO - его алиас) в commctrl.h:
typedef struct tagTOOLINFOW {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT_PTR uId;
RECT rect;
HINSTANCE hinst;
LPWSTR lpszText;
#if (_WIN32_IE >= 0x0300)
LPARAM lParam;
#endif
#if (_WIN32_WINNT >= 0x0501)
void *lpReserved;
#endif
} TTTOOLINFOW, NEAR *PTOOLINFOW, *LPTTTOOLINFOW;В параметрах компиляции у меня стоит _WIN32_WINNT = 0x0501, чтобы была возможность использовать все возможности WinXP, такие как XP themes и прочие. Как видно выше, при таких настройках я получаю sizeof(TOOLINFO), учитывающее lpReserved, про который Win2000 не знает. И встретив toolinfo.cbSize больше того размера TOOLINFO, которое известно Win2000, функции WinAPI отказываются работать.
Я, однако, ожидал, что WinAPI проглатывает такие структуры когда cbSize > известного текущей версии WinAPI размера структуры, а cbSize нужно наоборот, для того, чтобы более поздние версии WinAPI не пытались читать за границами структур, передаваемых старыми программами.
Ну да ладно, не проглатывает и пусть. Но как быть? Установить _WIN32_WINNT меньше 0x0501 я не могу. Остается "вручную" (вычесть указатель на tooltip из указателя на tooltip.lpReserved) считать размер TOOLINFO для Win2000 и использовать его. Здесь надо заметить, что сам я sizeof(TOOLINFO) не считаю, так как он за меня считается внутри WTL::CToolInfo::Init() ;-) Выход я, конечно найду (откажусь от WTL::CToolTipCtrlT или внесу в WTL::CToolInfo::Init() поправку), но осадок остается.
01 ноября 2006
Unit tests rule
Сегодня на собственной шкуре убедился в ценности юнит-тестов.
В одном модуле обнаружилась небольшая ошибка. Не откладывая в долгий ящик полез ее фиксить. Поглядел код модуля, внес изменения. Теперь в том случае, в котором выявилась ошибка, стало все нормально.
Но не задел ли я при этом ничего другого?
Как хорошо, что у этого модуля был полноценный юнит-тест (да, конечно, при его создании прозевали тест, который выявил бы ту ошибку, из-за которой я и полез править код, но все же тест был довольно всеобъемлющий). И запустив этот тест сразу же обнаружилась новая ошибка, которую я привнес своими исправлениями. Переосмыслив код еще раз, я убедился (запустив тест), что все пофиксил.
Не будь этого теста, количество ошибок в модуле осталось бы таким как было, просто одна ошибка сменилась на другую ;)
PS. Кстати, первым делом перед внесением каких-либо изменений в модуль я, конечно, добавил в этот тест данные, воспроизводящие обнаружившуюся ошибку. Убедился, что тест проваливается, и только тогда полез смотреть код и править его.
В одном модуле обнаружилась небольшая ошибка. Не откладывая в долгий ящик полез ее фиксить. Поглядел код модуля, внес изменения. Теперь в том случае, в котором выявилась ошибка, стало все нормально.
Но не задел ли я при этом ничего другого?
Как хорошо, что у этого модуля был полноценный юнит-тест (да, конечно, при его создании прозевали тест, который выявил бы ту ошибку, из-за которой я и полез править код, но все же тест был довольно всеобъемлющий). И запустив этот тест сразу же обнаружилась новая ошибка, которую я привнес своими исправлениями. Переосмыслив код еще раз, я убедился (запустив тест), что все пофиксил.
Не будь этого теста, количество ошибок в модуле осталось бы таким как было, просто одна ошибка сменилась на другую ;)
PS. Кстати, первым делом перед внесением каких-либо изменений в модуль я, конечно, добавил в этот тест данные, воспроизводящие обнаружившуюся ошибку. Убедился, что тест проваливается, и только тогда полез смотреть код и править его.
Подписаться на:
Сообщения (Atom)