13 May

C++/Qt: пора валить?.

PHPProgrammingC++Qt

image


Если бы раньше я запускал новый долгоживущий проект, в котором основные функции связаны с большим объёмом вычислений при каком-то взаимодействии с пользователем через графический интерфейс, я бы не задумываясь использовал С++/Qt. Это позволяло использовать один фреймворк/язык везде, независимо от структуры проекта и его компонентов, без дополнительных сложностей с зоопарком технологий и зависимостей.

Сейчас, в свете присходящего с Qt, придётся менять подход...



Лирическое вступление


Сразу обращу внимание, что я пишу именно о С++/Qt и чем эту связку заменить, а не C++ как таковом. Да, язык стал, скажем так, «обширным». Но я никогда не понимал нытья про его сложность, костыли и тому подобное. Eсли соблюдать правило, что не надо фанатизма задачу надо решать самым простым из приемлемых способов, код на С++ не лучше и не хуже остальных, при этом язык и его экосистема – одни из самых универсальных. Меня стала смущать именно Qt в этой связке, что тянет за собой и пересмотр области применения С/C++.


А что там, собственно, происходит с Qt?..


Несмотря на то, что Qt – продукт с открытым исходным кодом, правами на него обладает Qt Group Plc. А когда чем-то рулит коммерческая организация, она, вполне естественно, старается извлекать из этого выгоду. И это нормально, пока соблюдается баланс между предлагаемыми преимуществами продукта и его стоимостью/ограничениями.

И вот тут-то и проявляются изменения последнего времени от эффективных менеджеров.


Как менялось лицензирование


Когда-то Qt распространялся на условиях двойной лицензии: «неограниченной» коммерческой и LGPL версии 2. LGPL 2, если упрощать, даёт право использовать Qt в проектах с закрытым исходным кодом, если используется связывание с Qt в её неизменённом виде. То есть можно просто прикладывать к своему приложению «официальную» Qt в виде динамических библиотек (или связывать статически, но предоставлять пользователям всё необходимое, чтобы они могли самостоятельно связать ваше приложение с другим вариантом Qt).


Потом появилась LGPL версии 3. Суть изменений по сравнению с LGPL 2 в том, что она обязывает поставщика приложения обеспечить пользователю возможность заменить вариант Qt, с которым связано приложение на другой, независимо от того, где работает такое приложение. То есть, если вы продаёте пользователю «коробку» (не важно, промышленный контроллер или сотовый телефон) в которой работает приложение, связанное с Qt, вы обязаны обеспечить пользователю возможность попасть внутрь такой коробки и поменять там библиотеки Qt. Что при этом происходит с обеспечением безопасности и т.п. – вопрос сложный. В общем, за пределами десктопов LGPL 3 – это большой геморрой очень неудобная лицензия, а про магазины приложений я даже и начинать не буду...


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


Ну и вообще… Всё, что появляется новое в Qt за рамками коммерческой лицензии, появляется либо на жёстких (LGPL 3), либо полностью запрещающих (GPL) условиях для проектов с закрытым программным кодом.


При этом, мне кажется, что платная версия была бы в большем ходу, если бы коммерческая лицензия была бы более адекватной реалиям. Стоимость такой лицензии – $3950 (~300 тыс. руб) на один год на каждого разработчика. При этом, однажды затащив в проект использование коммерческой версии Qt, за эту лицензию придётся платить до конца жизни проекта, так как любые последующие доработки/исправления/поддержка должны осуществляться с использованием коммерческой лицензии. То есть стоимость лицензий Qt надо закладывать в стоимость поддержки конечного продукта. Кроме того, при использовании коммерческой лицензии Qt усложняется подключение временных внешних разработчиков или использование в небольших проектах.


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


И тут Qt Group Plc взяла и вообще вбросила бомбу, заявив, что последующие LTS-версии (стабильные версии с расширенным сроком поддержки) Qt будут выходить только с коммерческой лицензией.


image


Перспективы


И вот эти последние заявления вызвали очень резкую реакцию со стороны сообщества вокруг Qt. В списке рассылки KDE стали звучать предложения вплоть до форка Qt под эгидой «открытого правительства», поддержанные серьёзными организациями (представитель KDAB открытым текстом сказал, что если будет форк, они его поддержат).


То есть, на горизонте забрезжил вариант раскола сообщества разработчиков. И чем это всё закончится, как отразится на качестве, не уйдёт ли эта история в другую крайность, например использование форка только в полностью открытых проектах – не ясно.


В итоге получается, что Qt пришёл к состоянию, когда он стал фреймворком с ограничениями на применение в широком пласте проектов, ограничениями относительно использования стабильных версий, потенциально фрагментированным сообществом разработки и неясной моделью распространения в будущем.


Весь предыдущий текст я немного упростил, так как всё несколько запутаннее. Например, над Qt Group Plc висит ограничение, позволяющее при определённых обстоятельствах, сделать форк Qt под очень свободной лицензией BSD, но при этом она имеет полное право на задержку выпуска некоммерческих версий на 12 месяцев.


Но суть происходящего именно такая – владелец прав на Qt движется семимильными шагами к максимальному ограничению использования некоммерческой версии Qt и имеет на это право, а остальные негодуют, но возможность форка, последствия фрагментации разработчиков и их влияние на состояние/распространение Qt пока не ясны. А оно нам такое надо? Что вызывает сомнения относительно его применения в новых проектах.


Что вместо?


За что многие любят связку С++ и Qt? За то, что она позволяет решать практически любые задачи без смены контекста. Язык, на котором можно писать быстрые программы, простая, единообразная и хорошо документированная структура, кроссплатформенность, и за всем этим – огромное сообщество.


На что же можно её променять, чтобы не наступить на грабли зависимости от коммерческих структур, не потерять всё, что нажито непосильным трудом возможности переиспользовать старый и писать новый код, требующий вычислительной производительности, и чтобы замена позволяла использовать дешёвый труд была простой в использовании/поддержке?..


Давайте структурно декомпозируем разделим на части тот тип проектов, о которых эта заметка (напомню: какие-то вычисления с каким-то взаимодействием с пользователем). Я не про модель-вид-контроллер и прочее, а про накладываемые требования. Получим два основных компонента: 1) клей из соплей и палок структурная логика, хранение данных и взаимодействие с пользователем, 2) там, где думать надо, и быстро обработка данных.


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


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


Обработка данных


Ну, тут вообще всё просто. Так как С/С++ со своими задачами по обработке данных вполне себе справляется и всем указанным требованиям удовлетворяет, шило на мыло менять смысла нет. Единственное, что тут дополнительно может возникать, так это небольшая обёртка вокруг алгоритмической части на чистом C для внешнего интерфейса, если планируется прямой вызов функций извне, а внутри используется C++. Ну или, как пример, такая же обёртка в виде интерфейса на сокетах. (Только никому не рассказывайте, а то смеяться будут: можно вообще просто запускать процессы и общаться через стандартные потоки, и затраты на это в лунуксах, относительно основной логики, почти никакие).


Общая логика и взаимодействие с пользователем


Да и тут всё более-менее очевидно… Что общедоступно, работает везде, для чего напридумано всё, что только можно, и что никуда не денется в обозримой перспективе, как и наш, прости-господи, любимый, C/C++? Что распространяется даже туда, куда оно «не пришей кобыле хвост» где раньше оно было неприменимо? Что позволит нам без дополнительных телодвижений выносить «тяжёлую» обработку от клиента на внешние мощные серверы?


Web-технологии, естественно. Под web-технологиями я понимаю связку из HTML, CSS и JS, плюс всё, что это генерирует/доставляет/выводит.


Со средствами доставки/вывода со стороны пользователя понятно – это либо браузер, либо какой-нибудь WebView. Ну да, кроссплатформенность, ага...


C HTML/CSS/JS мы ничего поделать не можем, оно уже тут и никуда не денется, значит выбора нет, а раз выбора нет – не надо задумываться, а когда задумываться не надо – голова не заболит.


Остаётся выбрать, что будет выдавать HTML/CSS/JS со стороны приложения.
Опять смотрим на требования (чтобы было просто и могло всё) и перебираем варианты. И тут опять просто.


image


Выбор небогат – это PHP. Да, я сказал PHP. И мне не стыдно.


Ещё раз напомню, о каком типе приложений идёт речь: о тех, где сложная логика, требующая вычислительной производительности, реализована на C/C++, а к ним в пару нам нужен как можно более простой открытый язык/экосистема для общения с внешним миром и связи компонентов между собой. А если будет C-подобный синтаксис – вообще хорошо. И тут мы ставим галочки напротив каждого пункта наших требований.


  • Главное – PHP прост. При том круге задач, которые он (и экосистема) может решать — он божественно прост. И дело не только в когнитивной нагрузке при кодинге и переключении контекста. Простота ещё и в развёртывании, администрировании и минимальном количестве вариантов, которыми можно решить одну задачу.
  • PHP медленно, без резких движений, ползёт в правильную сторону, от увеличения производительности в 3 раза, до строгой типизации, решая задачи простым способом. И обрастая по пути крутыми штуками типа Swoole.
  • PHP реализовал офигенский FFI (foreign function interface) к C. Офигенский в том контексте, о котором идёт речь — сочетании простоты и возможностей. Вы только наберите в гугле «PHP: Basic FFI usage».
  • В PHP кругом $, а кто по нынешнему курсу их не любит...
  • PHP быстрый. Да. Могу по слогам: бы-стрый. Для своей простоты и задач он божественно быстрый.

Тут многие могут начать холивар, если не начали после слов про простоту вспомнить про Node.js и Python, меряться запросами в секунду на голом «Hello, world!» (даже без рендеринга шаблонов, конечно, иначе ж не так понтово выигрышно будет выглядеть). На что я в очередной раз напомню, что в нашем случае, где значительное время уходит на обработку данных на C/C++, все остальные затраты не имеют решающего значения.


Пипкомер


Прошу пардона, не удержался, ибо холивар — дело святое. Поэтому для накидывания субстанции на вентилятор давайте сравним «в лоб» сферических коней в вакууме на PHP и C++/Qt.


Коней для замеров расположим в самом простом одноядерном вакууме с 1 Гб ОЗУ за $5 в месяц, а замерять их будем из другого такого же в том же дата-центре. Перед приложениями – nginx.


Первый конь – скрипт на PHP, выводящий шаблон страницы, на которой показывается значение из базы данных на основе параметра, переданного в запросе. Тормоза – для трусов Для простоты, ошибки не обрабатываем, и вообще не паримся. Но чтобы ещё хоть что-то происходило, для параметра из запроса ещё хэш sha256 посчитаем.


Второй конь – то же самое, но выводится с использованием QtWebApp.


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


Фрагмент на PHP
<?php

$horse_name = htmlspecialchars($_GET['horse_name'], ENT_QUOTES, 'UTF-8');
$horse_hash = hash('sha256', $horse_name);

$database = new PDO("mysql:dbname=vacuum;host=127.0.0.1", 'user', 'password');
$database->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$query = "SELECT `horse_color` FROM `horses` WHERE `horse_hash`=:horse_hash";
$statement = $database->prepare($query);
$statement->bindParam('horse_hash', $horse_hash);
$statement->execute();
$result = $statement->fetch(PDO::FETCH_ASSOC);

if ($result)
  $horse_color = $result['horse_color'];
else
  $horse_color = 'unknown';

?>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Horse Color</title>
</head>

<body>
  <p><?= $horse_name ?> color is <?= $horse_color ?>.</p>
</body>

</html>


Фрагмент с QtWebApp
void HorseHandler::service(HttpRequest& request, HttpResponse& response)
{
    QSqlDatabase database = QSqlDatabase::addDatabase(
        "QMYSQL", QString::number(
                      reinterpret_cast<uint64_t>(QThread::currentThreadId())));

    database.setHostName("127.0.0.1");
    database.setPort(3306);
    database.setDatabaseName("vacuum");
    database.setUserName("user");
    database.setPassword("password");
    database.open();

    QString horseName =
        QString(request.getParameter("horse_name")).toHtmlEscaped();
    QByteArray horseHash =
        QCryptographicHash::hash(horseName.toUtf8(), QCryptographicHash::Sha256)
            .toHex();

    QString queryString(
        "SELECT `horse_color` FROM `horses` WHERE `horse_hash`=:horse_hash");
    QSqlQuery query(database);
    query.prepare(queryString);
    query.bindValue(":horse_hash", horseHash);
    query.exec();

    QString horseColor;
    if (query.next())
        horseColor = query.value("horse_color").toString();
    else
        horseColor = "unknown";

    QString templateString(R"RAW(<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Horse Color</title>
</head>

<body>
  <p>{horse_name} color is {horse_color}.</p>
</body>

</html>)RAW");

    Template horseColorTemplate(templateString, "horseColor");
    horseColorTemplate.setVariable("horse_name", horseName);
    horseColorTemplate.setVariable("horse_color", horseColor);

    response.setHeader("Content-Type", "text/html; charset=UTF-8");
    response.write(horseColorTemplate.toUtf8(), true);
}


В итоге получили, что PHP выдаёт абстрактную фигню страницу даже в полтора раза быстрее.


ab -c 1 для PHP
root@horsesab:~# ab -n 1000 -c 1 http://x.x.x.x:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking x.x.x.x (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.10
Server Hostname:        x.x.x.x
Server Port:            8080

Document Path:          /
Document Length:        155 bytes

Concurrency Level:      1
Time taken for tests:   1.125 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      302000 bytes
HTML transferred:       155000 bytes
Requests per second:    888.77 [#/sec] (mean)
Time per request:       1.125 [ms] (mean)
Time per request:       1.125 [ms] (mean, across all concurrent requests)
Transfer rate:          262.12 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     1    1   0.2      1       6
Waiting:        1    1   0.2      1       6
Total:          1    1   0.2      1       6

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      2
  99%      2
 100%      6 (longest request)
root@horsesab:~#


ab -c 1 для QtWebApp
root@horsesab:~# ab -n 1000 -c 1 http://x.x.x.x:8081/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking x.x.x.x (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.10
Server Hostname:        x.x.x.x
Server Port:            8081

Document Path:          /
Document Length:        155 bytes

Concurrency Level:      1
Time taken for tests:   1.848 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      323000 bytes
HTML transferred:       155000 bytes
Requests per second:    541.26 [#/sec] (mean)
Time per request:       1.848 [ms] (mean)
Time per request:       1.848 [ms] (mean, across all concurrent requests)
Transfer rate:          170.73 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       4
Processing:     1    2   0.3      2       7
Waiting:        1    2   0.3      1       7
Total:          1    2   0.4      2       7
ERROR: The median and mean for the waiting time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%      2
  66%      2
  75%      2
  80%      2
  90%      2
  95%      2
  98%      2
  99%      2
 100%      7 (longest request)
root@horsesab:~#


ab -c 20 для PHP
root@horsesab:~# ab -n 1000 -c 20 http://x.x.x.x:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking x.x.x.x (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.10
Server Hostname:        x.x.x.x
Server Port:            8080

Document Path:          /
Document Length:        155 bytes

Concurrency Level:      20
Time taken for tests:   0.664 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      302000 bytes
HTML transferred:       155000 bytes
Requests per second:    1505.59 [#/sec] (mean)
Time per request:       13.284 [ms] (mean)
Time per request:       0.664 [ms] (mean, across all concurrent requests)
Transfer rate:          444.03 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       6
Processing:     2   13   1.2     13      18
Waiting:        1   13   1.3     13      18
Total:          3   13   1.2     13      18

Percentage of the requests served within a certain time (ms)
  50%     13
  66%     13
  75%     14
  80%     14
  90%     14
  95%     15
  98%     15
  99%     16
 100%     18 (longest request)
root@horsesab:~#


ab -c 20 для QtWebApp
root@horsesab:~# ab -n 1000 -c 20 http://x.x.x.x:8081/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking x.x.x.x (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        nginx/1.17.10
Server Hostname:        x.x.x.x
Server Port:            8081

Document Path:          /
Document Length:        155 bytes

Concurrency Level:      20
Time taken for tests:   1.716 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      323000 bytes
HTML transferred:       155000 bytes
Requests per second:    582.77 [#/sec] (mean)
Time per request:       34.319 [ms] (mean)
Time per request:       1.716 [ms] (mean, across all concurrent requests)
Transfer rate:          183.82 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       2
Processing:     4   34  13.3     34     188
Waiting:        2   34  13.3     34     188
Total:          4   34  13.3     34     188

Percentage of the requests served within a certain time (ms)
  50%     34
  66%     39
  75%     41
  80%     44
  90%     49
  95%     54
  98%     60
  99%     69
 100%    188 (longest request)
root@horsesab:~#


А отсюда избитая банальная истина мораль — вычислительные расходы на обвязку («инфраструктуру») приложения не сильно зависят от того, с помощью чего она реализуется. Отдельные компоненты, на которые и ложится основная нагрузка, от базы данных до внутренней реализации функций PHP, всё равно написаны на относительно низкоуровневых языках и достаточно оптимизированы. И могут быть даже быстрее криво написанных менее отточенных компонентов на номинально более быстрых языках. И где кто в какой ситуации будет быстрее — никто заранее не знает, так как все эти тупые бенчмарки ничего не скажут о той конкретной архитектуре, которая будет у вас в проекте.


Так что можем использовать для «обвязки» и интерфейса то, что проще, более открыто и распространённее, и не переживать. И «PHP плюс веб-фронтенд» для этого — оптимальный выбор.


Но в «тяжёлой» логике это, конечно, не так, поэтому никуда мы от второй части (С/С++) не денемся. И будем, как и раньше, писать её рабоче-крестьянским простым человеческим «Си с классами», регулярно почитывая на Хабре развлекательное нытьё про «задротский С++». Ну а когда встанет вопрос о прям вот оптимизации-оптимизации, то, как и раньше, наденем перчатки и опустим руки в субстанцию использем SIMD-интринзики на несколько строчек «горячего» кода, в каждой строчке прокомментируем что она делает, и ускорим код раз в несколько. А про геморрой красивое метапрограммирование со 100500 уровнями абстракций ради экономии нескольких процентов скорости будем с увлечением читать на «Хабре».


Вместо заключения


Я умышленно избегал технических деталей, чтобы не породить бессмыссленных дебатов о способах реализации конкретных решений. Для связки фронтенда/PHP/C++ cуществует огромное количество возможных комбинаций функций отдельных компонентов и их взаимного размещения, способов хранения информации и обмена сигналами/сообщениями, возможностей разработчиков в конкретном коллективе и т.д. Но однозначно можно говорить, что для всего этого уже всё придумано, на stackoverflow уже всё есть беспокоиться нечего.



Основной посыл такой – в огромном пласте приложений нет особых причин продолжать использовать Qt, а где-то теперь есть и противопоказания. И многое, что надо для замены, и в значительно большем количестве, уже есть в web-технологиях, от компоновки интерфейса до вывода графиков и трёхмерных моделей.
И технологии эти теперь имеют человеческое лицо со всех сторон: для владельца бизнеса, для пользователя, для программиста, и при общении с быстрым нативным кодом. Будем с ними дружить. А вот будем ли скучать по Qt — не знаю...

Tags:C++PHPархитектура приложений
Hubs: PHP Programming C++ Qt
+48
37.2k 99
Comments 301
Ads
Top of the last 24 hours