Слоты Qt вызываются слишком часто

У меня есть рабочий поток, который справляется с тяжелыми и длинными вычислениями (до десятых долей секунды). Эти вычисления дают несколько тысяч QLines, представляющий ребра динамически растущего дерева. Эти ребра могут быть изменены в любое время, так как они соединяют узлы деревьев путем проверки стоимости, представленной расстоянием. Я хотел бы плавного обновления QGraphicsScene, содержащего края. Я пробовал с сигналом и слотами:

  • Рабочий поток испускает сигнал, поэтому, когда буфер заполнен, этот сигнал перехватывается основным потоком, который справится с обновлением / отрисовкой линии.
  • Этот сигнал по-прежнему перехватывается основным потоком, но, похоже, он излучается очень часто, поэтому QGraphicsView задыхается от QLine быть добавленным
  • Изменение размера буфера не имеет значения
  • Есть ли альтернативный подход к этому?

Основной слот это:

void MainWindow::update_scene(bufferType buffer)
{
  for (int i = 0; i < buffer.size(); ++i)
  {
      if (buffer[i].first < (g_edges.size() - 1))
      {
          delete g_edges[buffer[i].first];
          g_edges[buffer[i].first] = scene->addLine(buffer[i].second);
      }
      else
          g_edges.push_back(scene->addLine(buffer[i].second));
   }
}

Обратите внимание, что bufferType имеет тип QList<std::pair<int,QLine>>, Вот тяжелая вычислительная часть

while (T.size() < max_nodes_number && !_stop)
{
    const cnode random_node = rand_conf ();
    const cnode nearest_node = T.nearest_node (random_node);
    cnode new_node = new_conf (nearest_node, random_node);

    if (obstacle_free(nearest_node, new_node))
    {
        QList<cnode*> X_near = T.neighbours (new_node, max_neighbour_radius);
        cnode lowest_cost_node = nearest_node;
        qreal c_min = nearest_node.cost() + T.distance (nearest_node, new_node);

        for (int j = 0; j < X_near.size(); ++j)
        {
            if (obstacle_free(*X_near[j], new_node) && ((X_near[j]->cost() + T.distance (*X_near[j], new_node)) < c_min))
            {
                c_min = X_near[j]->cost() + T.distance (*X_near[j], new_node);
                lowest_cost_node = *X_near[j];
            }
        }

        T.add_node (new_node, lowest_cost_node.id());
        queue (new_node.id(), QLine (new_node.x(), new_node.y(), lowest_cost_node.x(), lowest_cost_node.y()));

        for (int j = 0; j < X_near.size(); ++j)
        {
            if (obstacle_free(*X_near[j], new_node) && (new_node.cost() + T.distance (new_node, *X_near[j])) < X_near[j]->cost())
            {
                queue (X_near[j]->id(), QLine (new_node.x(), new_node.y(), X_near[j]->x(), X_near[j]->y()));

                T.update_parent (*X_near[j], new_node.id());
                T.rewire_tree (X_near[j]->id());
            }
        }
    }
}
emit finished();

Обратите внимание, что T это класс, представляющий дерево. Он состоит из нескольких методов, позволяющих добавить узел, найти ближайший и т. Д. QList<cnode> как приватный член, хранящий узлы дерева. cnode является структурой, состоящей из двух координат: идентификатора, родителя, стоимости, списка его дочерних элементов.

1 ответ

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

Пакетная работа. В вашем сценарии вы можете объединить вычисленные строки в контейнере, и только когда он достигнет определенного порога, передайте этот контейнер основному потоку для рисования / обновления линий. Порог может быть счетчиком, временем или их комбинацией, вы не хотите не обновлять, если есть только несколько результатов для обновления. Вам нужно будет расширить свой дизайн, чтобы разделить while цикл для запуска в цикле событий потока вместо блокировки, чтобы вы могли периодически собирать и передавать обновления - что-то похожее на это. Это всегда хорошая идея для работников, которые занимают время - вы можете отслеживать прогресс, отмену, паузу и все виды полезных вещей.

Эти 2 строки выглядят подозрительно:

    edges.removeAt(i);
    edges.insert (i, scene->addLine (l));

Затем вы удаляете, а затем вставляете - это приглашение к потенциально дорогостоящему перераспределению, даже без перераспределения возникает ненужное копирование. Вместо удаления и вставки вы можете просто заменить элемент по этому индексу.

В вашем случае вы можете пропустить разделение фактического цикла while. Просто не излучайте в цикле, вместо этого сделайте что-то вроде этого (псевдокод):

while(...) {
    ...
    queue(new line)
    ...
    queue(update line)
    ...
    queue(final flush)
}

void queue(stuff) {
    stuffBuffer.append(stuff)
    if (stuffBuffer.size() > 50 || final_flush) {
        emit do_stuff(stuffBuffer) // pass by copy
        stuffBuffer.clear() // COW will only clear stuffBuffer but not the copy passed above
    } 
}

Или, если это заставит вас чувствовать себя лучше:

    copy = stuffBuffer
    stuffBuffer.clear()
    emit do_stuff(copy)

Таким образом, два контейнера отделяются от общих данных перед отправкой копии.

РЕДАКТИРОВАТЬ: После долгого обсуждения я закончил тем, что предложил ряд изменений в дизайне для повышения производительности (соединения в очереди были только одним аспектом проблемы производительности):

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

  • отключите автоматическое обновление сцены и вместо этого явно управляйте обновлением сцены, таким образом, сцена не обновляется для каждого крошечного изменения.

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

Другие вопросы по тегам