Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Общие вопросы _ Создание быстродействующего распределителя памяти

Автор: AD 29.6.2009, 10:38

Раньше был обычный массив (динамический), который содержал значения определенных параметров, прочитанных из лог-файла (широта, долгота, скорость и пр.). Сейчас сделал в виде вектора. Вместо оператора new теперь использую resize. Но так как эти операции чересчур затратные (а цикл может содержать и 100000 итераций (где каждый раз происходит выделение)), то стало необходимо сделать алгоритм немного по-другому.
Выделять память блоками. Т.е. вместо след. операций:
code1

t.params = new PARAMVALUE[rec_descr.size()];
t.words = new uint[rec_descr.size()];
memset(t.params, 0, sizeof(PARAMVALUE) * rec_descr.size());
memset(t.words, 0, sizeof(uint) * rec_descr.size());

Делать следующее:
code2
/// 0 <= num_block <= 1000
char *buff, *buff1;
if(num_block > 1000)
{
buff = new char[rec_descr.size() * BLOCKSIZE];
buff1 = new char[rec_descr.size() * BLOCKSIZE];
flag = false;
num_block = 0;
}
++num_block;
t.params = buff + rec_descr.size() * num_block;
t.words = buff1 + rec_descr.size() * num_block;


При замене на QVector code1 перешел в следующий:
code3
t.params.resize(rec_descr.size());
t.words.resize(rec_descr.size());


Как переписать code2 для использования в векторе QVector? Заранее благодарен за помощь....

Автор: Tonal 29.6.2009, 10:53

Интересно, в code2 при num_block <= 1000 t.params и t.words будут указывать куда попало или ты опустил кусок кода?

Ну а вообще, контейнерные классы обычно при надобности увеличивают свой размер с запасом.
Например std::vector увеличивает свой размер в 1.5 - 2 раза по сравнению с текущим, поэтому последовательные push_bask довольно дёшевы.

Автор: AD 29.6.2009, 11:10

Цитата(Tonal)
Интересно, в code2 при num_block <= 1000 t.params и t.words будут указывать куда попало или ты опустил кусок кода?

Да это приближенный кусок кода, пока только на бумаге написанный.
Если делать по-разумному, то будет где-то так:
if(!num_block)
{
/// те операции, которые делались, если num_block > 1000
}
if(num_block > 1000)
         num_block = 0;


Цитата(Tonal)
Ну а вообще, контейнерные классы обычно при надобности увеличивают свой размер с запасом.
Например std::vector увеличивает свой размер в 1.5 - 2 раза по сравнению с текущим, поэтому последовательные push_bask довольно дёшевы.

Дело в том, что мне нельзя делать push_back или для QVector append, потому что необходимо, чтобы широта или долгота была именно под тем же индексом, что и в файле описания параметров. t.params - хранит значение параметра. А rec_descr - вектор описания параметров.

Автор: Litkevich Yuriy 29.6.2009, 11:36

Цитата(AD @ 29.6.2009, 14:38) *
Выделять память блоками.

Цитата(Tonal @ 29.6.2009, 14:53) *
Ну а вообще, контейнерные классы обычно при надобности увеличивают свой размер с запасом.
http://doc.crossplatform.ru/qt/4.6.x/containers.html#growth-strategies

Автор: SABROG 29.6.2009, 11:43

Кстати интересный класс есть QVarLengthArray, если я правильно понял, то он до какого-то момента выделяет память в стеке, а после превышения некоторого размера начинает выделять память в куче. Якобы операция выделения памяти в стеке быстрее по скорости, чем new.

Автор: AD 29.6.2009, 11:59

Цитата(Litkevich Yuriy @ 29.6.2009, 12:36) *
http://doc.crossplatform.ru/qt/4.6.x/containers.html#growth-strategies

Ну с запасом в 2 раза. Но не через 1000, а через 2000 увеличивать, но придется. Я не очень знаю как, потому и прошу помочь. Ведь кусочек кода совсем маленький....

Автор: AD 29.6.2009, 13:25

Очень прошу помочь. Как реализовать код для QVector? Делать выделение, когда счетчик будет более 2000? Ну хоть как-то помогите, пожалуйста!

Автор: Litkevich Yuriy 29.6.2009, 13:34

Цитата(AD @ 29.6.2009, 17:25) *
Делать выделение, когда счетчик будет более 2000?
попробуй так, да посмотри какой эффект будет.

Автор: AD 29.6.2009, 14:05

Цитата(Litkevich Yuriy @ 29.6.2009, 14:34) *
попробуй так, да посмотри какой эффект будет.

Хорошо, а вот саму эту штуку как сделать для QVector: именно ее, я не знаю как изобразить в коде для вектора:
// param - обычный массив
if(/*<Условие>*/)
{
     buff = new char[/**/];
}
t.params = buff + rec_descr.size() * num_block;
t.words = buff1 + rec_descr.size() * num_block;

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

Автор: Litkevich Yuriy 29.6.2009, 15:19

по ссылке, что я привёл, дочитай до конца. Там есть рекомендации "которые позволяют контролировать и задавать столько памяти, сколько вам нужно"

Автор: AD 29.6.2009, 16:14

Юра, мне непонятна одна конкретная вещь:

вот я выделил память какому-то вектору:

QVector bigVec;
bigVec.resize(1000000);

Как теперь выделенную вектору память распределить между маленькими векторами t.params?

Автор: Litkevich Yuriy 29.6.2009, 17:52

Цитата(AD @ 29.6.2009, 20:14) *
Как теперь выделенную вектору память распределить между маленькими векторами t.params?
а её зачем распределять?
Ты вектор даными заполняй и всё.

Автор: AD 29.6.2009, 18:02

Цитата(Litkevich Yuriy @ 29.6.2009, 18:52) *
а её зачем распределять?
Ты вектор даными заполняй и всё.

Тогда я не понимаю как! В смысле? У меня идет такая штуковина:
/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        uint var = (uint)1E+6, index = 0;
        QVector<LOGRECORD> log;
        while(_file -> read((char*)&var, sizeof(var)) > 0)
        {
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    LOGRECORD t = parseBlock();
                    /// что-то еще...
                    log.append(t);
                    /// что-то еще...
                }
                bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
    }

LOGRECORD LogReader::parseBlock()
{
        LOGRECORD t;
        t.params.resize(rec_descr.size()); ///< это самая длительная операция. Если лог-файлов много - то именно из-за нее довольно долго крутимся в этих циклах!
        t.name_file = strippedName(_file -> fileName());
        for(QVector<uint>::iterator iter=m_vBlock.begin(); iter!=m_vBlock.end(); ++iter)
        {
            uint var = *iter;
            Suint adr = var % 256;
            register int i = 0;
            for(paramI jter=rec_descr.begin(); jter!=rec_descr.end(); ++jter, ++i)
            {
                                    /// actions with paramters
                         }
                   }
}

Автор: BRE 29.6.2009, 19:18

AD, прости, а можешь словами рассказать, что нужно получить.
Почитал, но особо не врубился.
При последовательном заполнении (чтении) все равно QList или QVector. Только QList не нужно resize'ить.

Автор: AD 29.6.2009, 22:01

code 1, code2 из первого примера - это рабочие коды. Он плохи тем, что при чтении большого количества лог-файлов (или же очень-очень большого лог-файла) на операцию выделения памяти тратится много времени, из-за чего загрузка длится дольше, чем хотелось бы и сжирает процессорное время! Хотелось бы, чтобы загрузка происходила быстрее. Вариант который можно сделать следующий: выделять память более большими блоками, а потом ее распределять между более мелкими массивами - модернизация code 3. Так как я вместо обычных массивов, стал пользоваться QVector, то хотелось бы получить аналогичный алгоритм для векторов!

P.S. Заполнение непоследовательное - вначале могут заполнить 20 элемент, а потом 1-ый!

Автор: Tonal 30.6.2009, 7:15

Т.е. у тебя много маленьких векторочков. :)
В stl у std::vector есть параметр шаблона, который позволяет указать свой менеджер памяти для этого вектора - это именно то, что тебе нужно.
В Qt, QVector не имеет подобного механизма, и сделать тут ничего нельзя. :(

Выходов может быть несколько:
* Перейти на std::vector и свой аллокатор вместо QVector.
* Запихивать всё в единый QVector, а в LOGRECORD хранить индекс массиве и количество элементов.
* Если все блоки одинаковы по составу (или есть небольшое количество разных составов) то может проще выделить отдельную структуру для этих данных вместо векторов, и включить в состав LOGRECORD (в случае нескольких типов хранить указатель).

Автор: AD 30.6.2009, 8:23

Цитата(Tonal)
Т.е. у тебя много маленьких векторочков. :)

Я бы сказал так: их ох...но (ну очень-очень) много! :))))

Цитата(Tonal)
В stl у std::vector есть параметр шаблона, который позволяет указать свой менеджер памяти для этого вектора - это именно то, что тебе нужно.
В Qt, QVector не имеет подобного механизма, и сделать тут ничего нельзя. :(

Раньше не писал собственных аллокаторов памяти. Поможешь если что? С чего написание аллокатора памяти начать писать? Заранее спасибо!

P.S. Заменил QVetor на std::vector (для этого практически ничего не пришлось изменять, кроме самого типа)!

Автор: BRE 30.6.2009, 8:45

Как я понял задачу. Есть несколько конфигурационных файлов в которых храниться информация для вектора. Порядок загрузки файлов может быть любым. Нужно сформировать вектор с определенной последовательность данных.
Известно, что неприемлемое время тратиться на resize вектора.
Можно попробовать использовать QList. Т.е. читаем все файлы в отдельные списки, а после этого собрать общий список из маленьких, соблюдая необходимый порядок. Сложение QList операция быстрая, так же как и добавления в список элемента.

Автор: SABROG 30.6.2009, 8:53

Цитата(Tonal @ 30.6.2009, 8:15) *
и сделать тут ничего нельзя


Цитата
Как теперь выделенную вектору память распределить между маленькими векторами t.params?


Это удобно при большом количестве однотипных векторов, но ведь это не отменяет возможности перевыделять память каждому такому векторку через resize в каком-нибудь цикле.

Если размеры мелких векторов влияют на размер основного вектора, то сначала надо увеличить размер основного вектора скажем на 10000, а потом разделить эту сумму на количество мелких векторов. Если их 1000, то 10000/1000=10. Т.е. +10 к resize'у каждого мелкого :lol:

Автор: AD 30.6.2009, 9:03

Цитата(BRE @ 30.6.2009, 9:45) *
Как я понял задачу. Есть несколько конфигурационных файлов в которых храниться информация для вектора. Порядок загрузки файлов может быть любым. Нужно сформировать вектор с определенной последовательность данных.
Известно, что неприемлемое время тратиться на resize вектора.
Можно попробовать использовать QList. Т.е. читаем все файлы в отдельные списки, а после этого собрать общий список из маленьких, соблюдая необходимый порядок. Сложение QList операция быстрая, так же как и добавления в список элемента.

Есть ини-файл, в котором определено, какие-именно параметры следует выбирать из лог-файлов (бинарные файлы определенного формата, записанные авиа-приборами)! Данные этого ини-файла (описание параметров) считываются в вектор QVector<ParamDescr*>, ParamDescr - абстрактный класс, описывающий вид параметров (в общем виде - название параметра, адрес (в восьмеричной системе счисления), тип параметра). При чтении лог-файлов значения нужных параметров (сравниваются получаемые адреса) записываются по нужному индексу в вектор значений параметров (как раз в params)! Что именно делать с QList, как и что складывать я не совсем понял. Можешь пояснить, пожалуйста? Если что-то в коде запишешь, буду благодарен.

Автор: BRE 30.6.2009, 9:28

Цитата(AD @ 30.6.2009, 10:03) *
Есть ини-файл, в котором определено, какие-именно параметры следует выбирать из лог-файлов (бинарные файлы определенного формата, записанные авиа-приборами)! Данные этого ини-файла (описание параметров) считываются в вектор QVector<ParamDescr*>, ParamDescr - абстрактный класс, описывающий вид параметров (в общем виде - название параметра, адрес (в восьмеричной системе счисления), тип параметра). При чтении лог-файлов значения нужных параметров (сравниваются получаемые адреса) записываются по нужному индексу в вектор значений параметров (как раз в params)! Что именно делать с QList, как и что складывать я не совсем понял. Можешь пояснить, пожалуйста? Если что-то в коде запишешь, буду благодарен.

Так может удобней будет использовать QMap. INI-файл читается в QMap<адрес, ParamDescr*>, а дальше при чтении логов из мапа находиться необходимый параметр и устанавливаются его значение?

Автор: AD 30.6.2009, 9:31

Цитата(BRE @ 30.6.2009, 10:28) *
Так может удобней будет использовать QMap. INI-файл читается в QMap<адрес, ParamDescr*>, а дальше при чтении логов из мапа находиться необходимый параметр и устанавливаются его значение?

Нельзя, уже реализовано. Перетряхивать всю программу - нерентабельно. Задача - ускорить при загрузке. Далее - будет ускорить при отрисовке- но это уже другой вопрос.

Автор: Tonal 30.6.2009, 10:28

Цитата(AD @ 30.6.2009, 12:23) *
Цитата(Tonal)
В stl у std::vector есть параметр шаблона, который позволяет указать свой менеджер памяти для этого вектора - это именно то, что тебе нужно.

Раньше не писал собственных аллокаторов памяти. Поможешь если что? С чего написание аллокатора памяти начать писать? Заранее спасибо!

Ничего писать не нужно. :)
Смотри http://www.boost.org/doc/libs/1_39_0/libs/pool/doc/interfaces.html, в самом конеце страницы.

Автор: AD 30.6.2009, 10:41

Цитата(Tonal @ 30.6.2009, 11:28) *
Ничего писать не нужно. :)
Смотри http://www.boost.org/doc/libs/1_39_0/libs/pool/doc/interfaces.html, в самом конце страницы.

А мне какой allocator использовать? Буста в проекте нет! Ну как я понял, хватит какого-то стандартного. Какой необходим? Прости, если глупый вопрос, возможно, чего-то не понимаю!

Автор: Tonal 30.6.2009, 10:54

Можешь написать свой, проще зацепить из буста. :)
Где-то я встречал библиотечку шаблонов для лёгкого написания аллокаторов stl - думал в бусте, сейчас не вспомню...

Автор: AD 30.6.2009, 11:11

Цитата(Tonal @ 30.6.2009, 11:54) *
Можешь написать свой, проще зацепить из буста. :)
Где-то я встречал библиотечку шаблонов для лёгкого написания аллокаторов stl - думал в бусте, сейчас не вспомню...

Думаю, тогда может попробовать написать свой! :) Если что, поможете? Надо научиться их писать! Заодно увидеть, как это все устроено. А то одной теории из Саттера, маловато будет!

Автор: AD 30.6.2009, 12:09

да, кстати, а мне нужно наследоваться от стандартного аллокатора? Или полностью "свои велосипеды" писать?

Автор: AD 30.6.2009, 14:09

Странно, как-то не очень помогло. Или я что-то забыл?

allocator
#ifndef SPECIFIC_ALLOCATOR_INL
#define SPECIFIC_ALLOCATOR_INL

#include <memory>
#include <cassert>

/// Собственный распределитель памяти
template <typename T> void allocator_construct(T* p, const T& t) { new(p) T(t); }
template <typename T> void allocator_destroy(T* p) { p -> ~T(); }

template <typename T, typename A>
struct rebind_allocator
{
    typedef typename A::template rebind<T> binder;
    typedef typename binder::other type;
};

template <typename T>
class spec_allocator
{
public:
   typedef T                value_type;
   typedef value_type*        pointer;
   typedef const T*            const_pointer;
   typedef T&                reference;
   typedef const T&            const_reference;
   typedef std::size_t        size_type;
   typedef std::ptrdiff_t    difference_type;

   template <typename Other>
   struct rebind
   { typedef spec_allocator<Other> other; };

   spec_allocator() {}
   template <typename Other> spec_allocator(const spec_allocator<Other>&) {}
   spec_allocator(const spec_allocator&) {}
   template <typename Other> spec_allocator& operator=(const spec_allocator<Other>&) { return *this; }
   ~spec_allocator() {}
   pointer address(reference x) { return &x; }
   const_pointer address(const_reference x) const { return &x; }
   pointer allocate(size_type n, const void* = 0)
   { return (n != 0) ? reinterpret_cast<pointer>(::operator new(n * sizeof(value_type))) : 0; }
   void deallocate(pointer p, size_type n)
   {
       assert((p == 0) == (n == 0));
       ::operator delete((void*)p);
   }
   size_type max_size() const { return size_t(-1) / sizeof(value_type); }
   void construct(pointer p, const T& val) const { allocator_construct(p, val); }
   void destroy(pointer p) const { allocator_destroy(p); }
};

#endif // SPECIFIC_ALLOCATOR_INL


Вот как использую
/// Структура для хранения всех значений параметров в логе
struct LOGRECORD
{
public:
    //PARAMVALUE* params;                    ///< массив параметров
    std::vector<PARAMVALUE, spec_allocator<PARAMVALUE> > params;    ///< вектор параметров
/// ....
};

В чем еще могут быть проблемы? Что-то еще следует дописать?

Автор: Влад 30.6.2009, 14:49

Хм, есть опасение, что если причиной разработки аллокатора стали проблемы с быстродействием программы (т.е. это попытка улучшить быстродействие), то вряд ли чего получится. Хотя, конечно, "нет процессора, кроме процессора, и Профайлер - пророк его" :)
Причина мне представляется в том, что в многопоточных версиях CRT (по крайней мере, под Win) глобальные операторы ::new и ::delete защищены критической секцией - и операции выделения памяти по ::new и удаления по ::delete ну очень! "дорогие" по времени.

Автор: BRE 30.6.2009, 14:56

Цитата(AD @ 30.6.2009, 15:09) *
В чем еще могут быть проблемы? Что-то еще следует дописать?

Попробуй сделать свой аллокатор наследником std::allocator.

Автор: AD 30.6.2009, 14:59

Влад, исходя из твоих слов, следует заменить глобальные new и delete!

P.S. Изначально, именно для ускорения работы программы и затеялась вся канитель!

Автор: Tonal 1.7.2009, 8:47

Цитата(AD @ 30.6.2009, 18:09) *
Странно, как-то не очень помогло. Или я что-то забыл?
...
В чем еще могут быть проблемы? Что-то еще следует дописать?

Ты выделяешь столько памяти, сколько просит клиент (std::vector). Ровно это же и делает стандартный аллокатор. Т.е. ты ничего не выигрываешь.
Для того, чтобы получить эффект, нужно изменить стратегии выделения - например выделять память сразу большими блоками, а потом раздавать на них место. :)
Попробуй таки зацепить аллокатор из буста - хоть посмотришь к чему надо стремиться. :)

Да, можно ещё такой финт провернуть:
При разборе блока параметры загоняешь в список - std::list с быстрым аллокатором.
Когда блок разобран, создаёшь массив std::vector или обычный массив с уже известным размером и копируешь туда данные.
Если все блоки не очень большого размера, то можно выделить для аллокатора списка сразу память под самый большой блок и переразмещений в нём не будет.
И массивы будут размещаться всего один раз.
Стало быть получим количество выделений памяти в идеале: кол-во блоков + 1. :)
А переразмещений не будет вовсе - что сильно снизает нагрузку на всю подсистему памяти. :)

Автор: AD 1.7.2009, 9:05

Цитата(Tonal @ 1.7.2009, 9:47) *
Ты выделяешь столько памяти, сколько просит клиент (std::vector). Ровно это же и делает стандартный аллокатор. Т.е. ты ничего не выигрываешь.
Для того, чтобы получить эффект, нужно изменить стратегии выделения - например выделять память сразу большими блоками, а потом раздавать на них место. :)
Попробуй таки зацепить аллокатор из буста - хоть посмотришь к чему надо стремиться. :)

Я аллокатор из буста и смотрел. Вот именно большими блоками и хочу выделять. Можешь на коде показать, как это провернуть? Я не очень понимаю...

Цитата(Tonal @ 1.7.2009, 9:47) *
Да, можно ещё такой финт провернуть:
При разборе блока параметры загоняешь в список - std::list с быстрым аллокатором.
Когда блок разобран, создаёшь массив std::vector или обычный массив с уже известным размером и копируешь туда данные.
Если все блоки не очень большого размера, то можно выделить для аллокатора списка сразу память под самый большой блок и переразмещений в нём не будет.
И массивы будут размещаться всего один раз.
Стало быть получим количество выделений памяти в идеале: кол-во блоков + 1. :)
А переразмещений не будет вовсе - что сильно снизает нагрузку на всю подсистему памяти. :)

Спасибо большое. Мне бы хоть пример небольшой, тогда смогу сделать, а так сложно представить, как это реализовать!

Автор: AD 1.7.2009, 9:59

В boost в файле allocator.hpp нашел следующий код:

typedef std::alloc alloc_type;



template <typename T>
class allocator
{
   pointer allocate(size_type n, const void* = 0)
   {
      #ifdef BOOST_HAVE_SGI_ALLOCATOR
      return n != 0 ?
         reinterpret_cast<pointer>(alloc_type::allocate(n * sizeof(value_type)))
         : 0;
      #else
      return n != 0 ?
         reinterpret_cast<pointer>(::operator new(n * sizeof(value_type)))
         : 0;
      #endif
   }

   void deallocate(pointer p, size_type n)
   {
      #ifdef BOOST_HAVE_SGI_ALLOCATOR
      assert( (p == 0) == (n == 0) );
      if (p != 0)
         alloc_type::deallocate((void*)p, n);
      #else
      assert( (p == 0) == (n == 0) );
      if (p != 0)
         ::operator delete((void*)p);
      #endif
   }
}

Беда в том, что я не нашел в std данного типа: std::alloc! И что это за alloc_type::allocate(), alloc_type::deallocate() не смог узнать и найти код реализации этих функций. Буду благодарен за помощь в реализации корректной стратегии выделения памяти.

Автор: BRE 1.7.2009, 10:37

Почитай вот эту тему. В конце есть готовый алокатор, с примером.
Стратегия там такая, выделяется один кусок памяти под 1K объектов (chunk), когда память в нем заканчивается, выделяется следующий chunk и т.д.

Блин, ссылку забыл приложить. :)
http://forum.sources.ru/index.php?showtopic=249933

Автор: Tonal 1.7.2009, 10:44

Цитата(AD @ 1.7.2009, 13:05) *
Цитата(Tonal @ 1.7.2009, 9:47) *
Попробуй таки зацепить аллокатор из буста - хоть посмотришь к чему надо стремиться. :)

Я аллокатор из буста и смотрел. Вот именно большими блоками и хочу выделять. Можешь на коде показать, как это провернуть? Я не очень понимаю...

Вот и смотри как там работают с памятью.
А подключить действительно просто - бостовский пул не требует подключения библиотек - только хедеров и пути к ним. :)

Кстати, в том коде, который ты приводил в сообщении http://www.forum.crossplatform.ru/index.php?s=&showtopic=2998&view=findpost&p=21524
Попробуй сделать для LOGRECORD конструктор с размером параметров:
explicit LOGRECORD::LOGRECORD(size_t size) : params(size) ...

Конструирование вектора сразу нужного размера выгоднее, чем конструирование пустого, с последующим его увеличением.

П.С.
Цитата(AD @ 1.7.2009, 13:05) *
Цитата(Tonal @ 1.7.2009, 9:47) *
Да, можно ещё такой финт провернуть...

Спасибо большое. Мне бы хоть пример небольшой, тогда смогу сделать, а так сложно представить, как это реализовать!

Рассмотрел ещё раз твой код и подумал, что я похоже фигню написал.

Автор: AD 1.7.2009, 10:50

Цитата(Tonal @ 1.7.2009, 11:44) *
Вот и смотри как там работают с памятью.
А подключить действительно просто - бостовский пул не требует подключения библиотек - только хедеров и пути к ним. :)

Подключение буста - не решение. Довольно много причин, по которым я не хочу его подключать! Вот я и задаю вопросы, рассмотрев код буста, вообще-то!

Цитата(Tonal @ 1.7.2009, 11:44) *
Кстати, в том коде, который ты приводил в сообщении http://www.forum.crossplatform.ru/index.php?s=&showtopic=2998&view=findpost&p=21524
Попробуй сделать для LOGRECORD конструктор с размером параметров:
explicit LOGRECORD::LOGRECORD(size_t size) : params(size) ...

Конструирование вектора сразу нужного размера выгоднее, чем конструирование пустого, с последующим его увеличением.

Это я уже сделал! :)


Цитата(Tonal @ 1.7.2009, 11:44) *
П.С. Рассмотрел ещё раз твой код и подумал, что я похоже фигню написал.

В чем именно? :blink:

Автор: AD 1.7.2009, 14:55

Вот блин. Сделал следующий распределитель:

allocator
#ifndef SPECIFIC_ALLOCATOR_INL
#define SPECIFIC_ALLOCATOR_INL

#include <memory>
#include <cassert>

#define CHUNK_SIZE 4096

/// Собственный распределитель памяти
template <typename T> class pool_alloc: public std::allocator<T>
{
private:
    static const difference_type chunk_size = CHUNK_SIZE;
    struct Link { Link* next; };
    struct Chunk
    {
        Chunk* next;
        char memory[chunk_size];
    };
    Link* head;
    Chunk* chunks;
    static const size_type elem_size = (sizeof(T) < sizeof(Link*)) ? sizeof(Link*) : sizeof(T);
    static const size_type num_elem = chunk_size / elem_size;

private:
    void grow(bool special = false)            ///< увеличиваем пулл
    {
        Chunk* n = new Chunk;
        n -> next = chunks;
        chunks = n;

        char* start = n -> memory;
        char* last = &n -> memory[(num_elem - 1) * elem_size];
        for(char* p=start; p<last; p+=elem_size)
            reinterpret_cast<Link*> (p) -> next = reinterpret_cast<Link*> (p + elem_size);
        reinterpret_cast<Link*> (last) -> next = (special) ? head : 0;
        head = reinterpret_cast<Link*> (start);
    }

public:
    pool_alloc() throw(): head(0), chunks(0) {}
    pointer allocate(size_type n)            ///< выделяем память под n элементов типа T
    {
        if(n * elem_size > chunk_size) throw "Unsuccessfully memory allotment";
        if(!head) grow();
        else if(n != 1) grow(true);
        Link* p = head;
        while(n--) head = head -> next;
        return reinterpret_cast<pointer> (p);
    }
    void deallocate(pointer p, size_type n) ///< возвращаем память в пулл
    {
        while(n--)
        {
            reinterpret_cast<Link*> (p) -> next = head;
            head = reinterpret_cast<Link*> (p);
        }
    }
    ~pool_alloc() throw()
    {
        Chunk* n = chunks;
        while(n)
        {
            Chunk* p = n;
            n = n -> next;
            delete p;
        }
    }
    template <typename Other> struct rebind
    { typedef pool_alloc<Other> other; };
    template <typename Other> pool_alloc(const pool_alloc<Other>& p) throw() { head = 0, chunks = 0; }
    template <typename Other> pool_alloc<T>& operator=(const pool_alloc<Other>&)
    { head = 0; chunks = 0; return *this; }
    pool_alloc(const pool_alloc<T>& p) throw() { head = 0, chunks = 0; }
};

#endif // SPECIFIC_ALLOCATOR_INL


Загрузка 5 логов, объемом записей в 7038 грузилось 8-9 секунд. Нехорошо! Что-то опять-таки не так реализовал? Или взял не тот распределитель? в чем теперь проблемы?

Автор: BRE 1.7.2009, 15:24

А ты в профилировщике результаты смотрел, точно именно на выделении памяти такие тормоза?

Автор: AD 1.7.2009, 15:57

Цитата(BRE @ 1.7.2009, 16:24) *
А ты в профилировщике результаты смотрел, точно именно на выделении памяти такие тормоза?

Придется его еще раз подключать. Видимо, все-таки не только в выделении. Сейчас немного другую картинку наблюдаю!

Надо будет еще раз профайлером пройтись. Что-то не то. Как-то по-другому теперь программа себя ведет! В смысле тормозит теперь в другом месте. Операция выделения теперь шустренькая.

Автор: Влад 1.7.2009, 16:07

Цитата(AD @ 30.6.2009, 15:59) *
Влад, исходя из твоих слов, следует заменить глобальные new и delete!

Нет. Тут фича в том, что new и delete можно перегрузить для конкретного класса - именно того, который тормозит.

Автор: AD 1.7.2009, 16:10

Цитата(Влад @ 1.7.2009, 17:07) *
Нет. Тут фича в том, что new и delete можно перегрузить для конкретного класса - именно того, который тормозит.

Да, видимо, придется! :(
Еще раз проверил, все-таки притормаживает на выделении памяти!

Автор: BRE 1.7.2009, 16:49

Цитата(AD @ 1.7.2009, 17:10) *
Да, видимо, придется! :(

А чем думаешь их заменить? В чем смысл?

Цитата(AD @ 1.7.2009, 17:10) *
Еще раз проверил, все-таки притормаживает на выделении памяти!

Попробуй задать в алокаторе такой размер, что бы все элементы гарантированно помещались в один chunk. И посмотри результаты в профилировщике. Странно все как-то.

Автор: AD 1.7.2009, 17:14

У меня видоизменилась несколько структура, потому приведу ее еще раз:

/// Структура для хранения всех значений параметров в логе
struct LOGRECORD
{
public:
    std::vector<PARAMVALUE, pool_alloc<PARAMVALUE> > params;    ///< массив параметров
    QMap<bool, QString> phase;                ///< название этапа полета
    QString name_file;                        ///< имя лог-файла для данной записи

public:
    LOGRECORD(size_t size = 0): params(size), name_file("") {}
    LOGRECORD(const LOGRECORD& l): params(l.params), phase(l.phase), name_file(l.name_file) {}
/// anything
};

/// Функции чтения из файла
/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        uint var = (uint)1E+6, index = 0;
        bool bBlock = false, file_read = false;
        QVector<LOGRECORD> log;
        time_t before_read, contin_read;        time(&before_read);

        while(_file -> read((char*)&var, sizeof(var)) > 0)
        {
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(LOGRECORD(rec_descr.size()));
                    parseBlock(log.last());

                    file_read = true;
                    /// Сравнение временных меток
                    time(&contin_read);
                    time_t delta = contin_read - before_read;
                    if(delta > 3)
                    {
                        before_read = contin_read;
                        fillVec(log);
                        log.last().params.clear();
                        log.clear();
                    }
                    ++index;
                }
                bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
        if(log.size()) { fillVec(log); log.clear(); }

        /// Закрываем файл с логом
        shutdown();
        return file_read;
    }

/// Разбор одной записи
    bool LogReader::parseBlock(LOGRECORD& t)
    {
        t.name_file = strippedName(_file -> fileName());
        int size1 = m_vBlock.size(), size2 = rec_descr.size();
        QTextStream out(&traceFile);
        out << "m_vBlock.size() = " << size1 << "          rec_descr.size() = " << size2 << endl;
        /*for(QVector<uint>::iterator iter=m_vBlock.begin(); iter!=m_vBlock.end(); ++iter)
        {
            uint var = *iter;
            Suint adr = var % 256;
            register int i = 0;
            for(paramI jter=rec_descr.begin(); jter!=rec_descr.end(); ++jter, ++i)
            {
                if(adr == (*jter) -> Address())
                {
                    t.params[i] = (*jter) -> GetValue(var);
                    t.params[i].evn = 0;
                }
            }
        }*/

        return true;
    }


Видно, что два вложенных цикла, на которых, по идее могли быть тормоза закомментированы (пока!) и все-равно чувствительная длительность по времени!

Автор: AD 1.7.2009, 17:57

Заметил такую странную вещь:
Указал CHUNK_SIZE = 2^20 (два в 20 степени). Все произошло моментально. Расскоментировал код, загрузка стала производиться довольно долго, а вот уже само отображение крайне шустрое (хотя раньше и при отображении были тормоза)! Буду продолжать искать длительные операции.
Сейчас сделал CHUNK_SIZE = 2^13 (в 20 степени - все-таки не есть хорошо - чуть всю операционку не сожрало! :))

Автор: Tonal 2.7.2009, 8:04

Может всё же так:

...
    struct Chunk
    {
        Chunk* next;
        char memory[chunk_size * sizeof(T)];
    };
...

Тогда размер Chunk::memory будет всегда кратен количеству элементов - не будут пропадать попусту невлезшие кусочки. :)
Ну и размер chunk_size я бы поставил как среднее rec_descr.size() * 100, например. :)

Да, ты ведь профилируешь релизную сборку, я надеюсь?

Автор: AD 2.7.2009, 9:11

Цитата(Tonal @ 2.7.2009, 9:04) *
Да, ты ведь профилируешь релизную сборку, я надеюсь?

На счет профилировки хотел посоветоваться! Не помню как мне один раз удалось спрофилировать программу, но больше не удавалось. Причина следующая: файлы, которые генерирует MOC типа ui_tlv.h я создаю еще cpp, переношу туда setupUi, retranslateUi! Делаю это потому что кое-что изменяю в этих функциях! Так проблема в том, что при включенном профайлере компилятор каждый пересобирает проект, соответственно h-файлы все время пересоздаются и выдается куча ошибок. При попытке просто скомпилировать, он снова пересобирает проект. Есть способ победить эту штуку?

Вот видоизменил распределитель.
Пока не видны изменения в скорости: :(
allocator
#ifndef SPECIFIC_ALLOCATOR_INL
#define SPECIFIC_ALLOCATOR_INL

#include <memory>
#include <cassert>

#define CHUNK_SIZE 2048

/// Собственный распределитель памяти
template <typename T> class pool_alloc: public std::allocator<T>
{
private:
    static const difference_type chunk_size = CHUNK_SIZE;
    struct Link { Link* next; };
    struct Chunk
    {
        Chunk* next;
        char memory[chunk_size * ((sizeof(T) <= 0) ? 1 : sizeof(T))];
    };
    Link* head;
    Chunk* chunks;
    static const size_type elem_size = (sizeof(T) < sizeof(Link*)) ? sizeof(Link*) : sizeof(T);
    static const size_type num_elem = chunk_size / elem_size;

private:
    void grow(bool special = false)            ///< увеличиваем пулл
    {
        Chunk* n = new Chunk;
        n -> next = chunks;
        chunks = n;

        char* start = n -> memory;
        char* last = &n -> memory[(num_elem - 1) * elem_size];
        for(char* p=start; p<last; p+=elem_size)
            reinterpret_cast<Link*> (p) -> next = reinterpret_cast<Link*> (p + elem_size);
        reinterpret_cast<Link*> (last) -> next = (special) ? head : 0;
        head = reinterpret_cast<Link*> (start);
    }

public:
    pool_alloc() throw(): head(0), chunks(0) {}
    pointer allocate(size_type n)            ///< выделяем память под n элементов типа T
    {
        if(n * elem_size > chunk_size) throw std::bad_alloc();
        if(!head) grow();
        else if(n != 1) grow(true);
        Link* p = head;
        while(n--) head = head -> next;
        return reinterpret_cast<pointer> (p);
    }
    void deallocate(pointer p, size_type n) ///< возвращаем память в пулл
    {
        char* end = (char*)(p) + n;
        for(char* q=(char*)p; q<end; q+=elem_size)
            reinterpret_cast<Link*> (q) -> next = reinterpret_cast<Link*> (q + elem_size);
        reinterpret_cast<Link*> (end - elem_size) -> next = head;
        head = reinterpret_cast<Link*> (p);
    }
    ~pool_alloc() throw()
    {
        Chunk* n = chunks;
        while(n)
        {
            Chunk* p = n;
            n = n -> next;
            delete p;
        }
    }
    template <typename Other> struct rebind
    { typedef pool_alloc<Other> other; };
    template <typename Other> pool_alloc(const pool_alloc<Other>& p) throw() { head = 0, chunks = 0; }
    template <typename Other> pool_alloc<T>& operator=(const pool_alloc<Other>&)
    { head = 0; chunks = 0; return *this; }
    pool_alloc(const pool_alloc<T>& p) throw() { head = 0, chunks = 0; }
};

#endif // SPECIFIC_ALLOCATOR_INL

Автор: AD 2.7.2009, 10:51

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

Пока попробую тогда реализовать вариант с массивами, описанный в code 2!

Автор: Влад 2.7.2009, 13:38

Видел: http://rsdn.ru/forum/cpp.applied/930448.aspx ?

Автор: AD 3.7.2009, 12:00

Так, получилось ускорение с помощью массивов (пока что достаточное, но не необходимое)!
На следующей неделе все-таки доразбираюсь с распределителем памяти и постараюсь перевести решение для векторов.
Код следующий:

// h-file
/// Класс для хранения лог-файла
    class LogReader: public QObject
    {
        Q_OBJECT

    protected:
        QVector<uint> m_vBlock;                    ///< вектор прочитанных слов
        QFile* _file;                            ///< указатель на файл загрузки (лог-файл)
        char* _pm_buff;        ///< буфер памяти, который выделяется для распределения между массивами параметров
        int _pm_buff_count;                        ///< счетчик для корректного выделения памяти

private:
        bool parseBlock(LOGRECORD& t);

public:
            bool tRead();
};



cpp-code
// cpp-file

/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        uint var = (uint)1E+6, index = 0;
        bool bBlock = false, file_read = false;
        QVector<LOGRECORD> log;
        time_t before_read, contin_read;        time(&before_read);

        while(_file -> read((char*)&var, sizeof(var)) > 0)
        {
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(LOGRECORD());
                    parseBlock(log.last());
                    ChangeDateTime(log.last());
                    if(mainWindow -> iniReader() -> indexComputeParam() > 0)
                        FillCompParams(log.last());

                                        /// Сравнение временных меток
                    time(&contin_read);
                    time_t delta = contin_read - before_read;
                    if(delta > 3)
                    {
                        before_read = contin_read;
                        fillVec(log);
                        log.clear();
                    }
                    ++index;
                                  }
                              bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);

if(log.size()) { fillVec(log); log.clear(); }

        /// Закрываем файл с логом
        shutdown();
        return true;
}

/// Разбор одной записи
    bool LogReader::parseBlock(LOGRECORD& t)
    {
        if(_pm_buff_count >= BUFF_SIZE) _pm_buff_count = 0;
        if(!_pm_buff_count)
            _pm_buff = new char[rec_descr.size() * BUFF_SIZE * sizeof(PARAMVALUE)],
            memset(_pm_buff, 0, rec_descr.size() * BUFF_SIZE * sizeof(PARAMVALUE));
        t.params = reinterpret_cast<PARAMVALUE*> (_pm_buff + _pm_buff_count * rec_descr.size()
                                    * sizeof(PARAMVALUE));
        ++_pm_buff_count;
        t.name_file = strippedName(_file -> fileName());
        for(QVector<uint>::iterator iter=m_vBlock.begin(); iter!=m_vBlock.end(); ++iter)
        {
            uint var = *iter;
            Suint adr = var % 256;
            register int i = 0;
            for(paramI jter=rec_descr.begin(); jter!=rec_descr.end(); ++jter, ++i)
            {
                if(adr == (*jter) -> Address())
                {
                    t.params[i] = (*jter) -> GetValue(var);
                    t.params[i].evn = 0;
                }
                           //// anything
                        }
                }
                     /// anything

                   return true;
           }


Именно строки для ускорения кода - вот:
if(_pm_buff_count >= BUFF_SIZE) _pm_buff_count = 0;
        if(!_pm_buff_count)
            _pm_buff = new char[rec_descr.size() * BUFF_SIZE * sizeof(PARAMVALUE)],
            memset(_pm_buff, 0, rec_descr.size() * BUFF_SIZE * sizeof(PARAMVALUE));
        t.params = reinterpret_cast<PARAMVALUE*> (_pm_buff + _pm_buff_count * rec_descr.size()
                                    * sizeof(PARAMVALUE));
        ++_pm_buff_count;


Еще раз повторюсь, что с помощью векторов я все-таки постараюсь сделать корректный код, но чуть позже!

СПАСИБО ВСЕМ ЗА ПОМОЩЬ! Еще, возможно, обращусь! :)

Автор: AD 6.7.2009, 17:36

Сделал распределитель памяти следующий:

allocator
#ifndef SPECIFIC_ALLOCATOR_INL
#define SPECIFIC_ALLOCATOR_INL

#include <memory>
#include <cassert>

#include <QObject>
#ifdef Q_WS_WIN
    #pragma once
    #include <windows.h>
#endif

#define ALLOC_PAGE ((64 * 1024) - sizeof(Chunk))

template <class T> class virtalloc;

template <>
class virtalloc<void>
{
public:
    typedef size_t      size_type;
    typedef ptrdiff_t   difference_type;
    typedef void*       pointer;
    typedef const void* const_pointer;
    typedef void        value_type;
    template <class U>  struct rebind { typedef virtalloc<U> other; };
};

/*                   STRUCTURE OF THE MEMORY POOL
                                    
<-----------  Chunk0 ----------- ->  <-----------  Chunk1 ----------- ->
|                                  |                                  |
+----------------------------------+----------------------------------+
| obj0 | obj1 | obj2 | .... | objN |objN+1|objN+2| none | none | none |
+----------------------------------+----------------------------------+
^                                  ^             ^   |  ^   |  ^   |  
|       stored objects             |             |   |__|   |__|   + -> 0
|                                  |             |  
                           m_pChunks       m_pHead   Link*  Link*  Link*
*/
template <class T> class virtalloc
{
private:
    struct Link
    {
    public:
        Link* next;

    public:
        Link(): next(0) {}
        ~Link() { if(next) { delete next; next = 0; } }
    };
    struct Chunk
    {
    public:
        Chunk*    next;
        char    block[0];

    public:
        static void* operator new(size_t size, size_t block)
        { return ::VirtualAlloc(0, size + block, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); }
        static void operator delete(void* p)
        { ::VirtualFree(p, 0, MEM_RELEASE); }
    };
    class ElementSize
    {
    public:
        ElementSize() { size = 0; }
        void set_once(size_t s)
        {
            assert(sizeof(T) <= s);
            if(!size) size = s;
            assert(size == s);
        }
        operator size_t()
        {            
            if(!size)  size = sizeof(T) < sizeof(Link*) ? sizeof(Link*) : sizeof(T);
            return size;
        }
    private:
        size_t  size;
    };
    
    ElementSize        m_elemSize;
    Chunk*             m_pChunks;
    Link*              m_pHead;
    
public:
    typedef size_t     size_type;
    typedef ptrdiff_t  difference_type;
    typedef T*         pointer;
    typedef const T*   const_pointer;
    typedef T&         reference;
    typedef const T&   const_reference;
    typedef T          value_type;
    template <class U> struct rebind { typedef virtalloc<U> other; };

    virtalloc() throw(): m_pChunks(0), m_pHead(0) {}
    template <class U> virtalloc(const virtalloc<U>&) throw(): m_pChunks(0), m_pHead(0) {}
    ~virtalloc() throw()
    {
        while(m_pChunks)
        {
            Chunk* p  = m_pChunks;
            m_pChunks = m_pChunks -> next;
            delete p;
        }
    }
    pointer address(reference x) const { return &x; }    
    const_pointer address(const_reference x) const { return &x; }
    pointer allocate(size_type elements, virtalloc<void>::const_pointer hint=0)
    {
        assert(elements);

        Link* prev  = m_pHead;
        Link* first = m_pHead;
        Link* last  = m_pHead;

        size_t size = elements * m_elemSize;
        int count   = elements - 1;

        // check if we have enough free space to
        // store a continuous sequence of objects
        while(last && last -> next && count)
        {
            if(size_t((char*)last -> next -(char*)last) != m_elemSize)
            {                        
                prev  = last;
                first = last -> next;
                count = elements - 1;
            }
            else --count;
            last = last -> next;
        }
        if(!count && last)
        {
             // space found in current chunk
            if(first != m_pHead)
            {
                Link* p = last;
                while(p -> next) p = p -> next;
                p -> next = m_pHead;
                prev -> next = 0;
            }
            m_pHead = last -> next;
        }
        else // no free space => allocate one more chunk
        {
            grow(size, m_pHead);
            first = m_pHead;
            m_pHead = ((Link*)((char*)first +size-m_elemSize)) -> next;
        }
        return (pointer) first;
    }
    char* _Charalloc(size_type bytes)
    {
        m_elemSize.set_once(bytes);
        return (char*) allocate(1);
    }
    void deallocate(pointer pBlk, size_type elements)
    {
        if(!pBlk || !elements)
            return;
        
        Link* p = (Link*) pBlk;
        for(size_type n=1; n<elements; ++n)
        {
            p -> next = (Link*)((char*)p + m_elemSize);
            p = p -> next;
        }
        p -> next = m_pHead;
        m_pHead = p;        
    }
    void deallocate(void* pBlk, size_type elements)
    { deallocate(static_cast<pointer>(pBlk), elements); }
    size_type max_size() const throw()
    { return size_t(-1) / sizeof(T); }
    void construct(pointer p, const T& val)
    { ::new((void*)p) T(val); }  
    void destroy(pointer p) { p -> ~T(); }

private:    
    void grow(size_t size, Link* tail=0)
    {        
        size = (size / ALLOC_PAGE + (size % ALLOC_PAGE ? 1:0)) * ALLOC_PAGE;

        Chunk* chunk = new(size) Chunk;
        chunk -> next  = m_pChunks;
        m_pChunks    = chunk;
        
        char* first  = chunk -> block;
        char* last   = &first[(size / m_elemSize - 1) * m_elemSize];

        for(char* p=first; p<last; p+=m_elemSize)
            ((Link*)p) -> next = (Link*)(p+m_elemSize);

        ((Link*)last) -> next = tail;
        m_pHead = (Link*)first;
    }
};

template <class T>
bool operator ==(const virtalloc<T>&, const virtalloc<T>&) throw()
{ return true; }

template <class T>
bool operator !=(const virtalloc<T>&, const virtalloc<T>&) throw()
{ return false; }

#endif // SPECIFIC_ALLOCATOR_INL

Распределитель работает. Но работает ну очень медленно! Собственно код чтения остался тот, что я приводил до того, как снова перешел к массивам!
Скачал тестовую версию VTune посмотрел на что уходит время. Основная нагрузка на функцию grow(). Что сделать, чтобы не было такой убийственно-долгой загрузки (даже пары-тройки файлов)?
Где еще исправить и что?

Вот результаты работы профилировщика:
[attachment=697:intelVTune_results.JPG]

А вот работа профилировщика при коде, который был приведен в прошлом посте, т.е. при работе с массивами:
[attachment=698:intelVTu...ts_array.JPG]

P.S. В обоих случаях я загружал один и тот же файл (один!)!

Автор: AD 7.7.2009, 11:07

Буду очень благодарен за любую помощь. Очень требуется - так как самому тяжело найти решение проблемы.

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

/// Структура для хранения всех значений параметров в логе
struct LOGRECORD
{
public:
    std::vector<PARAMVALUE, virtalloc<PARAMVALUE> > params;    ///< массив параметров
/////////////////////////////////////////
/////////////////////////////////////////
};


/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        uint var = (uint)1E+6, index = 0;
        bool bBlock = false, file_read = false;
        QVector<LOGRECORD> log;
        time_t before_read, contin_read;        time(&before_read);

        while(_file -> read((char*)&var, sizeof(var)) > 0)
        {
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(LOGRECORD(rec_descr.size()));
                                        ///////////////////////////
                                 }
                        default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
        if(log.size()) { fillVec(log); log.clear(); }

        /// Закрываем файл с логом
        shutdown();
        return file_read;
    }

Автор: AD 8.7.2009, 12:12

Так... Ну теперь я совсем не понимаю, почему при загрузке нескольких файлов такие задержки! (Посмотрел в профилировщике) :shock:
При этом программа зависает, забирая 99% процессорного времени.


Тормоза на функции allocate!
Не могу понять, где в ней в принципе, кроме grow, могут быть тормоза. В grow после корректного создания конструкторов LOGRECORD теперь заходит довольно редко!

Код, слегка изменил. Вот так:

/// функция allocate из класса virtalloc
pointer allocate(size_type elements, virtalloc<void>::const_pointer hint=0)
    {
        assert(elements);

        Link* prev  = m_pHead;
        Link* first = m_pHead;
        Link* last  = m_pHead;

        size_t size = elements * m_elemSize;
        int count   = elements - 1;

        // check if we have enough free space to
        // store a continuous sequence of objects
        while(last && last -> next && count)
        {
            if(size_t((char*)last -> next -(char*)last) != m_elemSize)
            {                        
                prev  = last;
                first = last -> next;
                count = elements - 1;
            }
            else --count;
            last = last -> next;
        }
        if(!count && last)
        {
             // space found in current chunk
            if(first != m_pHead)
            {
                Link* p = last;
                while(p -> next) p = p -> next;
                p -> next = m_pHead;
                prev -> next = 0;
            }
            m_pHead = last -> next;
        }
        else // no free space => allocate one more chunk
        {
            grow(size, m_pHead);
            first = m_pHead;
            m_pHead = ((Link*)((char*)first +size-m_elemSize)) -> next;
        }
        return (pointer) first;
    }


template<class T> class allocator_wrapper
{
    typedef virtalloc<T> Allocator;

public:
    typedef typename Allocator::size_type size_type;
    typedef typename Allocator::difference_type difference_type;
    typedef typename Allocator::pointer pointer;
    typedef typename Allocator::const_pointer const_pointer;
    typedef typename Allocator::reference reference;
    typedef typename Allocator::const_reference const_reference;
    typedef typename Allocator::value_type value_type;
    template <class U> struct rebind { typedef allocator_wrapper<U> other; };

private:
    Allocator* m_allocator;

public:
    allocator_wrapper(Allocator* allocator = 0): m_allocator(allocator) {}
    pointer address(reference x) const { return &x; }
    const_pointer address(const_reference x) const { return &x; }
    pointer allocate(size_type elements, virtalloc<void>::const_pointer hint=0)
    { return (m_allocator) ? m_allocator -> allocate(elements, hint) : 0; }
    void deallocate(void* pBlk, size_type elements)
    { if(m_allocator) m_allocator -> deallocate(pBlk, elements); }
    size_type max_size() const throw() { return size_t(-1) / sizeof(T); }
    void construct(pointer p, const T& val) { ::new((void*)p) T(val); }
    void destroy(pointer p) { p -> ~T(); }
};


class LogReader: public QObject
    {
        Q_OBJECT

    private:
        QVector<uint> m_vBlock;                    ///< вектор прочитанных слов
        QFile* _file;                            ///< указатель на файл загрузки (лог-файл)
        virtalloc<PARAMVALUE> alloc;            ///< распределитель памяти
///////////////////////////////////////
};

/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        uint var = (uint)1E+6, index = 0;
        bool bBlock = false, file_read = false;
        LOGRECORD unit(allocator_wrapper<PARAMVALUE>(&alloc), rec_descr.size());
        QVector<LOGRECORD> log(0, unit);
        time_t before_read, contin_read;        time(&before_read);

        while(_file -> read((char*)&var, sizeof(var)) > 0)
        {
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(unit);
                    parseBlock(log.last());
                    /////////////////////////////////
                        
                                        file_read = true;
                    /// Сравнение временных меток
                    time(&contin_read);
                    time_t delta = contin_read - before_read;
                    if(delta > 3)
                    {
                        before_read = contin_read;
                        fillVec(log);
                        log.clear();
                    }
                    ++index;
                }
                bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
        if(log.size()) { fillVec(log); log.clear(); }

        /// Закрываем файл с логом
        shutdown();
        return file_read;
    }


/// Структура для хранения всех значений параметров в логе
struct LOGRECORD
{
public:
    std::vector<PARAMVALUE, allocator_wrapper<PARAMVALUE> > params;    ///< массив параметров
    QMap<bool, QString> phase;                ///< название этапа полета
    QString name_file;                        ///< имя лог-файла для данной записи

public:
    LOGRECORD(const allocator_wrapper<PARAMVALUE>& only_once_allocator = allocator_wrapper<PARAMVALUE>(),
                size_t size = 0): params(size, PARAMVALUE(), only_once_allocator), name_file("") {}
    LOGRECORD(const LOGRECORD& l): params(l.params.size(), PARAMVALUE(), l.params.get_allocator()),
                                    phase(l.phase), name_file(l.name_file)
    { for(register int i=0; i<l.params.size(); ++i) params[i] = l.params.at(i); }
////////////////////////////////////////////////////////////////////////////////////////////////
};


Буду благодарен, если подскажите в чем еще проблемы могут быть.

Автор: AD 9.7.2009, 17:10

Сделал так: вначале чтение всех файлов. Пихаем прочитанное в буфер. А затем уже ходим по буферу и распарсиваем.

Reading files
/// Цикл по всем выбранным лог-файлам
void RThread::run()
{
    countRecords(); ///< подсчет количества записей в файлах
    logger -> initializeBuffer(Reader::FBUFF_SIZE);
    logger -> setBytesRead(0);
    read_count = flist.size();
    if(flist.size())
    {
        foreach(QString fname, flist)
        {
            if(stopped) break;
            logger -> setFileName(fname);
            if(logger -> tRead()) --read_count;
        }
        logger -> parseBuffer();
    }
    if(read_count == 0) --read_count;
    logger -> destroyBuffer();
    exec();
}

/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        if(!open()) return false;        ///< открываем файл с логом

        if(_file -> size() > FBUFF_SIZE - bytes_read)
            return false;
        qint64 really_read = _file -> read(&files_buff[bytes_read], _file -> size());
        assert(really_read == _file -> size());
        bytes_read += really_read;

        shutdown();                        ///< закрываем файл с логом

        return true;
    }

    /// Разбор прочитанного буфера
    bool LogReader::parseBuffer()
    {
        uint var = 1e+6, index = 0;
        int i = 0;
        bool bBlock = false, buff_ready = false;
        QVector<LOGRECORD> log(0, LOGRECORD());
        time_t before_read;        time(&before_read);

        for(uint* p=reinterpret_cast<uint*>(files_buff); i < (bytes_read / sizeof(var)); ++p, ++i)
        {
            var = *p;
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(LOGRECORD());
                    parseBlock(log.last());
                    ChangeDateTime(log.last());
                    if(mainWindow -> iniReader() -> indexComputeParam() > 0)
                        FillCompParams(log.last());
                    fillHlpLog(log.last(), index);
                    buff_ready = true;
                    compareTimeMark(log, before_read);
                    ++index;
                }
                bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
        lastCompare(log, before_read);

        return buff_ready;
    }


Как и предполагалось, это разделение не решило основную проблему - притормаживание.
Пока что, так как при всех собственных распределителях памяти программа падает при выделении большого количества файлов оставил (пока!) стандартный распределитель памяти. Притормаживает в функции parseBlock(), на строках params.resize()
Вот полный код функции:
/// Разбор одной записи
    bool LogReader::parseBlock(LOGRECORD& t)
    {
        t.params.resize(rec_descr.size());
        t.name_file = strippedName(_file -> fileName());
        for(QVector<uint>::iterator iter=m_vBlock.begin(); iter!=m_vBlock.end(); ++iter)
        {
            uint var = *iter;
            Suint adr = var % 256;
            register int i = 0;
            for(paramI jter=rec_descr.begin(); jter!=rec_descr.end(); ++jter, ++i)
            {
                if(adr == (*jter) -> Address())
                {
                    t.params[i] = (*jter) -> GetValue(var);
                    t.params[i].evn = 0;
                }
                selectPhase(*jter, i, t);
            }
        }
        selectPhase(t);

        return true;
    }

Автор: AD 13.7.2009, 11:40

Цитата(Tonal @ 1.7.2009, 11:44) *
Вот и смотри как там работают с памятью.
А подключить действительно просто - бустовский пул не требует подключения библиотек - только хедеров и пути к ним. :)

Хочу попробовать подключить boost(овский) pool_allocator. Как там с помощью-какой-то утилиты мне собрать нужные пути? Сможете помочь? Буду благодарен.

Автор: Влад 13.7.2009, 11:59

Цитата
bcp
Usage:
bcp --list [options] module-list
bcp --list-short [options] module-list
bcp --report [options] module-list html-file
bcp [options] module-list output-path

Options:
--boost=path sets the location of the boost tree to path
--scan treat the module list as a list of (possibly non-boost)
files to scan for boost dependencies
--cvs only copy files under cvs version control
--unix-lines make sure that all copied files use Unix style line endings

module-list: a list of boost files or library names to copy
html-file: the name of a html file to which the report will be written
output-path: the path to which files will be copied

Автор: AD 13.7.2009, 17:30

Итак, сделал вырезку некоторых файлов из boost и добавил в свой проект.
Распределителем памяти сделал fast_pool_allocator. Скорость значительно улучшилась. Есть еще тормоза при загрузке 50 и более файлов, но это уже несмертельно и не так долго.
Вот версия, с которой буду "жить" некоторое время. Потом оптимизирую отрисовку - должно улучшить результаты! :)

Reading Logs
#include "boost/pool/pool_alloc.hpp"

/// Структура для хранения всех значений параметров в логе
struct LOGRECORD
{
public:
    QString name_file;                        ///< имя лог-файла для данной записи
    QMap<bool, QString> phase;                ///< название этапа полета
    std::vector<PARAMVALUE, boost::fast_pool_allocator<PARAMVALUE> > params;    ///< вектор параметров

public:
    LOGRECORD(const boost::fast_pool_allocator<PARAMVALUE>& only_once_allocator =
                boost::fast_pool_allocator<PARAMVALUE>(), size_t size = 0): name_file(""),
                params(size, PARAMVALUE(), only_once_allocator) {}
    LOGRECORD(const LOGRECORD& l): name_file(l.name_file), phase(l.phase),
                                   params(l.params.size(), PARAMVALUE(), l.params.get_allocator())
    { std::copy(l.params.begin(), l.params.end(), params.begin()); }
    LOGRECORD& operator=(const LOGRECORD& l);
    PARAMVALUE GetLat(QVector<ParamDescr*>* descr) const;
    PARAMVALUE GetLon(QVector<ParamDescr*>* descr) const;
    PARAMVALUE GetDateTime(QVector<ParamDescr*>* descr) const;
    PARAMVALUE GetParamValue(QVector<ParamDescr*>* descr, const std::string& name, const Suint& addr) const;
    PARAMVALUE GetParamValue(QVector<ParamDescr*>* descr, const std::string& name) const;
    PARAMVALUE GetParamValue(QVector<ParamDescr*>* descr, const Suint& addr, std::string& instance);
};

class LogReader: public QObject
    {
        Q_OBJECT

    private:
        QVector<uint> m_vBlock;                    ///< вектор прочитанных слов
        QFile* _file;                            ///< указатель на файл загрузки (лог-файл)
        char* files_buff;                        ///< буфер, в который будут считываться все данные из файлов
        qint64 bytes_read;                        ///< реальное количество прочитанных данных
        int records_count;                        ///< количество записей в лог-файле
        boost::fast_pool_allocator<PARAMVALUE> alloc;///< распределитель памяти

    public:
        QVector<ParamDescr*> rec_descr;            ///< вектор параметров из файла описания (файла загрузки)
        TLV* mainWindow;                        ///< указатель на объект главного окна

    private:
        bool parseBlock(LOGRECORD& t);
        void ChangeDateTime(LOGRECORD& t);
        void FillCompParams(LOGRECORD& t);
        void selectPhase(ParamDescr* descrParam, int index, LOGRECORD& t);
        void selectPhase(LOGRECORD& t);
        void fillHlpLog(const LOGRECORD& t, uint index);
        void compareTimeMark(QVector<LOGRECORD>& log, time_t& before_read);
        void lastCompare(QVector<LOGRECORD>& log, const time_t& before_read);

    public:
        LogReader(std::string FileName, TLV* main): mainWindow(main), records_count(0), _file(new QFile()),
                                        files_buff(0), bytes_read(0) { _file -> setName(FileName.c_str()); }
        LogReader(const char* FileName, TLV* main): mainWindow(main), records_count(0), _file(new QFile()),
                                        files_buff(0), bytes_read(0) { _file -> setName(FileName); }
        ~LogReader() { shutdown(); }
    ///// ................................................... ////////
        void setBytesRead(qint64 n) { bytes_read = n; }
        qint64 bytesRead() const { return bytes_read; }
        bool tRead();
        bool parseBuffer();
    };

/// Выбор этапа полета
    void LogReader::selectPhase(ParamDescr* descrParam, int index, LOGRECORD& t)
    {
        if(descrParam -> Name() != "phaseoperation") return;
        int n = t.params[index].value;
        if(n < phaseList.size() && !phaseList.isEmpty())
        {
            t.phase[true] = phaseList[n][(mainWindow) ? mainWindow -> Language() : true];
            t.phase[false] = phaseList[n][(mainWindow) ? !mainWindow -> Language() : false];
        }
    }

    /// Выбор этапа полета - если нет совпадений имен
    void LogReader::selectPhase(LOGRECORD& t)
    {
        if(t.phase.isEmpty() && !phaseList.isEmpty())
        {
            bool f = (mainWindow) ? mainWindow -> Language() : true;
            t.phase[f] = phaseList[0][f];
            t.phase[!f] = phaseList[0][!f];
        }
    }

    /// Разбор одной записи
    bool LogReader::parseBlock(LOGRECORD& t)
    {
        struct VARS {
            bool flag;
            uint var;
            VARS(): flag(false), var(0) {}
        } vars[ADDR_LIMIT];

        t.name_file = strippedName(_file -> fileName());

        int i = 0, block_size = m_vBlock.size();
        for(int j=0; j<block_size; ++j)
        {
            uint index = m_vBlock[j] % ADDR_LIMIT;
            vars[index].flag = true;
            vars[index].var = m_vBlock[j];
        }

        paramI end(rec_descr.end());
        for(paramI iter=rec_descr.begin(); iter!=end; ++iter, ++i)
        {
            uint addr_index = (*iter) -> Address();
            if(addr_index < ADDR_LIMIT)
                if(vars[addr_index].flag)
                    t.params[i] = (*iter) -> GetValue(vars[addr_index].var);
            selectPhase((*iter), i, t);
        }
        selectPhase(t);

        return true;
    }

    /// Заполнение вычислимых параметров
    void LogReader::FillCompParams(LOGRECORD& t)
    {
        if(mainWindow -> iniReader() -> indexComputeParam() < 0) return;
        for(int i=mainWindow -> iniReader() -> indexComputeParam(); i<rec_descr.size(); ++i)
        {
            CompParamDescr* p = dynamic_cast<CompParamDescr*> (rec_descr[i]);
            ElemFormul formul(p -> formulList());
            COMPUTE* element = formul.calculateFormul(&t, &rec_descr);
            if(element != 0)
            {
                t.params[i].value = element -> GetValue();
                t.params[i].status = PS_OK;
                t.params[i].evn = 0;
            }
        }
    }

    /// Заполнение вектора-получателя частями прочитанных данных
    void LogReader::fillVec(QVector<LOGRECORD>& sender) { mainWindow -> addData(sender); }

    /// Запись значений времени и даты
    void LogReader::ChangeDateTime(LOGRECORD& t)
    {
        int time_index = mainWindow -> iniReader() -> indexTimeParam(),
            date_index = mainWindow -> iniReader() -> indexDateParam();
        TimeParamDescr* dt_time = dynamic_cast<TimeParamDescr*> (rec_descr[time_index]);
        DateParamDescr* dt_date = dynamic_cast<DateParamDescr*> (rec_descr[date_index]);
        tm c_time = dt_time -> TCTime(), c_date = dt_date -> TCDate();
        TimeDateParamDescr tmd(c_time, c_date);
        t.params[time_index] = tmd.GetValue(0);
        t.params[date_index] = tmd.GetValue(0);
    }

    /// Заполнение hlp_log
    void LogReader::fillHlpLog(const LOGRECORD& t, uint index)
    {
        if(index == 0)
            mainWindow -> hlp_log.append(VEC_PARTNER(index, t.name_file, t.phase));
        else if(index > 0 && mainWindow -> hlp_log.size() != 0)
        {
            if(mainWindow -> hlp_log.last().name != t.name_file ||
                mainWindow -> hlp_log.last().phase != t.phase)
                mainWindow -> hlp_log.append(VEC_PARTNER(index, t.name_file, t.phase));

        }
    }

    /// Сравнение временных меток
    void LogReader::compareTimeMark(QVector<LOGRECORD>& log, time_t& before_read)
    {
        time_t contin_read;
        time(&contin_read);
        time_t delta = contin_read - before_read;
        if(delta > 2)
        {
            before_read = contin_read;
            fillVec(log);
            log.clear();
        }
    }

    /// Сравнение временных меток, если чтение файла завершилось раньше, чем через 3 секунды
    void LogReader::lastCompare(QVector<LOGRECORD>& log, const time_t& before_read)
    {
        if(!log.size()) return;
        fillVec(log);
        log.clear();
    }

/// Чтение файла загрузки
    bool LogReader::tRead()
    {
        if(!open()) return false;        ///< открываем файл с логом

        if(_file -> size() > FBUFF_SIZE - bytes_read)
            return false;
        qint64 really_read = _file -> read(&files_buff[bytes_read], _file -> size());
        assert(really_read == _file -> size());
        bytes_read += really_read;

        shutdown();                        ///< закрываем файл с логом

        return true;
    }

    /// Разбор прочитанного буфера
    bool LogReader::parseBuffer()
    {
        uint var = 1e+6, index = 0;
        int i = 0;
        bool bBlock = false, buff_ready = false;
        LOGRECORD unit(alloc, rec_descr.size());
        QVector<LOGRECORD> log(0, unit);
        time_t before_read;        time(&before_read);

        for(uint* p=reinterpret_cast<uint*>(files_buff); i<(bytes_read / sizeof(var)); ++p, ++i)
        {
            var = *p;
            Suint adr = var % 256;
            /// Определение начала/конца одной записи и записывание ее в вектор
            switch(adr)
            {
            case 0000:
                m_vBlock.clear();
                bBlock = true;
            break;
            case 0001:
                if(bBlock)
                {
                    log.append(unit);
                    parseBlock(log.last());
                    ChangeDateTime(log.last());
                    if(mainWindow -> iniReader() -> indexComputeParam() > 0)
                        FillCompParams(log.last());
                    fillHlpLog(log.last(), index);
                    buff_ready = true;
                    compareTimeMark(log, before_read);
                    ++index;
                }
                bBlock = false;
            break;
            default:
                if(bBlock) m_vBlock.append((uint)var);
            }
        }
        lastCompare(log, before_read);

        return buff_ready;
    }

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

Да.... Огромное спасибо Tonal(у), Владу, BRE за помощь! Без вас я бы не смог так улучшить код! :)

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)