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

まとめ

デフォルトビルド21821596B100%
-static17321224B79.4%

今回、静的リンクにすることで、2割強、トータルのバイナリのサイズを削減することができました。

なお、LGPL版の Qt で静的リンクを利用する場合は、アプリケーションの配布時に以下のような対応が必要になるようです。

(LGPLの)及ぶ作品に対し、静的 vs 動的にリンクされたモジュールについて、LGPLには異なる要求がありますか?

(1) LGPLのライブラリに対し静的にリンクする場合、ユーザがライブラリを改変してアプリケーションと再リンクできる機会のために、あなたは、あなたのアプリケーションを、オブジェクト(ソースの必要は必ずしもありません)フォーマットでも提供する必要があります。
(2) ユーザのコンピュータに既に存在するLGPLのライブラリに対し動的にリンクする場合、あなたは、ライブラリのソースを運搬する必要はありません。一方、あなたのアプリケーションと一緒にLGPLのライブラリの実行形式をあなた自身が運搬する場合、それが静的、あるいは動的にリンクされているかによらず、LGPLが提供する方法の一つでライブラリのソースを運搬する必要があります。

GNUライセンスに関してよく聞かれる質問

あわせて読みたい