Как избежать последовательного освобождения/выделений в C++?

Рассмотрите следующий код:

class A
{
    B* b; // an A object owns a B object

    A() : b(NULL) { } // we don't know what b will be when constructing A

    void calledVeryOften(…)
    {
        if (b)
            delete b;

        b = new B(param1, param2, param3, param4);
    }
};

Моя цель: Я должен максимизировать производительность, которая, в этом случае, означает минимизировать выделения объема памяти.

Очевидная вещь сделать здесь состоит в том, чтобы измениться B* b; к B b;. Я вижу две проблемы с этим подходом:

  • Я должен инициализировать b в конструкторе. Так как я не знаю что b будет, это означает, что я должен передать фиктивные значения конструктору B. Который, IMO, ужасно.
  • В calledVeryOften(), Я должен буду сделать что-то вроде этого: b = B(…), который является неправильным по двум причинам:
    • Деструктор b не будет назван.
    • Временный экземпляр B будет создан, затем скопирован в b, тогда деструктор временного экземпляра назовут. Копии и вызова деструктора можно было избежать. Хуже, называя деструктор мог очень хорошо привести к нежелательному поведению.

Таким образом, что решения, я должен избегать использования new? Следует иметь в виду что:

  • Я только управляю A. Я не управляю B, и я не управляю пользователями A.
  • Я хочу сохранить код максимально чистым и читаемым.
13
задан Etienne Dechamps 2 February 2010 в 20:04
поделиться

10 ответов

Мне понравился ответ Клаима, поэтому я написал его очень быстро. Я не претендую на идеальную правильность, но мне это кажется неплохим. (т.е. единственное тестирование, которое он имеет, - это образец main ниже)

Это общий ленивый инициализатор. Пространство для объекта выделяется один раз, и объект начинается с нуля. Затем вы можете создать , перезаписав предыдущие объекты, без выделения новой памяти.

Он реализует все необходимые конструкторы, деструктор, копирование / присваивание, свопинг, ядда-ядда. Вот и все:

#include <cassert>
#include <new>

template <typename T>
class lazy_object
{
public:
    // types
    typedef T value_type;
    typedef const T const_value_type;
    typedef value_type& reference;
    typedef const_value_type& const_reference;
    typedef value_type* pointer;
    typedef const_value_type* const_pointer;

    // creation
    lazy_object(void) :
    mObject(0),
    mBuffer(::operator new(sizeof(T)))
    {
    }

    lazy_object(const lazy_object& pRhs) :
    mObject(0),
    mBuffer(::operator new(sizeof(T)))
    {
        if (pRhs.exists())
        {
            mObject = new (buffer()) T(pRhs.get());
        }
    }

    lazy_object& operator=(lazy_object pRhs)
    {
        pRhs.swap(*this);

        return *this;
    }

    ~lazy_object(void)
    {
        destroy();
        ::operator delete(mBuffer);
    }

    // need to make multiple versions of this.
    // variadic templates/Boost.PreProccesor
    // would help immensely. For now, I give
    // two, but it's easy to make more.
    void create(void)
    {
        destroy();
        mObject = new (buffer()) T();
    }

    template <typename A1>
    void create(const A1 pA1)
    {
        destroy();
        mObject = new (buffer()) T(pA1);
    }

    void destroy(void)
    {
        if (exists())
        {
            mObject->~T();
            mObject = 0;
        }
    }

    void swap(lazy_object& pRhs)
    {
        std::swap(mObject, pRhs.mObject);
        std::swap(mBuffer, pRhs.mBuffer);
    }

    // access
    reference get(void)
    {
        return *get_ptr();
    }

    const_reference get(void) const
    {
        return *get_ptr();
    }

    pointer get_ptr(void)
    {
        assert(exists());
        return mObject;
    }

    const_pointer get_ptr(void) const
    {
        assert(exists());
        return mObject;
    }

    void* buffer(void)
    {
        return mBuffer;
    }

    // query
    const bool exists(void) const
    {
        return mObject != 0;
    }

private:
    // members
    pointer mObject;
    void* mBuffer;
};

// explicit swaps for generality
template <typename T>
void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
{
    pLhs.swap(pRhs);
}

// if the above code is in a namespace, don't put this in it!
// specializations in global namespace std are allowed.
namespace std
{
    template <typename T>
    void swap(lazy_object<T>& pLhs, lazy_object<T>& pRhs)
    {
        pLhs.swap(pRhs);
    }
}

// test use
#include <iostream>

int main(void)
{
    // basic usage
    lazy_object<int> i;
    i.create();
    i.get() = 5;

    std::cout << i.get() << std::endl;

    // asserts (not created yet)
    lazy_object<double> d;
    std::cout << d.get() << std::endl;
}

В вашем случае просто создайте члена в своем классе: lazy_object и все готово. Никаких ручных выпусков или создания копий-конструкторов, деструкторов и т. Д. Обо всем позаботятся в вашем красивом небольшом повторно используемом классе. :)

РЕДАКТИРОВАТЬ

Устранена необходимость в векторе, это должно сэкономить немного места и все такое.

РЕДАКТИРОВАТЬ 2

Здесь используются align_storage и alignment_of для использования стека вместо кучи. Я использовал boost , но эта функция существует как в TR1, так и в C ++ 0x. Мы теряем возможность копирования, а значит и свопинга.

#include <boost/type_traits/aligned_storage.hpp>
#include <cassert>
#include <new>

template <typename T>
class lazy_object_stack
{
public:
    // types
    typedef T value_type;
    typedef const T const_value_type;
    typedef value_type& reference;
    typedef const_value_type& const_reference;
    typedef value_type* pointer;
    typedef const_value_type* const_pointer;

    // creation
    lazy_object_stack(void) :
    mObject(0)
    {
    }

    ~lazy_object_stack(void)
    {
        destroy();
    }

    // need to make multiple versions of this.
    // variadic templates/Boost.PreProccesor
    // would help immensely. For now, I give
    // two, but it's easy to make more.
    void create(void)
    {
        destroy();
        mObject = new (buffer()) T();
    }

    template <typename A1>
    void create(const A1 pA1)
    {
        destroy();
        mObject = new (buffer()) T(pA1);
    }

    void destroy(void)
    {
        if (exists())
        {
            mObject->~T();
            mObject = 0;
        }
    }

    // access
    reference get(void)
    {
        return *get_ptr();
    }

    const_reference get(void) const
    {
        return *get_ptr();
    }

    pointer get_ptr(void)
    {
        assert(exists());
        return mObject;
    }

    const_pointer get_ptr(void) const
    {
        assert(exists());
        return mObject;
    }

    void* buffer(void)
    {
        return mBuffer.address();
    }

    // query
    const bool exists(void) const
    {
        return mObject != 0;
    }

private:
    // types
    typedef boost::aligned_storage<sizeof(T),
                boost::alignment_of<T>::value> storage_type;

    // members
    pointer mObject;
    storage_type mBuffer;

    // non-copyable
    lazy_object_stack(const lazy_object_stack& pRhs);
    lazy_object_stack& operator=(lazy_object_stack pRhs);
};

// test use
#include <iostream>

int main(void)
{
    // basic usage
    lazy_object_stack<int> i;
    i.create();
    i.get() = 5;

    std::cout << i.get() << std::endl;

    // asserts (not created yet)
    lazy_object_stack<double> d;
    std::cout << d.get() << std::endl;
}

И поехали.

8
ответ дан 1 December 2019 в 20:42
поделиться

Просто оформируйте память, необходимую для B (через пул или вручную) и повторно используйте его каждый раз, когда вы удаляете / новое вместо перераспределения каждый раз.

Пример:

class A
{
    B* b; // an A object owns a B object
    bool initialized;
public:
    A() : b( malloc( sizeof(B) ) ), initialized(false) { } // We reserve memory for b
    ~A() { if(initialized) destroy(); free(b); } // release memory only once we don't use it anymore

    void calledVeryOften(…)
    {
        if (initialized)
            destroy();

        create();
    }

 private:

    void destroy() { b->~B(); initialized = false; } // hand call to the destructor
    void create( param1, param2, param3, param4 )
    {
        b = new (b) B( param1, param2, param3, param4 ); // in place new : only construct, don't allocate but use the memory that the provided pointer point to
        initialized = true;
    }

};

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

Стоимость строительства / разрушения будет тогда только зависимым от конструктора и деструктора класса B.

8
ответ дан 1 December 2019 в 20:42
поделиться

Как насчет выделения памяти для B один раз (или для его наибольшего возможного варианта) и используя Размещение Новое ?

A будет хранить Char Memb [SizeOf (Big Gestb)]; и B * . Конечно, вам нужно будет вручную вызвать деструкторы, но память не будет выделена / освобождена.

   void* p = memB;
   B* b = new(p) SomeB();
   ...
   b->~B();   // explicit destructor call when needed.
5
ответ дан 1 December 2019 в 20:42
поделиться

Если B корректно реализует его оператор назначения копирования b = b ( ...) Не следует вызывать любой деструктор на b . Это самое очевидное решение вашей проблемы.

Если, однако, B B не может быть соответствующим образом «по умолчанию» инициализирован вами может сделать что-то вроде этого. Я бы только рекомендовал этот подход как последний курорт, так как очень трудно получить безопасную. Непроверенные, и очень, вероятно, с угловыми случаями исключения Bugs:

// Used to clean up raw memory of construction of B fails
struct PlacementHelper
{
    PlacementHelper() : placement(NULL)
    {
    }

    ~PlacementHelper()
    {
        operator delete(placement);
    }

    void* placement;
};

void calledVeryOften(....)
{
    PlacementHelper hp;

    if (b == NULL)
    {
        hp.placement = operator new(sizeof(B));
    }
    else
    {
        hp.placement = b;
        b->~B();
        b = NULL;  // We can't let b be non-null but point at an invalid B
    }

    // If construction throws, hp will clean up the raw memory
    b = new (placement) B(param1, param2, param3, param4);

    // Stop hp from cleaning up; b points at a valid object
    hp.placement = NULL;
}
3
ответ дан 1 December 2019 в 20:42
поделиться

Также можно использовать БД Berkley. Он хорошо работает с программами C и реализует дерево B +.

-121--3995725-

попробуйте добавить атрибут [Авторизовать] к методам действия

-121--2663127-

Вы уверены, что выделение памяти является узким местом? Является ли конструктор B тривиально быстрым?

Если выделение памяти является реальной проблемой, то размещение новых или некоторых других решений здесь вполне может помочь.

Если типы и диапазоны парама [1.. 4] являются разумными, а конструктор B «тяжелым», вы также можете рассмотреть возможность использования кэшированного набора B. Это предполагает, что вам фактически разрешено иметь более одного за раз, что он не является фронтальным ресурсом, например.

1
ответ дан 1 December 2019 в 20:42
поделиться

ERM, есть ли какой-то причиной, вы не можете это сделать?

A() : b(new B()) { }

void calledVeryOften(…) 
{
    b->setValues(param1, param2, param3, param4); 
}

(или установить их индивидуально, поскольку у вас нет доступа к B класс - эти ценности DO имеют мутатор-методы, верно?)

0
ответ дан 1 December 2019 в 20:42
поделиться
-

Как и другие уже предложили: попробуйте разместить новый ..

Вот полный пример:

#include <new>
#include <stdio.h>

class B
{
  public:
  int dummy;

  B (int arg)
  {
    dummy = arg;
    printf ("C'Tor called\n");
  }

  ~B ()
  {
    printf ("D'tor called\n");
  }
};


void called_often (B * arg)
{
  // call D'tor without freeing memory:
  arg->~B();

  // call C'tor without allocating memory:
  arg = new(arg) B(10);
}

int main (int argc, char **args)
{
  B test(1);
  called_often (&test);
}
1
ответ дан 1 December 2019 в 20:42
поделиться

Просто возьмите кучу ранее использованных B и используйте их повторно.

0
ответ дан 1 December 2019 в 20:42
поделиться

Предпочтительной альтернативой скриптлетам является язык выражения JSTL; здесь - хороший обзор. Вам нужно будет добавить tallib так:

<%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>

В качестве примера, JSTL предоставляет кучу неявных объектов, которые дают вам материал, который вам нужен; требуется pageContext.request .

Таким образом, можно заменить <% request.getRequestURI% > на $ {pageContext.request.requestURI} .

Можно выполнить условия с помощью < c: if > тэги.

-121--2526270-

В первую очередь - вы правы, что вам нужно использовать класс java.sql.Timestamp, так как класс java.sql.Date явно не представляет определенное время дней (скорее, он пытается представить полночь по Гринвичу).

Что касается вашей ошибки - вы не предоставили достаточно информации, чтобы сделать что-то большее, чем догадаться (для фактического определения причины потребуется посмотреть как конфигурацию Hibernate, так и класс). Однако , если вы просто изменили класс поля в классе Java, то, конечно, вам также придется обновить сопоставление Hibernate. Если вы не сделали последнее, это, вероятно, приведет к вашему несоответствию. Попробуйте явно указать type = «timestamp» для соответствующего сопоставления.

EDIT: Если вы используете аннотации, вы обновили аннотацию для этого свойства на @ Temporal (TemporalType.TIMESTAMP) ? Если вы этого не сделали, то вам нужно будет (а если вы это сделали, вы должны были так сказать: -)).

-121--3113853-

Быстрая проверка утверждения Мартина Йорка о том, что это преждевременная оптимизация, и что новые/удаленные оптимизированы далеко за пределы способности простых программистов улучшить. Очевидно, что вопроснику придется успеть свой собственный код, чтобы увидеть, помогает ли ему избегание нового/удаления, но мне кажется, что для определенных классов и его использования это будет иметь большое значение:

#include <iostream>
#include <vector>

int g_construct = 0;
int g_destruct = 0;

struct A {
    std::vector<int> vec;
    A (int a, int b) : vec((a*b) % 2) { ++g_construct; }
    ~A() { 
        ++g_destruct; 
    }
};

int main() {
    const int times = 10*1000*1000;
    #if DYNAMIC
        std::cout << "dynamic\n";
        A *x = new A(1,3);
        for (int i = 0; i < times; ++i) {
            delete x;
            x = new A(i,3);
        }
    #else
        std::cout << "automatic\n";
        char x[sizeof(A)];
        A* yzz = new (x) A(1,3);
        for (int i = 0; i < times; ++i) {
            yzz->~A();
            new (x) A(i,3);
        }
    #endif

    std::cout << g_construct << " constructors and " << g_destruct << " destructors\n";
}

$ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
automatic
10000001 constructors and 10000000 destructors

real    0m7.718s
user    0m7.671s
sys     0m0.030s

$ g++ allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
dynamic
10000001 constructors and 10000000 destructors

real    0m15.188s
user    0m15.077s
sys     0m0.047s

Это примерно то, что я ожидал: код в стиле GMan (destruct/placement new) занимает вдвое больше времени, и, предположительно, делает вдвое больше распределения. Если векторный член A заменен на int, то код в стиле GMan занимает долю секунды. Это GCC 3.

$ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=1 -g && time ./allocperf
dynamic
10000001 constructors and 10000000 destructors

real    0m5.969s
user    0m5.905s
sys     0m0.030s

$ g++-4 allocperf.cpp -oallocperf -O3 -DDYNAMIC=0 -g && time ./allocperf
automatic
10000001 constructors and 10000000 destructors

real    0m2.047s
user    0m1.983s
sys     0m0.000s

Но я не уверен в этом: теперь удаление/новая версия занимает в три раза больше времени, чем уничтожение/размещение новой версии.

[Edit: Я думаю, я понял это - GCC 4 быстрее на 0-размерных векторах, фактически вычитая постоянное время из обеих версий кода. При изменении (a * b)% 2 на (a * b)% 2 + 1 восстанавливается отношение времени 2:1: 3,7s против 7,5]

Обратите внимание, что я не предпринял никаких специальных шагов для правильного выравнивания массива стека, но при печати адреса он выровнен по 16.

Кроме того, -g не влияет на время. Я оставил его случайно после того, как посмотрел на обломок, чтобы проверить, что -O3 не полностью снял петлю. Указатели назвали yzz, потому что поиск «y» прошел не так хорошо, как я надеялся. Но я только что сбежал без этого.

3
ответ дан 1 December 2019 в 20:42
поделиться

См. ответы на CSS/JavaScript Используйте Div для серого на странице .

-121--3853742-

Сначала необходимо разделить прочитанное здесь и там о транзакциях SQL Server на 2 отдельных случая: локальный и распределенный.

Локальные транзакции SQL :

  • SQL Server разрешает выполнение только одного запроса для каждой локальной транзакции.
  • По умолчанию только один сеанс может быть зарегистрирован в локальной транзакции. Использование sp_getbindtoken и sp_bindsession нескольких сеансов может быть зарегистрировано в локальной транзакции. Сеансы все еще ограничены только одним, выполняющим запрос в любое время.
  • С помощью нескольких активных результирующих наборов (MARS) один сеанс может выполнять несколько запросов. Все запросы должны быть зарегистрированы в одной локальной транзакции.

Распределенные транзакции :

  • Для нескольких сеансов локальная транзакция может быть зарегистрирована в одной распределенной транзакции.
  • Каждый сеанс по-прежнему включен в локальную транзакцию с учетом всех ограничений, упомянутых выше для локальных транзакций
  • Локальные транзакции, включенные в распределенную транзакцию, подлежат двухфазной фиксации, координируемой распределенной транзакцией
  • . Все локальные транзакции в экземпляре, зарегистрированном в распределенной транзакции, по-прежнему являются независимыми локальными транзакциями, что в первую очередь означает, что они имеют конфликтующие пространства имен блокировок.

Таким образом, когда клиент создает .Net TransacityScope и под этой областью транзакции выполняет несколько запросов на одном сервере, все эти запросы являются локальными транзакциями, зарегистрированными в распределенной транзакции. Простой пример:

class Program
    {
        static string sqlBatch = @"
set nocount on;
declare @i int;
set @i = 0;
while @i < 100000
begin
    insert into test (a) values (replicate('a',100));
    set @i = @i+1;
end";

        static void Main(string[] args)
        {
            try
            {
                TransactionOptions to = new TransactionOptions();
                to.IsolationLevel = IsolationLevel.ReadCommitted;
                using (TransactionScope scp = new TransactionScope(TransactionScopeOption.Required, to))
                {
                    using (SqlConnection connA = new SqlConnection(Settings.Default.connString))
                    {
                        connA.Open();
                        using (SqlConnection connB = new SqlConnection(Settings.Default.connString))
                        {
                            connB.Open();

                            SqlCommand cmdA = new SqlCommand(sqlBatch, connA);
                            SqlCommand cmdB = new SqlCommand(sqlBatch, connB);

                            IAsyncResult arA = cmdA.BeginExecuteNonQuery();
                            IAsyncResult arB = cmdB.BeginExecuteNonQuery();

                            WaitHandle.WaitAll(new WaitHandle[] { arA.AsyncWaitHandle, arB.AsyncWaitHandle });

                            cmdA.EndExecuteNonQuery(arA);
                            cmdB.EndExecuteNonQuery(arB);
                        }
                    }
                    scp.Complete();
                }
            }
            catch (Exception e)
            {
                Console.Error.Write(e);
            }
        }
    }

Создайте фиктивную тестовую таблицу

create table test (id int not null identity(1,1) primary key, a varchar(100));

и запустите код в образце. Вы увидите, что оба запроса выполняются параллельно, каждый из которых вставляет 100 000 строк в таблицу, затем оба фиксируют, когда область транзакции завершена. Таким образом, проблемы, которые вы видите, не связаны ни с SQL Server, ни с TransacityScope, они могут легко справиться с описанным сценарием. Более того, код очень прост и прост, и нет необходимости создавать зависимые транзакции, клонировать или продвигать транзакции.

Обновлено

Использование явных потоков и зависимых транзакций:

 private class ThreadState
    {
        public DependentTransaction Transaction {get; set;}
        public EventWaitHandle Done {get; set;}
        public SqlConnection Connection { get; set; }
    }
    static void Main(string[] args)
    {
        try
        {
            TransactionOptions to = new TransactionOptions();
            to.IsolationLevel = IsolationLevel.ReadCommitted;
            using (TransactionScope scp = new TransactionScope(TransactionScopeOption.Required, to))
            {
                ThreadState stateA = new ThreadState 
                {
                    Transaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete),
                    Done = new AutoResetEvent(false),
                    Connection = new SqlConnection(Settings.Default.connString),
                };
                stateA.Connection.Open();
                ThreadState stateB = new ThreadState
                {
                    Transaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete),
                    Done = new AutoResetEvent(false),
                    Connection = new SqlConnection(Settings.Default.connString),
                };
                stateB.Connection.Open();

                ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), stateA);
                ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), stateB);

                WaitHandle.WaitAll(new WaitHandle[] { stateA.Done, stateB.Done });

                scp.Complete();

                //TODO: dispose the open connections
            }

        }
        catch (Exception e)
        {
            Console.Error.Write(e);
        }
    }

    private static void Worker(object args)
    {
        Debug.Assert(args is ThreadState);
        ThreadState state = (ThreadState) args;
        try
        {
            using (TransactionScope scp = new TransactionScope(state.Transaction))
            {
                SqlCommand cmd = new SqlCommand(sqlBatch, state.Connection);
                cmd.ExecuteNonQuery();
                scp.Complete();
            }
            state.Transaction.Complete();
        }
        catch (Exception e)
        {
            Console.Error.WriteLine(e);
            state.Transaction.Rollback();
        }
        finally
        {
            state.Done.Set();
        }

    }
-121--4321020-

Я бы пошел с boost:: scoped _ ptr здесь:

class A: boost::noncopyable
{
    typedef boost::scoped_ptr<B> b_ptr;
    b_ptr pb_;

public:

    A() : pb_() {}

    void calledVeryOften( /*…*/ )
    {
        pb_.reset( new B( params )); // old instance deallocated
        // safely use *pb_ as reference to instance of B
    }
};

Нет необходимости в ручном деструкторе, A не является копируемым, как это должно быть в вашем оригинальном коде, не утечка памяти на

Я бы предложил переосмыслить дизайн, хотя если вам нужно очень часто перераспределять какой-то внутренний государственный объект. Ознакомьтесь с параметрами Flyweight и State узоров.

0
ответ дан 1 December 2019 в 20:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: