Очень точно здесь автор описал то, как решить казалось бы неразрешимую проблему: – отвлечься; – еще раз отвлечься; – поесть, поспать, погулять, почитать – всё, что угодно, но только не думать о самой проблеме.
И потом хоп… и озарение!
Очень точно здесь автор описал то, как решить казалось бы неразрешимую проблему: – отвлечься; – еще раз отвлечься; – поесть, поспать, погулять, почитать – всё, что угодно, но только не думать о самой проблеме.
И потом хоп… и озарение!
Очень забавная история. Был работающий и написанный сервер вещаний (a-la Red5) на Python. В связи с развитием основной класс – класс протокола – был разрезан на несколько более мелких классов, которые собирались вместе множественным наследованием в единый монолит, который должен был по функциональности повторять исходный класс (до рефакторинга). Как показало вскрытие, старая версия стабильно работает неделю за неделей, а новая версия через неопределенный, но достаточно короткий период времени (несколько часов) откушивает всю доступную память и погибает.
Типичная ситуация для memory leak, причём опытным путём стало понятно, что эта утечка как-то коррелирует с нагрузкой на сервер (что логично), но зависимость неоднозначная. Момент появления утечки был сужен до одного коммита – казалось бы дело за малым. Анализ исходников в поисках того момента, когда была внесена утечка ничего дает – исходники испаханы вдоль и поперек. Ничего.
Анализ во время выполнения с помощью с помощью довольно замысловатых утилит из этого поста
ни к чему не привёл – нет ничего. Я пробовал считать количество объектов, которые видит модуль gc в питоне (то есть тех, кто поддерживает garbage collection), но количество не растет существенно и пропорционально нагрузке на сервер. Однако память процесс кушает и довольно быстро.
В проекте было расширение, написанное на Си, – его анализ, исправление мелких неточностей результата не дает.
Остается неутешительный вывод – в силу того, что число объектов в программе не растет, то значит либо растет размер каких-то объектов (например, строк путем конкатенации), либо это какая-то бага в Python, ОС или где-то еще.
Последнее выглядит романтично и привлекательно, но на самом деле ничего не дает – такую ошибку еще тяжелее найти и поправить, однако багтрекеры FreeBSD, Python и Twisted молчат по поводу таких ошибок.
Двигаемся дальше – создается тестовый клиент вещаний, который имитирует нагрузку на сервер: разное количество вещаний, авторы, подписчики вещания, различные ошибочные ситуации, чат. Тест, запущенный локально с локальным сервером (а нелокально тестовое окружение собрать тяжело – поток порядка 50-100 Мбит/с) не дает результатов – никаких следов утечки памяти ни за час, ни за два.
Отчаяние, практически неделя потерянного впустую времени, тестирование различных вариантов… Что еще? Есть еще Twisted-Conch+Twisted-Manhole – живая консоль с интерпретатором питона в работающем сервере. Но и она не дает никаких результатов – garbage collection работает, никаких разумных объектов, которые могли бы дать утечку памяти нет.
Озарение как всегда приходит в тот момент, когда его совсем не ждешь: ведь сервер вещает, то есть получает на вход поток в 0,5 Мбит/с, раздает его 200 клиентам и делает на выходе 100 Мбит/с, если у кого-то из этих 200 клиентов канал меньше по ширине 0,5 Мбит/с, то данные в буферах процессах начнут скапливаться. Для этого есть решение – дроп пакетов, он был реализован и работал…
Эврика! После разделения большого класса на кучу мелких фрагментов некоторые методы «разрезались» на части. И в них надо поставить вызов метода предка, чтобы все «куски» метода отработали, как и раньше. Но ведь в питоне есть еще MRO (Method Resolution Order), и надо отвыкнуть от того, что базовый класс в моём ощущении является последним в цепочке, то есть у него нет super(Klass, self) – он есть! Просто метод, который включал анализ буфера записи, который в свою очередь вовремя включал механизм пропуска пакетов отключился. И хотя всё работало, но пропуск пакетов не включался и буферы записи росли неограниченно. Исправление в одной строке – вызова метода предка решило проблему. Обидно
Что выведет данный код?
#include <stdio.h>
struct A
{
A(int x = 3) { printf("%d\n", x); }
};
struct B: virtual public A
{
B() : A(4) {}
};
struct C: virtual public A
{
C() : A(5) { }
};
struct D: public B, public C
{
};
int main()
{
D d;
B b;
C c;
return 0;
}
Ответ:
3
4
5
Что самое смешное, можно из D::D() явно вызвать конструктор A с другим параметром. Наслаждаемся в C++ смесью виртуального наследования, раздельной компиляции и полученной кривости.
Бывший (?) менеджер MicroSoft рассказывает о program management в США, MicroSoft, отдельных софтверных компаниях.
Видео:
Что происходит, когда мы пишем A a(3), A a = 3 и A a = A(3)? Меня этот вопрос давно интересовал. Конечно, у класса должны быть корректные конструкторы, в том числе и копирования, операторы присваивания и т.п.
Проведем следственный эксперимент…
#include <stdio.h>
class A
{
public:
A()
{
printf("A::A()\n");
}
A(int)
{
printf("A::A(int)\n");
}
A(int, int)
{
printf("A::A(int, int)\n");
}
A(const A& a)
{
printf("A::A(const A& a)\n");
}
void operator=(const A& a)
{
printf("A::operator=(const A& a)\n");
}
};
int main()
{
printf("=> A a1;\n");
A a1;
printf("=> A a2(3);\n");
A a2(3);
printf("=> A a3;\n");
A a3 = 3;
printf("=> A a4 = A(3);\n");
A a4 = A(3);
printf("=> A a5; a5 = A(3);\n");
A a5; a5 = A(3);
printf("=> A a6(a1);\n");
A a6(a1);
printf("=> A a7(4,4);\n");
A a7(4, 4);
printf("=> A a8 = A(4,4);\n");
A a8 = A(4, 4);
return 0;
}
Выведет на экран:
=> A a1;
A::A()
=> A a2(3);
A::A(int)
=> A a3;
A::A(int)
=> A a4 = A(3);
A::A(int)
=> A a5; a5 = A(3);
A::A()
A::A(int)
A::operator=(const A& a)
=> A a6(a1);
A::A(const A& a)
=> A a7(4,4);
A::A(int, int)
=> A a8 = A(4,4);
A::A(int, int)
Т.е. каждый раз вызывается обычный конструктор и все три формы, приведённые в начале поста, эквивалентны.
Однако стоит нам конструктор копирования объявить private, и сразу же получим вот это:
[smir@smira Teaching]$ g++ test.cxx
test.cxx: In function ‘int main()’:
test.cxx:22: ошибка: ‘A::A(const A&)’ is private
test.cxx:39: ошибка: в данном контексте
test.cxx:22: ошибка: ‘A::A(const A&)’ is private
test.cxx:39: ошибка: в данном контексте
test.cxx:39: ошибка: initializing temporary from result of ‘A::A(int)’
test.cxx:22: ошибка: ‘A::A(const A&)’ is private
test.cxx:41: ошибка: в данном контексте
test.cxx:22: ошибка: ‘A::A(const A&)’ is private
test.cxx:41: ошибка: в данном контексте
Строка 22 – это объявление конструктора копирования, а 39 и 41 это обращения A a3 = 3 и A a4 = A(3);. Интересно получается, что хотя конструктор копирования и не вызывается, но эти формы требуют его открытости. Это стандарт C++, оптимизация gcc или что-то ещё?
Вот я знаю, что юнит-тесты и документирование кода – это чудо. Правда, это чудесные идеи, которые потрясающе повышают эффективность работы. Но это знаю я, я этим активно пользуюсь, не могу без этого уже работать.
Как объяснить это другим? Как их «зажечь» этой возможностью?
Ведь и то, и другое лень. На это нужно время… Объяснить на пальцах, что и то, и другое, экономит время, очень тяжело. Программисту кажется, что кроме выполнения его основных обязанностей – написания кода – его заставляют делать что-то ещё, что непосредственно не является видимым результатом его деятельности. Да, конечные пользователи продукта не увидят юнит-тестов и их совершенно не будет интересовать документация по коду. Но ведь эти средства позволяют (при грамотном использовании, конечно) улучшить качество продукта, а это конечные пользователи заметят, и именно это должно служить основной мотивацией для разработчика.
Мне кажется, что документация – это сродни чистоплотности, это то же самое, что и форматирование (оформление) кода, это ещё один аспект читаемости кода. Если я уважаю человека, который будет читать мой код, я должен его красиво оформить, снабдить комментариями. Ведь мы моемся, одеваем чистую одежду, чтобы другим людям не было противно с нами общаться. И здесь наш код – одежка, по которой нас встречают.
Так как всё-таки передать другим ощущение чуда? Мне кажется, очень важно показать пример, показать результат деятельности. Вот, смотри, давай построим документацию по коду – красота… Вот, этот модуль полностью отдокументирован и он выглядит так классно. А вот твой файл… Добавишь комментарии – и ведь он будет такой же! Юнит-тесты так же. Показать пример, как увеличивается скорость разработки от применения юнит-тестов!
В связи с нашими разработками, в том числе кроссплатформенных приложений, я озаботился возможностью как-то организовать процесс компиляции, прохождения unit-тестов, различных артефактов разработки, например, документации по коду. Полезно иметь такую вещь: часть программистов использует Windows, там не все утилиты работают нормально или их настроить бывает непросто. Ну и оттестировать своё приложение нескольких архитектурах после каждого комита бывает непросто.
После небольшого осмотра того, что нам может предложить Internet из open-source проектов были отобраны кандидаты на роль continuous integration-средства:
Меня зовут Андрей Смирнов, я работаю техническим директором компании НетСтрим. Если говорить проще, я руковожу разработкой программных продуктов. Наша компания разработала много интересных проектов, например, LoadUp.
В этом блоге я собираюсь рассказывать о вещах, которые так или иначе связаны с программированием или с интересными событиями в моей жизни.
В качестве хобби я преподаю в Московском Государственном Университете им. М.В. Ломоносова на факультете Вычислительной математики и кибернетики.