LINUX.ORG.RU

Qt Linux memory leaks

 , ,


2

7

Здравствуйте

Имеется программа, написанная на Qt (С++), которая парсит входящие файлы и запихивает их в базу данных. Проблема заключается в том, что по каким-то непонятным причинам обьект, хранящий результат, не удаляется. А самое интересное заключается в том, что проблема происходит исключительно на линуксе (fedora).

Немного дополнительной информации:
1. Обработка каждого файла происходит в разных потоках. Создание нового потока реализовано в таком виде:

            QThread *_t = new QThread();

            QObject::connect(_t, SIGNAL(started()), task.data(), SLOT(run()), Qt::DirectConnection);
            QObject::connect(task.data(), SIGNAL(finished()), this, SLOT(taskFinished()));
            QObject::connect(_t, SIGNAL(finished()), _t, SLOT(quit()));
            QObject::connect(_t, SIGNAL(finished()), _t, SLOT(deleteLater()));
            QObject::connect(task.data(), SIGNAL(finished()), _t, SLOT(quit()));

            _t->start();
2. Расширение файла - CSV. Обработка файла осуществляется этой библиотекой
3. Обработанные данные кладутся в
    struct LineParseResult { QString article, brand, price, stock, multiplicity, __article, price_id; QByteArray key; };
    typedef QList<LineParseResult> PResult;
4. После того, как файл обработался и ParserTask заканчивает свою работу - эмитится сигнал finished(). Мы его ловим и получаем объект ParserTask'a:
        auto _task = qobject_cast<ParserTask *>(QObject::sender());
после чего ищем его в списке тасков, которые работают на данный момент и очищаем. Список выглядит таким образом:
QList<QSharedPointer<ParserTask>> m_currentRunningTasks;
и очистка памяти осуществляется таким образом:
                auto task = m_currentRunningTasks.takeAt(i);
                task.clear();
4. Платформо-зависимого кода в программе не имеется, за исключением того, что присутствует в самом Qt'e

После всех проделанных операций происходят разные вещи на Windows 10 и Linux, а именно программа, которая работает на Linux (диструбтив указан в начале) потребляет гораздо больше памяти, что не так страшно, как то, что память, которая занята после обработки файла, не очищается даже принудительно. На Windows же обратное, программа ест мало памяти и очищает ее после отработки

Программа была проверена валгриндом, который говорит, что утечек не имеется

Что это может быть и как с этим бороться?



Последнее исправление: Flassie (всего исправлений: 1)

Продолжая тему утечек памяти
Собрал пример, в котором происходит эта самая утечка памяти

Parser.h:

#ifndef PARSER_H
#define PARSER_H

#include <iostream>

#include <QObject>
#include <QList>

#include <QtConcurrent>
#include <QFutureWatcher>

#include "ParserTask.h"

class Parser : public QObject
{
    Q_OBJECT
public:
    Parser(QObject *parent = nullptr)
        : QObject(parent), m_maxThreads(2)
    {}

public slots:
    void appendTask() {
        m_tasksQueue.append(new ParserTask());
        tryStart();
    }

    void taskFinished() {
        auto watcher = static_cast<QFutureWatcher<ParserTask *> *>(QObject::sender());
        std::cout << "PARSED" << std::endl;

        m_currentRunningTasks.removeAll(watcher->result());

        delete watcher->result();
        delete watcher;

        tryStart();
    }

private:
    void tryStart() {
        while(m_currentRunningTasks.size() < m_maxThreads && !m_tasksQueue.isEmpty()) {
            ParserTask* task = m_tasksQueue.takeFirst();
            m_currentRunningTasks.append(task);

            auto watcher = new QFutureWatcher<ParserTask *>(this);
            QObject::connect(watcher, SIGNAL(finished()), SLOT(taskFinished()));
            auto future = QtConcurrent::run(task, &ParserTask::run);
            watcher->setFuture(future);
        }
    }

private:
    int                 m_maxThreads;

    QList<ParserTask *> m_tasksQueue;
    QList<ParserTask *> m_currentRunningTasks;
};

#endif // PARSER_H


ParserTask.h:
#ifndef PARSERTASK_H
#define PARSERTASK_H

#include <QList>
#include <QString>

class ParserTask
{
public:
    struct LineParseResult { QString article, brand, price, stock, multiplicity, __article, price_id; };
    typedef QList<LineParseResult> PResult;

    ParserTask()
    {}

    ~ParserTask() {}
    inline const PResult& result() const       { return m_result;   }

public:
    ParserTask* run() {
        for(int i = 0; i < 700000; i++) {
            LineParseResult r;
            r.article = "111111111111111111";
            r.brand = "2222222222222222222";
            r.multiplicity = "33333333333333";
            r.price = "4444444444444444";
            r.price_id = "55555555555555";
            r.stock = "666666666666666";
            r.__article = "7777777777777777";

            m_result.append(r);
        }

        return this;
    }

private:
    PResult                  m_result;
};

#endif // PARSERTASK_H


main.cpp:
#include <QCoreApplication>

#include "Parser.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Parser p;
    p.appendTask();
    p.appendTask();
    p.appendTask();

    return a.exec();
}


Результат проверялся с помощью top на линуксе (память после окончания работы занята) и на винде (всё ок)

top показывает:
VIRT: 1010804
RES: 803940
для данного процесса

Flassie
() автор топика
Ответ на: комментарий от Flassie

Какой-то адовый быдлокод.

Почему вы не используете QThreadPool + QRunnable?

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

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Программа не выключалась после моего последнего сообщения. Памяти ест столько же

Это минимальный компилируемый пример, сделанный на скорую руку. Но всё равно спасибо :)

А зачем? В чем проблема QtConcurrent? Дело вкуса?

Flassie
() автор топика
Ответ на: комментарий от RazrFalcon

И каким образом я могу очистить кэш вручную?

Flassie
() автор топика
Ответ на: комментарий от Flassie

Проблема в том, что ваш код ужасен. Все что можно сделать плохо - вы сделали.

QThreadPool сделан именно для вашей задачи и сильно упростит код.

И каким образом я могу очистить кэш вручную?

Первая ссылка в гугле.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QList<QString> list;
    for(int i = 0; i < 14000000; i++)
        list.append("11111111");
    list.clear();

    return a.exec();
}



Еще более маленький пример, демонстрирующий то же самое поведение. 800мб памяти в никуда. Очистка кэша не помогает

Flassie
() автор топика
Ответ на: комментарий от Flassie

Прикол. Аналогичная проблема и у меня, на 5.5.1.

Такое чувство, что компилятор оптимизирует код так, что он живёт все время работы программы.

Нужно баг в багтрекер слать или ждать совета более умных товарищей.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon
#include <list>
#include <string>
#include <unistd.h>

int main(int argc, char* argv[]) {

    std::list<std::string> list;

    for(int i = 0; i < 14000000; i++)
        list.push_back("111111111");
    list.clear();

    sleep(10000);
    return 0;
}



Если я не ошибаюсь, то же самое без использования Qt'a

Flassie
() автор топика
Ответ на: комментарий от Flassie

Еще более маленький пример, демонстрирующий то же самое поведение.

Дык, из документации:

Note that the internal array only ever gets bigger over the life of the list. It never shrinks. The internal array is deallocated by the destructor and by the assignment operator, when one list is assigned to another.

Собственно для того стандартным контейнерам и добавляли метод shrink_to_fit, а этого пользовались следующей идиомой (swap to fit):

std::vector<int> v;
//v is swapped with its temporary copy, which is capacity optimal
std::vector<int>(v).swap(v);

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r
#include <vector>
#include <string>
#include <unistd.h>

int main(int argc, char* argv[]) {

    std::vector<std::string> list;

    for(int i = 0; i < 14000000; i++)
        list.push_back("111111111");
    list.clear();
    list.shrink_to_fit();

    sleep(10000);
    return 0;
}



Всё верно?
Если так, то ничего не выходит. Памяти занимает однозначно меньше, но до сих пор ~670мб res и ~729мб virt
Очистка кэша не помогает
Если я что не так делаю - обьясните, пожалуйста, что конкретно

Flassie
() автор топика
Ответ на: комментарий от Flassie

Всё верно?

Верно, но есть нюансы. Стандарт про shrink_to_fit говорит:

It is a non-binding request to reduce capacity() to size(). It depends on the implementation if the request is fulfilled.

То есть гарантий всё-таки нет. Если нужны гарантии, то проще будет пересоздать/перепресвоить переменную. Ну или воспользоваться трюком со swap.

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r
#include <vector>
#include <string>
#include <unistd.h>

int main(int argc, char* argv[]) {

    std::vector<std::string> list;

    for(int i = 0; i < 14000000; i++)
        list.push_back("111111111");
    list.clear();
    list = std::vector<std::string>();

    sleep(10000);
    return 0;
}



То есть так?
Тот же результат

Flassie
() автор топика
Ответ на: комментарий от curufinwe
#include <unistd.h>
#include <time.h>
#include <vector>
#include <string>

class ShrinkToFitTest {
public:
    ShrinkToFitTest() {
        for(int i = 0; i < 7000000; i++)
            m_list.push_back("111111111111111111");
    }

    ~ShrinkToFitTest() {
        m_list.clear();
        m_list.shrink_to_fit();
    }

private:
    std::vector<std::string> m_list;
};

int main(int argc, char* argv[]) {
    ShrinkToFitTest *a = new ShrinkToFitTest();
    delete a;

    sleep(10000);
    return 0;
}



Остается занято 221мб (RES) и 231мб (VIRT)

Flassie
() автор топика
Ответ на: комментарий от Flassie

Проверил в Debian Stable, gcc 4.9.2 (на работе сейчас). Остается вообще больше 400 мб и VIRT и RES.

Приду домой, попробую на gcc 6.

Все-таки интересно, что за борода.

curufinwe ★★★★★
()
Ответ на: комментарий от Flassie

Еще раз только что проверил это

#include <vector>
#include <string>
#include <unistd.h>

int main(int argc, char* argv[]) {

    std::vector<std::string> list;

    for(int i = 0; i < 14000000; i++)
        list.push_back("111111111");
    list.clear();
    list = std::vector<std::string>();

    sleep(10000);
    return 0;
}

Результат 12.8 mb, 2.2mb.

curufinwe ★★★★★
()
Ответ на: комментарий от ism

В данном случае все удаляется правильно

Flassie
() автор топика

Решением проблемы оказалась функция malloc_trim
Объяснение тут

Flassie
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.