Раньше был обычный массив (динамический), который содержал значения определенных параметров, прочитанных из лог-файла (широта, долгота, скорость и пр.). Сейчас сделал в виде вектора. Вместо оператора 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());
/// 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;
t.params.resize(rec_descr.size());
t.words.resize(rec_descr.size());
Интересно, в code2 при num_block <= 1000 t.params и t.words будут указывать куда попало или ты опустил кусок кода?
Ну а вообще, контейнерные классы обычно при надобности увеличивают свой размер с запасом.
Например std::vector увеличивает свой размер в 1.5 - 2 раза по сравнению с текущим, поэтому последовательные push_bask довольно дёшевы.
if(!num_block)
{
/// те операции, которые делались, если num_block > 1000
}
if(num_block > 1000)
num_block = 0;
Кстати интересный класс есть QVarLengthArray, если я правильно понял, то он до какого-то момента выделяет память в стеке, а после превышения некоторого размера начинает выделять память в куче. Якобы операция выделения памяти в стеке быстрее по скорости, чем new.
Очень прошу помочь. Как реализовать код для QVector? Делать выделение, когда счетчик будет более 2000? Ну хоть как-то помогите, пожалуйста!
// param - обычный массив
if(/*<Условие>*/)
{
buff = new char[/**/];
}
t.params = buff + rec_descr.size() * num_block;
t.words = buff1 + rec_descr.size() * num_block;
по ссылке, что я привёл, дочитай до конца. Там есть рекомендации "которые позволяют контролировать и задавать столько памяти, сколько вам нужно"
Юра, мне непонятна одна конкретная вещь:
вот я выделил память какому-то вектору:
QVector bigVec;
bigVec.resize(1000000);
/// Чтение файла загрузки
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
}
}
}
AD, прости, а можешь словами рассказать, что нужно получить.
Почитал, но особо не врубился.
При последовательном заполнении (чтении) все равно QList или QVector. Только QList не нужно resize'ить.
code 1, code2 из первого примера - это рабочие коды. Он плохи тем, что при чтении большого количества лог-файлов (или же очень-очень большого лог-файла) на операцию выделения памяти тратится много времени, из-за чего загрузка длится дольше, чем хотелось бы и сжирает процессорное время! Хотелось бы, чтобы загрузка происходила быстрее. Вариант который можно сделать следующий: выделять память более большими блоками, а потом ее распределять между более мелкими массивами - модернизация code 3. Так как я вместо обычных массивов, стал пользоваться QVector, то хотелось бы получить аналогичный алгоритм для векторов!
P.S. Заполнение непоследовательное - вначале могут заполнить 20 элемент, а потом 1-ый!
Т.е. у тебя много маленьких векторочков.
В stl у std::vector есть параметр шаблона, который позволяет указать свой менеджер памяти для этого вектора - это именно то, что тебе нужно.
В Qt, QVector не имеет подобного механизма, и сделать тут ничего нельзя.
Выходов может быть несколько:
* Перейти на std::vector и свой аллокатор вместо QVector.
* Запихивать всё в единый QVector, а в LOGRECORD хранить индекс массиве и количество элементов.
* Если все блоки одинаковы по составу (или есть небольшое количество разных составов) то может проще выделить отдельную структуру для этих данных вместо векторов, и включить в состав LOGRECORD (в случае нескольких типов хранить указатель).
Как я понял задачу. Есть несколько конфигурационных файлов в которых храниться информация для вектора. Порядок загрузки файлов может быть любым. Нужно сформировать вектор с определенной последовательность данных.
Известно, что неприемлемое время тратиться на resize вектора.
Можно попробовать использовать QList. Т.е. читаем все файлы в отдельные списки, а после этого собрать общий список из маленьких, соблюдая необходимый порядок. Сложение QList операция быстрая, так же как и добавления в список элемента.
Можешь написать свой, проще зацепить из буста.
Где-то я встречал библиотечку шаблонов для лёгкого написания аллокаторов stl - думал в бусте, сейчас не вспомню...
да, кстати, а мне нужно наследоваться от стандартного аллокатора? Или полностью "свои велосипеды" писать?
Странно, как-то не очень помогло. Или я что-то забыл?
#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; ///< вектор параметров
/// ....
};
Хм, есть опасение, что если причиной разработки аллокатора стали проблемы с быстродействием программы (т.е. это попытка улучшить быстродействие), то вряд ли чего получится. Хотя, конечно, "нет процессора, кроме процессора, и Профайлер - пророк его"
Причина мне представляется в том, что в многопоточных версиях CRT (по крайней мере, под Win) глобальные операторы ::new и ::delete защищены критической секцией - и операции выделения памяти по ::new и удаления по ::delete ну очень! "дорогие" по времени.
Влад, исходя из твоих слов, следует заменить глобальные new и delete!
P.S. Изначально, именно для ускорения работы программы и затеялась вся канитель!
В 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
}
}
Почитай вот эту тему. В конце есть готовый алокатор, с примером.
Стратегия там такая, выделяется один кусок памяти под 1K объектов (chunk), когда память в нем заканчивается, выделяется следующий chunk и т.д.
Блин, ссылку забыл приложить.
http://forum.sources.ru/index.php?showtopic=249933
explicit LOGRECORD::LOGRECORD(size_t size) : params(size) ...
explicit LOGRECORD::LOGRECORD(size_t size) : params(size) ...
Вот блин. Сделал следующий распределитель:
#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
А ты в профилировщике результаты смотрел, точно именно на выделении памяти такие тормоза?
У меня видоизменилась несколько структура, потому приведу ее еще раз:
/// Структура для хранения всех значений параметров в логе
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;
}
Заметил такую странную вещь:
Указал CHUNK_SIZE = 2^20 (два в 20 степени). Все произошло моментально. Расскоментировал код, загрузка стала производиться довольно долго, а вот уже само отображение крайне шустрое (хотя раньше и при отображении были тормоза)! Буду продолжать искать длительные операции.
Сейчас сделал CHUNK_SIZE = 2^13 (в 20 степени - все-таки не есть хорошо - чуть всю операционку не сожрало! )
Может всё же так:
...
struct Chunk
{
Chunk* next;
char memory[chunk_size * sizeof(T)];
};
...
#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
Так.... совсем интересно. При загрузке большого количества логов программа терпит молный крах.
Пока попробую тогда реализовать вариант с массивами, описанный в code 2!
Видел: http://rsdn.ru/forum/cpp.applied/930448.aspx ?
Так, получилось ускорение с помощью массивов (пока что достаточное, но не необходимое)!
На следующей неделе все-таки доразбираюсь с распределителем памяти и постараюсь перевести решение для векторов.
Код следующий:
// 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-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;
Сделал распределитель памяти следующий:
#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
Буду очень благодарен за любую помощь. Очень требуется - так как самому тяжело найти решение проблемы.
Выяснил, что выделение памяти происходит для каждого элемента, причем функция 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;
}
Так... Ну теперь я совсем не понимаю, почему при загрузке нескольких файлов такие задержки! (Посмотрел в профилировщике) :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); }
////////////////////////////////////////////////////////////////////////////////////////////////
};
Сделал так: вначале чтение всех файлов. Пихаем прочитанное в буфер. А затем уже ходим по буферу и распарсиваем.
/// Цикл по всем выбранным лог-файлам
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;
}
/// Разбор одной записи
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;
}
Итак, сделал вырезку некоторых файлов из boost и добавил в свой проект.
Распределителем памяти сделал fast_pool_allocator. Скорость значительно улучшилась. Есть еще тормоза при загрузке 50 и более файлов, но это уже несмертельно и не так долго.
Вот версия, с которой буду "жить" некоторое время. Потом оптимизирую отрисовку - должно улучшить результаты!
#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;
}
Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)