четверг, 10 июня 2010 г.

Ускоряем виртуальную сеть Hyper-V в Windows Server 2008 R2

Сегодня обнаружил, что сервер на базе виртуальной машины Hyper-V очень медленно отдает данные. Первым делом возникло подозрение, что он просто-напросто сильно загружен, однако, эти подозрения не подтвердились - загрузка процессора составляла 2-3%, объем используемой оперативной памяти порядка 50%. Говоря простым языком, сервер "курил", но данные с него тянулись очень медленно. Естественно, такое положение вещей меня не устраивало, и я полез в сеть искать решение.

При поиске в Google информации по данной проблеме, я неизменно наталкивался на обсуждения сетевых адаптеров Intel и таинственное свойство TCP Large Send Offload (IPv4). Суть обсуждений в блогах и форумах сводилась к тому, что виртуальная сеть, построенная на базе адаптеров Intel с данным включенным свойством работала очень медленно. Несмотря на то, что у меня встроенная в материнскую плату сетевуха (какая-то модель Marvell), я решил попробовать выключить это свойство.

Люди в интернете советовали выключать свойство на виртуальной машине, но я для начала попробовал выключить его хостовом сервере. Итак, идем в диспетчер устройств, заходим в свойства сетевого адаптера, далее вкладка "Дополнительно" и для свойства "IPv4: Разгрузка большой отправки" выставляем значение "Отключено". Все эти нехитрые манипуляции я производил при продолжающимся процессе копирования данных. Не прошло и 10 секунд, как изменения были подхвачены, и данные стали копироваться на порядок быстрее.

Количество ссылок по данной теме в интернете наводит на мысль, что это очень частая проблема. Может, Майкрософт стоит включить статью по этому поводу в "Ресурсы и поддержка"? Тем не менее, проблема решена. Hyper-V и Google рулит!

пятница, 28 мая 2010 г.

Простой IoC контейнер на C++

Совсем недавно в проекте, над которым я работаю в данный момент, потребовалось использовать IoC-контейнер. У программистов на C# в этой области достаточно большой выбор (Unity, Ninject, Castle-Windsor); однако, мой проект на С++, поэтому все гораздо сложнее. Потратив некоторое время на поиски решения в Интернете, наткнулся на следующий проект: PocoCapsule, но при взгляде на размер инсталлятора (22 мб), решительно отказался от этого решения, оно показалось мне тяжеловесным. Таким образом, пришло осознание того, что придется писать IoC контейнер самому.

Решение требовалось максимальное простое, без лишних наворотов наподобие XML-файла конфигурации и т.п. Все, что требовалось от контейнера – возможность зарегистрировать в нем объекты, а затем получить эти объекты, когда они потребуют. Итак, приступим.

Первый вопрос, который мне предстояло решить – каким образом контейнер будет хранить объекты. Варианта было два. Первый – регистрировать в контейнере конкретные экземпляры классов; от этого варианта сразу пришлось отказаться, т.к. хранить в контейнере экземпляры объектов, которые, возможно, никогда не потребуются, – дорогое удовольствие. Второй вариант – регистрировать в контейнере не объекты, а механизмы создания объектов определенного типа, т.е. то, каким образом мы можем создать конкретный экземпляр требуемого объекта. Решение с указателем на функцию меня вполне устроило, на нем и остановился.

Второй вопрос, который предстояло решить, состоял в том, каким образом идентифицировать объекты в контейнере, как задать тип требуемого объекта при его получении из контейнера. Кроме того, возникла еще одна проблема – каким образом определять, что объект, лежащий в контейнере удовлетворяет нашему запросу. В качестве идентификаторов рассматривались три варианта – обычное число типа int, строка (const char* или std::string), либо type_info. Последний вариант отпал сам собой, т.к. в проекте не были включены механизмы RTTI, да и проблему соответствия объекта в контейнере запрашиваемому объекту он не решал; кроме того, оператор typeid в общем случае не гарантирует идентичности возвращаемых type_info при повторном запуске программы. Остались целое число и строка; выбор пал на число в виду его простоты, хотя вариант со строкой вполне допустим, а в некоторых случаях и более предпочтительней.

Итак, в качестве реализации идей, описанных в абзаце выше, родился интерфейс, который должны наследовать все объекты, с которыми будет работать наш контейнер. Вот его код:

struct ITyped
{
    static 
    const int TypeOf = -1;
    virtual int GetType() const =0;
    virtual bool IsA(int type) const =0;
    virtual ~ITyped() { }
};

Константа TypeOf однозначно идентифицирует класс. Думаю, не стоит говорить о том, что TypeOf у каждого класса должен в приложении быть уникальным. Метод GetType() позволяет узнать типа объекта (аналог typeid), а IsA(int type) проверяет наследует ли данный объект класс, помеченный идентификатором type(аналог оператора is из C# или instanceof из Java).

Теперь вернемся к первому вопросу, а именно, к функциям создания объектов. Первоначально планировался такой их вид:

typedef ITyped* (*OBJECT_CREATE_FUNC)(IoCContainer& iocContainer);

Такая сигнатура мне поначалу показалась удачной – в процессе создания объекта ему могут потребоваться другие объекты, которые он может получить из контейнера (а-ля DI). Однако, немного подумав, я решил добавить еще один параметр – собственно идентификатор создаваемого типа. Это позволило бы использовать одну функцию для создания целого семейства объектов. Получилось вот что:

typedef ITyped* (*OBJECT_CREATE_FUNC)(int type, IoCContainer& iocContainer);

Напомню, что все, что мне требовалось получить от контейнера – это методы регистрации и получения объектов. В данный момент мы обладаем всем необходимым, что реализовать эти требования. Вот интерфейс нашего контейнера:

class IoCContainer
{
    public:
    void Register(int type, OBJECT_CREATE_FUNC createFunc);
    ITyped* Resolve(int type);
private:
map<int, OBJECT_CREATE_FUNC> m_types;
};

Реализация тривиальна:

void IoCContainer::Register(int type, OBJECT_CREATE_FUNC createFunc)
{
    m_types[type] = createFunc;
}
ITyped* IoCContainer::Resolve(int type)
{
    map<int, OBJECT_CREATE_FUNC>::const_iterator pos =     m_types.find(type);
    if (pos == m_types.end())
    {
        // raise exception
    }
    OBJECT_CREATE_FUNC func = pos->second;
    return func(type, *this);
}

Вот пример использования созданного нами контейнера. У нас имеется интерфейс IA, реализующий его класс A:

struct IA : public ITyped
{
    static const int TypeOf = 1;
    int GetType() const { return TypeOf; }
    bool IsA(int type) const { return TypeOf == type; }
    virtual void DoSomething() =0;
};
struct A : public IA
{
    static const int TypeOf = 2;
    int GetType() const { return TypeOf; }
    bool IsA(int type) const { return TypeOf == type || IA::IsA(type); }
    static ITyped* Create(int type, IoCContainer&)
    {
        return new A();
    }
    void DoSomething()
    {
        cout << "A::DoSomething" << endl;
    }
};
int main()
{
    IoCContainer container;
    container.Register(IA::TypeOf, &A::Create);
    IA* pA = (IA*)container.Resolve(IA::TypeOf);
    pA->DoSomething();
    delete pA;
    return 0;
}

На этом можно было бы закончить, но я решил добавить немного сахара в контейнер. Мне не очень нравилась строка:

IA* pA = (IA*)container.Resolve(IA::TypeOf);

во-первых, приходится явно приводить возвращаемый объект к требуемому интерфейсу, а во-вторых зачем писать IA::TypeOf, когда можно переложить эти обязанности на компилятор. Решается данный вопрос достаточно просто – нам на помощь приходят шаблоны:

class IoCContainer
{
    public:
    template <class T> 
    void Register(OBJECT_CREATE_FUNC createFunc)
    {
        Register(T::TypeOf, createFunc);
    }
    template<class T>
    T* Resolve()
    {
        return static_cast<T*>(T::TypeOf);
    }
    void Register(int type,     OBJECT_CREATE_FUNC createFunc);
    ITyped* Resolve(int type);
private:
    map<int, OBJECT_CREATE_FUNC> m_types;
};

Как можно заметить, с помощью шаблонов мы делаем то, что раньше приходилось делать ручками. Теперь нашу функцию main можно переписать следующим образом:

int main()
{
    IoCContainer container;
    container.Register<IA>(&A::Create);
    IA* pA = container.Resolve<IA>();
    pA->DoSomething();
    delete pA;
    return 0;
}

Итак, мы достигли поставленных вначале статьи целей – наш IoC контейнер умеет регистрировать и извлекать объекты. Однако, как и все в этом мире, он не идеален, вот лишь несколько недостатков:

  1. неплохо бы сделать наш контейнер синглтоном;
  2. возлагать удаление полученных от контейнера объектов на пользователя – не лучшая идея; возможно, стоит воспользоваться каким-либо типом умного указателя (например, shared_ptr), и возвращать из контейнера умный, а не обычный указатель;
  3. при получении из контейнера каждый раз создается новый экземпляр объекта. В большинстве сценариев такое поведение не требуется, предпочтительнее, чтобы при каждом запросе возвращался указатель на однажды созданный объект. Однако, эту функциональность я реализую в следующей статье в ближайшее время.

четверг, 11 марта 2010 г.

OpenSUSE 11.1 и Virtual PC 2007 SP1

Сегодня появилась необходимость установить дистрибутив openSUSE 11.1 на виртуальную машину, работающую под управлением Microsoft Virtual PC 2007 SP1. В данный момент на сайте www.opensuse.org доступна версия 11.2, однако, ее под рукой не оказалось, пришлось устанавливать 11.1.

При установке у меня возникли проблемы - после выбора пункта меню Installation в загрузочном диске виртуальная машина радовала меня черным экраном и никак не реагировала на мои действия. После недолгих поисков в Интернете нашлось решение - в параметрах запуска установки следует прописать следующую строку:

noreplace-paravirt i8042.noloop clock=pit

Поступаем подобным образом и оп-ля установка запускается ))) После нескольких десятков минут ожиданий имеем установленный openSUSE 11.1 на Microsoft Virtual PC 2007 SP1.