14.12.2016
V minulém článku o paralelním programování jsem nastínil, jak paralelní výpočty v Qt fungují a co je k tomu potřeba. Pro připomenutí: abychom mohli pracovat v Qt paralelně, potřebujeme nejméně dvě komponenty: vstupní data v některém z kontejnerů Qt (typicky QList) a funkci, která s jednou položkou vstupních dat dokáže provést požadovanou akci.
Občas mám v adresáři větší množství různých obrázků a potřebuju je zmenšit na nějakou maximální velikost. Tento problém občas skutečně řešívám a používám na to jednoduchý skript používající netpbm napsaný přímo na povelové řádce. Nutno přiznat, že skriptem na povelové řádce jsem s obrázky nikdy nemanipuloval paralelně - vždy hezky jeden po druhém. Je to nejjednodušší způsob, jak s obrázky manipulovat - psát něco podobného v C++ by mě jakživ nenapadlo. Jako příklad paralelního programování se ale taková úloha hodí velice dobře.
Roli vstupních dat v příkladu zde hraje QStringList s názvy jednotlivých souborů.
Názvy souborů se předávají programu jako argumenty na povelové řádce. V Qt získáme
seznam argumentů velice snadno voláním QCoreApplication::arguments()
.
Je třeba pamatovat na to, že v první položce v kontejneru není první soubor, ale
název programu - to je vlastnost operačního systému Unix. Před paralelním zmenšením
obrázků je třeba první položku ze seznamu vyhodit.
QStringList files = QCoreApplication::arguments(); files.takeFirst();
Výpočetní funkce dostane jako parametr jednu položku ze vstupních dat a provede s touto položkou požadovanou operaci. V našem případě dostane funkce v parametru jméno některého vstupního souboru s obrázkem a jejím úkolem je tento soubor načíst, zmenšit a uložit do nového souboru s modifikovaným jménem. V Qt může být taková funkce velmi jednoduchá:
void resize(const QString& filename) { QImage image(filename); QImage scaled = (image.width() > image.height()) ? image.scaledToWidth(196) : image.scaledToHeight(196); scaled.save("resized-"+filename); }
Pro paralelní výpočty nabízí knihovna Qt několik různých variant (map, filter, reduce, blocking a jejich kombinace). Pro naše účely je nejvhodnější blokující funkce map:
QtConcurrent::blockingMap ( files, // Vstupní data resize // Map funkce );
#include <QCoreApplication> #include <QtConcurrent> #include <QImage> #include <QDebug> void resize(const QString& filename) { QImage image(filename); QImage scaled = (image.width() > image.height()) ? image.scaledToWidth(196) : image.scaledToHeight(196); scaled.save("resized-"+filename); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); QStringList files = QCoreApplication::arguments(); files.takeFirst(); if (files.isEmpty()) { qDebug() << "Usage: resize filename1.jpg filename2.jpg ..."; return 1; } QtConcurrent::blockingMap (files, resize); return 0; }
Jednodušší už to být snad ani nemůže.
Program jsem zkoušel zhruba na tisícovce jpg obrázků. Pro testování jsem použil osmijádrový procesor AMD FX-8350.
Pro porovnání jsem vytvořil jednovláknovou variantu, kde místo volání QtConcurrent::blockingMap() byl jednoduchý cyklus:
for (int i=0; i<files.size(); i++) { resize(files[i]); }
A tady je výsledek:
time ../resize *.jpg real 0m3.507s user 0m3.344s sys 0m0.155s
Při prvním pohledu na výsledek jsem chvíli koukal, že celkově spotřeboval program více strojového času, než trval běh aplikace. Ale potom mi docvaklo - vždyť to přece běželo paralelně na více jádrech! Zajímavé je, že paralelní varianta výpočtu spotřebovala strojového času více, než jednovláknová varianta. Takto veliký rozdíl se objevoval i při opakovaném výpočtu. I přes více spotřebovaného strojového času dokončil program svou práci několikanásobně rychleji.
time ../resize *.jpg real 0m0.633s user 0m4.302s sys 0m0.171s
I přes použití osmijádrového procesoru je zrychlení jen zhruba pětinásobné. Urychlit výpočet ještě více bude pravděpodobně nemožné - velké množství času zde nejspíš padne na úkor IO operací (čtení a zápis na disk). V dalším dílu tohoto seriálu si ukážeme, že při čistě výpočetních operacích může zrychlení výpočtu mnohem lépe korespondovat s počtem jader procesoru.
Tento článek je jedním z několika článků o paralelním programování v Qt. Sledujte tyto stránky, sledujte náš Twitter. Další díly budou následovat.