Qtアプリのバイナリサイズを削減する(1)
という要望が最近多いので、Raspberry Pi 向けの Buildroot で Qt5 をカスタムビルドする方法 をベースに色々な方法をまとめました。
前提条件
主にローエンドのシステムで、1つの Qt アプリケーションで完結するものが対象です。
wayland などを使ったマルチプロセスなシステムは対象外です。
何度も Qt を自分でビルドします。以下の記事をあらかじめ読んで置くといいと思います。
目標
アプリケーションのサイズを10MBくらいにしたい(適当)
アプリの起動後、最初の1フレームを150ms以内に表示したい(適当)
現状の分析
現時点でのアプリケーションのサイズと、それが依存しているライブラリやプラグインのサイズを調べます。
アプリケーションのサイズ
# ls -l /opt/weather/bin/weather -rwxr-xr-x 1 root root 22164 Jan 1 01:55 /opt/weather/bin/weather
実行ファイルは 22164B, 21.6KB と、とてもコンパクトです。
ライブラリのサイズ
# ldd /opt/weather/bin/weather libQt5Quick.so.5 => /opt/qt/lib/libQt5Quick.so.5 (0x76bde000) libQt5Gui.so.5 => /opt/qt/lib/libQt5Gui.so.5 (0x76671000) libQt5QmlModels.so.5 => /opt/qt/lib/libQt5QmlModels.so.5 (0x765df000) libQt5Qml.so.5 => /opt/qt/lib/libQt5Qml.so.5 (0x76246000) libQt5Network.so.5 => /opt/qt/lib/libQt5Network.so.5 (0x7610f000) libQt5Core.so.5 => /opt/qt/lib/libQt5Core.so.5 (0x75b44000) libGLESv2.so => /usr/lib//libGLESv2.so (0x75b22000) libbcm_host.so => /usr/lib//libbcm_host.so (0x75b0d000) libvcos.so => /usr/lib//libvcos.so (0x75af4000) libvchiq_arm.so => /usr/lib//libvchiq_arm.so (0x75ade000) libstdc++.so.6 => /usr/lib//libstdc++.so.6 (0x759d5000) libgcc_s.so.1 => /lib//libgcc_s.so.1 (0x759a6000) libc.so.0 => /lib//libc.so.0 (0x75911000) ld-uClibc.so.1 => /lib/ld-uClibc.so.0 (0x76fa2000) libEGL.so => /usr/lib//libEGL.so (0x758e1000) libkhrn_client.so => /usr/lib//libkhrn_client.so (0x758ce000) libvchostif.so => /usr/lib//libvchostif.so (0x758a9000) libvcfiled_check.so => /usr/lib//libvcfiled_check.so (0x75897000)
Qt のライブラリ(libQt5*.so)が6つリンクされています。
# ls -l /opt/qt/lib/libQt5*.so.5.14.0 -rwxr-xr-x 1 root root 22224 Jun 21 2019 /opt/qt/lib/libQt5Concurrent.so.5.14.0 -rwxr-xr-x 1 root root 5994896 Jun 21 2019 /opt/qt/lib/libQt5Core.so.5.14.0 -rwxr-xr-x 1 root root 526248 Jun 21 2019 /opt/qt/lib/libQt5DBus.so.5.14.0 -rwxr-xr-x 1 root root 1190664 Jun 21 2019 /opt/qt/lib/libQt5EglFSDeviceIntegration.so.5.14.0 -rwxr-xr-x 1 root root 5608228 Jun 21 2019 /opt/qt/lib/libQt5Gui.so.5.14.0 -rwxr-xr-x 1 root root 1207792 Jun 21 2019 /opt/qt/lib/libQt5Network.so.5.14.0 -rwxr-xr-x 1 root root 3700816 Jun 21 2019 /opt/qt/lib/libQt5Qml.so.5.14.0 -rwxr-xr-x 1 root root 530184 Jun 21 2019 /opt/qt/lib/libQt5QmlModels.so.5.14.0 -rwxr-xr-x 1 root root 43020 Jun 21 2019 /opt/qt/lib/libQt5QmlWorkerScript.so.5.14.0 -rwxr-xr-x 1 root root 3877152 Jun 21 2019 /opt/qt/lib/libQt5Quick.so.5.14.0 -rwxr-xr-x 1 root root 467452 Jun 21 2019 /opt/qt/lib/libQt5QuickParticles.so.5.14.0 -rwxr-xr-x 1 root root 175508 Jun 21 2019 /opt/qt/lib/libQt5QuickShapes.so.5.14.0 -rwxr-xr-x 1 root root 154972 Jun 21 2019 /opt/qt/lib/libQt5QuickTest.so.5.14.0 -rwxr-xr-x 1 root root 261276 Jun 21 2019 /opt/qt/lib/libQt5Sql.so.5.14.0 -rwxr-xr-x 1 root root 292372 Jun 21 2019 /opt/qt/lib/libQt5Test.so.5.14.0 -rwxr-xr-x 1 root root 203100 Jun 21 2019 /opt/qt/lib/libQt5Xml.so.5.14.0
Core, Network, Gui, Qml, QmlModels, Quick を足すと、20919068B なので 20MB となります。
プラグインのサイズ
Qt アプリケーションが動的にロードしているプラグインを知る方法 を参考に調べます。
# QT_DEBUG_PLUGINS=1 /opt/weather/bin/weather 2>&1 |grep loaded loaded library "/opt/qt/plugins/platforms/libqeglfs.so" loaded library "/opt/qt/plugins/egldeviceintegrations/libqeglfs-brcm-integration.so" loaded library "/opt/qt/plugins/imageformats/libqgif.so" loaded library "/opt/qt/plugins/imageformats/libqico.so" loaded library "/opt/qt/plugins/imageformats/libqjpeg.so" loaded library "/opt/qt/qml/QtQuick.2/libqtquick2plugin.so" loaded library "/opt/qt/qml/QtQuick/Window.2/libwindowplugin.so" loaded library "/opt/qt/qml/QtQml/libqmlplugin.so" loaded library "/opt/qt/qml/Qt/labs/settings/libqmlsettingsplugin.so" loaded library "/opt/qt/qml/QtQml/Models.2/libmodelsplugin.so" loaded library "/opt/qt/plugins/bearer/libqconnmanbearer.so" loaded library "libdbus-1" loaded library "/opt/qt/plugins/bearer/libqgenericbearer.so" loaded library "/opt/qt/plugins/bearer/libqnmbearer.so" ^C
Qt のプラグインが 810980B で、QtQuick のプラグインが 69384B という結果になりました。
全体のサイズ
Qt アプリケーション | 22164 |
Qt ライブラリ | 20919068 |
Qt プラグイン | 810980 |
QtQuick プラグイン | 69384 |
合計 | 21821596 |
静的リンクにしてみよう
トータルのバイナリサイズを簡単に削減する方法としてよく使われる方法の1つが静的リンクです。(ここではQt関連の)すべてのバイナリを一つのバイナリにし、リンカが不必要なシンボルを削除することで全体のサイズが大幅に削減されます。
Qt とアプリケーションのビルド -static
$ mkdir -p ~/io/qt/code/qt/dev/static $ cd $_ $ ../qt5/configure -opensource -confirm-license -release -make libs -no-widgets -no-pch -prefix /opt/qt-static -hostprefix $PWD/root -ccache -opengl es2 -device linux-rasp-pi3-g++ -device-option CROSS_COMPILE=~/org/buildroot/raspberry-pi3/host/bin/arm-buildroot-linux-uclibcgnueabihf- -sysroot ~/org/buildroot/raspberry-pi3/host/arm-buildroot-linux-uclibcgnueabihf/sysroot/ -static $ make -j4 $ make install $ mkdir -p ~/com/github/task-jp/weather/static $ cd $_ $ ~/io/qt/code/qt/dev/static/root/bin/qmake -r ../weather $ make -j4
プラグインがリンクエラーになる場合
Qt 5.9 ~ Qt 5.12.4, Qt 5.13.0 あたりのバージョンでは、以下のようなエラーが出るかもしれません。
arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/platforms/libqeglfs.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/imageformats/libqgif.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/imageformats/libqico.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/imageformats/libqjpeg.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/egldeviceintegrations/libqeglfs-brcm-integration.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/egldeviceintegrations/libqeglfs-emu-integration.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_debugger.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_inspector.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_local.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_messages.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_native.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_nativedebugger.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_preview.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_profiler.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_quickprofiler.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_server.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/qmltooling/libqmldbg_tcp.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/bearer/libqconnmanbearer.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/bearer/libqgenericbearer.a: No such file or directory arm-buildroot-linux-uclibcgnueabihf-g++.br_real: error: /opt/qt-static/plugins/bearer/libqnmbearer.a: No such file or directory
[QTBUG-71673] static build fails to find plugins for application build がバグレポートで、Qt 5.12.5, Qt 5.13.1 で修正される予定となっています。
本日時点での qt5.git の dev ブランチの qtbase にはこの修正が forwardport されていないため、手動で取り込みます。
$ cd ~/io/qt/code/qt/dev/qt5/qtbase $ git cherry-pick 978987981ac1ad0689e042cc241c1732496b59bb $ cd ../static $ make && make install $ cd ~/com/github/task-jp/weather/static $ make && make install
バイナリサイズの確認 -static
$ ls -l weather -rwxr-xr-x 1 tasuku users 17321224 Jun 21 18:14 weather
まとめ
デフォルトビルド | 21821596B | 100% |
-static | 17321224B | 79.4% |
今回、静的リンクにすることで、2割強、トータルのバイナリのサイズを削減することができました。
なお、LGPL版の Qt で静的リンクを利用する場合は、アプリケーションの配布時に以下のような対応が必要になるようです。
(LGPLの)及ぶ作品に対し、静的 vs 動的にリンクされたモジュールについて、LGPLには異なる要求がありますか?
(1) LGPLのライブラリに対し静的にリンクする場合、ユーザがライブラリを改変してアプリケーションと再リンクできる機会のために、あなたは、あなたのアプリケーションを、オブジェクト(ソースの必要は必ずしもありません)フォーマットでも提供する必要があります。
GNUライセンスに関してよく聞かれる質問
(2) ユーザのコンピュータに既に存在するLGPLのライブラリに対し動的にリンクする場合、あなたは、ライブラリのソースを運搬する必要はありません。一方、あなたのアプリケーションと一緒にLGPLのライブラリの実行形式をあなた自身が運搬する場合、それが静的、あるいは動的にリンクされているかによらず、LGPLが提供する方法の一つでライブラリのソースを運搬する必要があります。