Как стать автором
Обновить

Тестирование мультипоточности в Symbian

Время на прочтение5 мин
Количество просмотров912
Недавно мы ставили SDK для разработки под Qt for Symbian на Linux. Теперь пришло время что-нибудь написать на нем.
Сейчас практически везде используются многопоточные архитектуры для выполнения каких-либо фоновых расчетов в то время как пользователь использует UI.
Давайте разберемся, насколько это эффективно при разработке под Symbian.

Для тестов был взят телефон Nokia 6120c (S60 v3 FP1). В свое время этот телефон был популярен как бюджетный и небольшой смартфон и является вполне типовым устройством на платформе Symbian.

Описание тестирования


В качестве фоновых расчетов был взят алгоритм проверки чисел на простоту. Он достаточно прост и короток и в то же время потребляет нехило процессорного времени.
bool isPrime = true;
for (int i = 2; i <= currentDummy/2; isPrime &= (currentDummy%i) != 0, ++i);


* This source code was highlighted with Source Code Highlighter.


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

Код класса потока (выложен финальный и включает в себя два варианта сразу, об этом подробнее чуть ниже).
//workerthread.h
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H

#include <QThread>

class WorkerThread : public QThread
{
Q_OBJECT
public:
  explicit WorkerThread(int start, int end, int step, QObject *parent = 0);

signals:
  void updateProcess(int value);

public slots:

protected:
  void run();

private:
  int rangeStart;
  int rangeEnd;
  int rangeStep;
};

#endif // WORKERTHREAD_H

//workerthread.cpp
#include "workerthread.h"

WorkerThread::WorkerThread(int start, int end, int step, QObject *parent) :
  QThread(parent), rangeStart(start), rangeEnd(end), rangeStep(step)
{
}

void WorkerThread::run()
{
  int currentDummy = rangeStart;
  int currentProcess = 0;
  while (currentDummy <= rangeEnd)
  {
    bool isPrime = true;
    for (int i = 2; i <= currentDummy/2; isPrime &= (currentDummy%i) != 0, ++i)
    {
      //Method 2. Slower, but more responsive UI
//      if (!(i%500))
//        yieldCurrentThread();
    }
    if (!(currentProcess%1000))
    {
      emit updateProcess(currentProcess);
    }
    currentDummy += rangeStep;
    ++currentProcess;

    //Method 1. Faster, but with UI stucks
    yieldCurrentThread();
  }
  emit updateProcess(currentProcess);
}


* This source code was highlighted with Source Code Highlighter.


Для исследование подтормаживаний UI был взят обычный слайдер, который по таймеру в GUI-потоке менял свое значение. Таким образом, моменты, когда основной поток не получает управление (что и приводит к подтормаживаниям и замерзаниям UI) мы можем отследить по этому слайдеру.
Вот внешний вид формы тестирования:
image
Вполне такой минималистский стиль, выводится только процент выполнения расчетов (прогресс-барами), время выполнения расчетов, индикатор UI, количество потоков и кнопка Start.

Тестирование


Как говорилось ранее, у нас два варианта реализации рабочего потока:
  1. Отдавать (yield) управление другому потоку после каждого проверенного числа
  2. Отдавать (yield) управление другому потоку после некоторого количества проверенных делителей

Даже без тестов понятно, что первый вариант будет быстрее работать, но иногда тормозить UI, а второй будет работать медленнее (за счет большего количества переключений контекста), но и тормозить UI меньше. Весь вопрос насколько это все будет критично и заметно.
Тестировать будем для 1, 2, 3 и 4 рабочих потоков, оценивая время работы и плавность UI (то есть плавность перемещения слайдера).

Тестирование первого варианта


1 поток

Выполнился за 64255мс, при этом было с десяток подтормаживаний (временем около секунды), но в целом слайдер передвигался плавно.

2 потока

Выполнились за 49477мс, при этом было 6 подтормаживаний (временем около секунды). Общие ощущения как от варианта с одним потоком.

3 потока

Выполнились за 45988мс, при этом было всего одно подтормаживание, но оно было с середины расчетов и до конца. Результат совсем не удовлетворительный с точки зрения UI.

4 потока

Выполнились за 46275мс, при этом работало аналогично варианту с 3 потоками.

Выводы по первому варианту

Больше двух рабочих потоков запускать нежелательно, но два потока дают прирост скорости примерно в 25 процентов и практически не влияют на UI.

Тестирование второго варианта


1 поток

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

2 потока

Выполнились за 231544мс, при этом было 6 подтормаживаний (временем около секунды) и также как во варианте с одним потоком очень много мелких.

3 потока

Выполнились за 154797мс, при этом было около 5 подтормаживаний (также временем около секунды), но в целом UI работал плавно.

4 потока

Выполнились за 116959мс, при этом работало аналогично варианту с 3 потоками.

Выводы по второму варианту

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

Общие выводы


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

Мои выводы


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

Скачать описанное выше


Исходники
sis-файл первого варианта
sis-файл второго варианта

UPD про приоритеты потоков


Запустил потоки с Idle и Low приоритетами (запускался только первый вариант тестирования). Результаты оказались даже хуже чем с Normal приоритетом. Два потоки замерли на середине, три и четыре потока замерли почти в самом начале. Скорость отличается в пределах погрешности (48с у двух и 45 у трех и четырех). Судя по всему шедулер не очень хорошо работает с разными приоритетами потоков.

Теги:
Хабы:
Всего голосов 31: ↑28 и ↓3+25
Комментарии42

Публикации