Недавно мы ставили SDK для разработки под Qt for Symbian на Linux. Теперь пришло время что-нибудь написать на нем.
Сейчас практически везде используются многопоточные архитектуры для выполнения каких-либо фоновых расчетов в то время как пользователь использует UI.
Давайте разберемся, насколько это эффективно при разработке под Symbian.
Для тестов был взят телефон Nokia 6120c (S60 v3 FP1). В свое время этот телефон был популярен как бюджетный и небольшой смартфон и является вполне типовым устройством на платформе Symbian.
В качестве фоновых расчетов был взят алгоритм проверки чисел на простоту. Он достаточно прост и короток и в то же время потребляет нехило процессорного времени.
Для чистоты эксперимента мы пробегаем все числа, а не останавливаемся на первом найденном делителе (иначе потоки бы завершались с очень большим временным разбросом). Данные между потоками разделены равномерно и не отрезками числовой прямой, а чередуются между потоками (что опять же дает нам более честный эксперимент с практически одновременно завершающимися потоками).
Код класса потока (выложен финальный и включает в себя два варианта сразу, об этом подробнее чуть ниже).
Для исследование подтормаживаний UI был взят обычный слайдер, который по таймеру в GUI-потоке менял свое значение. Таким образом, моменты, когда основной поток не получает управление (что и приводит к подтормаживаниям и замерзаниям UI) мы можем отследить по этому слайдеру.
Вот внешний вид формы тестирования:
Вполне такой минималистский стиль, выводится только процент выполнения расчетов (прогресс-барами), время выполнения расчетов, индикатор UI, количество потоков и кнопка Start.
Как говорилось ранее, у нас два варианта реализации рабочего потока:
Даже без тестов понятно, что первый вариант будет быстрее работать, но иногда тормозить UI, а второй будет работать медленнее (за счет большего количества переключений контекста), но и тормозить UI меньше. Весь вопрос насколько это все будет критично и заметно.
Тестировать будем для 1, 2, 3 и 4 рабочих потоков, оценивая время работы и плавность UI (то есть плавность перемещения слайдера).
Выполнился за 64255мс, при этом было с десяток подтормаживаний (временем около секунды), но в целом слайдер передвигался плавно.
Выполнились за 49477мс, при этом было 6 подтормаживаний (временем около секунды). Общие ощущения как от варианта с одним потоком.
Выполнились за 45988мс, при этом было всего одно подтормаживание, но оно было с середины расчетов и до конца. Результат совсем не удовлетворительный с точки зрения UI.
Выполнились за 46275мс, при этом работало аналогично варианту с 3 потоками.
Больше двух рабочих потоков запускать нежелательно, но два потока дают прирост скорости примерно в 25 процентов и практически не влияют на UI.
Выполнился за 463372мс, при этом было с десяток подтормаживаний (временем около секунды) и очень много мелких (гораздо меньше секунды, но заметных глазу).
Выполнились за 231544мс, при этом было 6 подтормаживаний (временем около секунды) и также как во варианте с одним потоком очень много мелких.
Выполнились за 154797мс, при этом было около 5 подтормаживаний (также временем около секунды), но в целом UI работал плавно.
Выполнились за 116959мс, при этом работало аналогично варианту с 3 потоками.
Естественно что тест с одним потоком в этом варианте нужен просто для сравнения, так как подобный агрессивный йелдинг не дает каких-либо преимуществ для одного потока. Тест с двумя потоками также показал не очень хорошие результаты из-за слишком больших накладных расходов на планировщик. Тесты же с тремя и четырьмя потоками показали себя очень хорошо.
В случае, когда нам нужно разделить какие-то однородные вычисления на потоки лучше конечно пользоваться первым вариантом с двумя рабочими потоками. Но в случае, когда нам нужно выполнять какие-то неоднородные операции в разных потоках второй вариант выглядит лучше первого (хоть он и затрачивает времени в два-три раза больше), так как он позволяет пользователю нормально работать с UI во время расчетов.
Я рад подобным результатам, так как они наглядно показывают что даже для бюджетных (и уже достаточно старых) устройств на Symbian можно писать многопоточные приложения без особых проблем, пусть и с несколькими оговорками и дополнительными тестами (например на оптимальную частоту йелдинга).
Исходники
sis-файл первого варианта
sis-файл второго варианта
Запустил потоки с Idle и Low приоритетами (запускался только первый вариант тестирования). Результаты оказались даже хуже чем с Normal приоритетом. Два потоки замерли на середине, три и четыре потока замерли почти в самом начале. Скорость отличается в пределах погрешности (48с у двух и 45 у трех и четырех). Судя по всему шедулер не очень хорошо работает с разными приоритетами потоков.
Сейчас практически везде используются многопоточные архитектуры для выполнения каких-либо фоновых расчетов в то время как пользователь использует 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) мы можем отследить по этому слайдеру.
Вот внешний вид формы тестирования:
Вполне такой минималистский стиль, выводится только процент выполнения расчетов (прогресс-барами), время выполнения расчетов, индикатор UI, количество потоков и кнопка Start.
Тестирование
Как говорилось ранее, у нас два варианта реализации рабочего потока:
- Отдавать (yield) управление другому потоку после каждого проверенного числа
- Отдавать (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 у трех и четырех). Судя по всему шедулер не очень хорошо работает с разными приоритетами потоков.