Linux下如何多線程打包

文件結構

data 					# 待打包的文件夾
├── DEBIAN				# 這裏放着各種安裝包的描述、配置文件,還有安裝前後執行的腳本等
│   ├── control
│   ├── copyright
│   └── postinst
├── opt					# 安裝目錄下的相對內容
├── etc					# 安裝目錄下的相對內容
└── usr					# 安裝目錄下的相對內容

打包

data的上一級目錄執行

dpkg-deb -Z xz -z 9 --build ./data test.deb

這個命令會在當前目錄生成一個 test.deb就是打包好的安裝包

多線程打包

在上述命令上多添加一個參數:--compress-program=pixz

dpkg-deb --compress-program=pixz -Z xz -z 9 --build ./data test.deb
  • pixz是一個多線程xz壓縮工具, 需要提前安裝sudo apt-get install pixz

dpkg-deb

當然,默認的dpkg-deb是沒有上面那個參數的,這個dpkg-deb是我修改源碼後重新編譯的版本。如果你也有多線程打包的需求,可以使用這樣兩個方案:

  1. 下載使用我的dpkg-deb:https://download.csdn.net/download/Three_dog/12027997
  2. 下載dpkg源碼,修改代碼內容。編譯生成dpkg-deb使用。

方案二中,如何下載編譯dpkg請參見:https://blog.csdn.net/Three_dog/article/details/103418141
下載後修改的內容不多,我懶得一個一個文件改動寫那麼詳細了,這裏直接貼個diff,有需要的小夥伴對照着看下應該知道該改哪裏。

diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 3317b51..0fc7e96 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -582,6 +582,7 @@ do_build(const char *const *argv)
     control_compress_params.type = COMPRESSOR_TYPE_GZIP;
     control_compress_params.strategy = COMPRESSOR_STRATEGY_NONE;
     control_compress_params.level = -1;
+    control_compress_params.program = NULL;
     if (!compressor_check_params(&control_compress_params, &err))
       internerr("invalid control member compressor params: %s", err.str);
   }
diff --git a/dpkg-deb/main.c b/dpkg-deb/main.c
index 3420e44..4405a24 100644
--- a/dpkg-deb/main.c
+++ b/dpkg-deb/main.c
@@ -112,6 +112,8 @@ usage(const struct cmdinfo *cip, const char *value)
 "  -S<strategy>                     Set the compression strategy when building.\n"
 "                                     Allowed values: none; extreme (xz);\n"
 "                                     filtered, huffman, rle, fixed (gzip).\n"
+"      --compress-program=<PROG>    Use PROG for compression instead of builtin\n"
+"                                   implementation. (must accept level: -0..-9)\n"
 "\n"));
 
   printf(_(
@@ -200,6 +202,14 @@ set_compress_type(const struct cmdinfo *cip, const char *value)
     badusage(_("obsolete compression type '%s'; use xz or gzip instead"), value);
 }
 
+static void
+set_compress_program(const struct cmdinfo *cip, const char *value)
+{
+  free(compress_params.program);
+  compress_params.program = m_strdup(value);
+}
+
+
 static const struct cmdinfo cmdinfos[]= {
   ACTION("build",         'b', 0, do_build),
   ACTION("contents",      'c', 0, do_contents),
@@ -223,6 +233,7 @@ static const struct cmdinfo cmdinfos[]= {
   { NULL,            'z', 1, NULL,           NULL,         set_compress_level },
   { NULL,            'Z', 1, NULL,           NULL,         set_compress_type  },
   { NULL,            'S', 1, NULL,           NULL,         set_compress_strategy },
+  { "compress-program", 0, 1, NULL,          NULL,         set_compress_program },
   { "showformat",    0,   1, NULL,           &showformat,  NULL             },
   { "help",          '?', 0, NULL,           NULL,         usage            },
   { "version",       0,   0, NULL,           NULL,         printversion     },
diff --git a/lib/dpkg/compress.c b/lib/dpkg/compress.c
index 44075cd..7a76a87 100644
--- a/lib/dpkg/compress.c
+++ b/lib/dpkg/compress.c
@@ -20,6 +20,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+
+
+
+
 #include <config.h>
 #include <compat.h>
 
@@ -882,6 +886,21 @@ compress_filter(struct compress_params *params, int fd_in, int fd_out,
 {
 	va_list args;
 	struct varbuf desc = VARBUF_INIT;
+	if (params->program) {
+		struct command cmd;
+		char level[] = "-0";
+
+		command_init(&cmd, params->program, "compress program");
+		command_add_arg(&cmd, params->program);
+
+		level[1] += params->level;
+		command_add_arg(&cmd, level);
+
+		m_dup2(fd_in, STDIN_FILENO);
+		m_dup2(fd_out, STDOUT_FILENO);
+		command_exec(&cmd);
+	}
+
 
 	va_start(args, desc_fmt);
 	varbuf_vprintf(&desc, desc_fmt, args);
diff --git a/lib/dpkg/compress.h b/lib/dpkg/compress.h
index 08aaf25..a629501 100644
--- a/lib/dpkg/compress.h
+++ b/lib/dpkg/compress.h
@@ -57,6 +57,7 @@ enum compressor_strategy {
 struct compress_params {
 	enum compressor_type type;
 	enum compressor_strategy strategy;
+	char * program ;
 	int level;
 };

實現原理

打包dpkg-deb的時候,壓縮過程它使用的是內置的算法,這個算法是單線程的,無法發揮多核CPU的優勢。

而這個改動,給dpkg-deb加了一個參數。這個參數可以指定一個應用程序,在打包進行到壓縮步驟的時候,調用這個指定的程序進行壓縮。當然,這個程序打包的類型,必須和-Z指定的類型一致。

我這裏使用的是壓縮率最高的xz格式,這裏指定的外部程序叫做pixz, 這個程序默認會使用最大線程數進行全量壓縮,通過這種辦法曲線救國,實現了dpkg-deb的多線程打包。

一些多線程的壓縮工具pigz/pbzip2/pxz/pixz等等都可以在這裏使用以提高效率。

參考鏈接

這個方案當然也不是我拍腦袋想出來的,還是下載了一個外國人針對舊版本的diff,對着現在的代碼改的,他原文也提到了這種方案最好打打平時的構建包和測試包,如果發佈的話,最好還是老老實實用原版的dpkg-deb。
鏈接1:https://askubuntu.com/questions/841784/any-way-to-multithread-dpkg-deb
鏈接2:https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=501456#27
有能力的小夥伴可以看看這倆鏈接,着實給我打開了新世界的大門。

另外,網上說的什麼fpm debuild dpkg-buildpackage 說這個支持多線程打包, 然而這些全都是對dpkg-deb和rpm的封裝,最後還是調用這些工具實現。多線程僅僅在編譯階段是多線程,對於打包可以說卵用沒有。

rpm

deb的這個方案大概折騰了一禮拜。而rpm的方案,折騰了兩個禮拜結論是不可行。。。。
rpm的代碼耦合性比較大,沒有辦法指定第三方的壓縮工具壓縮,底層依賴的cpio的算法進行壓縮,外部工具插不上手。

雖然官方在後續版本,也說實現了多線程打包的功能:https://github.com/rpm-software-management/rpm/issues/211

在4.14版本之後的代碼裏應該就已經帶上了,ubuntu源裏默認安裝的是4.12版本。我下載了最新的源碼編譯安裝後仍然不可行。

最終在github上也得到了rpm項目維護者的確認,這個暫時。。。無法實現。
https://github.com/rpm-software-management/rpm/issues/970


更新一下rpm的解決方案,當時得出無法實現的結論後,同事的大佬找到了另一個issuse,讓我看看這個人說的方法能搞不:https://github.com/rpm-software-management/rpm/issues/113
在這裏插入圖片描述
我看了一下,還真能。大概意思就是,如果你的本機xz版本是5.2以上的話,可以在執行rpmbuild命令時指定binary_payloadw9T12.xzdio這樣的方式,這樣它就會使用新版xz的多線程壓縮進行打包。

所以首先:編譯安裝新版xz,ubuntu16.04自帶的是5.1版本的。

wget https://tukaani.org/xz/xz-5.2.4.tar.gz
tar zxvf xz-5.2.4.tar.gz
cd xz-5.2.4
./autogen.sh
./configure
make 
sudo make install 

報錯我沒記錄,大家遇到啥自行解決一下,都不復雜。
但是替換了xz之後並沒有生效,還需要把rpm重新編譯一下。編譯的時候,必須指定鏈接新的liblzma.so的庫,這樣打包出來的rpm才能多線程壓縮。修改rpm源碼裏的autogen.sh成這樣:

#!/bin/sh

export CPPFLAGS="/usr/local/lib/liblzma.so"
export CFLAGS="-I/usr/include/lua5.2 -I/usr/include/nspr -I/usr/include/nss /usr/local/lib/liblzma.so"
export LDFLAGS="-llua5.2"
export LUA_LIBS="-I/usr/lib64" 
export LUA_CFLAGS="-I/usr/bin"


autoreconf -i

sed -i "s/sysconfdir='\${prefix}\/etc'/sysconfdir='\/etc'/g" ./configure  # 自動生成的configure文件中,sysconfdir的路徑指定的是{$prefix}/etc,打包的時候會有問題,應該改爲/etc

case "$1" in
  "--noconfigure")
    exit 0;
    ;;
  "--rpmconfigure")
    shift
    eval "`rpm --eval %configure`" "$@"
    ;;
  *)
    ./configure "$@" --prefix="/usr"
    ;;
esac

編譯安裝後,現在的rpmbuild就支持多線程壓縮了。編譯命令要記得指定w9T12.xzdio,w後面是壓縮等級,0到9,9最高,T後面是最大線程數,最好和CPU線程數一致,xz是壓縮類型,這裏必須是xz,lzdio也就是lzma還是隻能單線程。

我在實踐過程中仍然有小問題:

  • 在我使用w9T12的時候,CPU最高佔用只有600%,也就是用了6個線程。可我的CPU最高支持12個。

一開始我以爲是它獲取CPU核數錯了,但是後來發現不是。當我使用w6T12的時候,CPU佔用可以達到1200%,w7T12最高1000%,w8T12最高800% 。

經過調查發現這個限制是因爲xz,在xz多線程壓縮的時候,會預先給文件進行分塊,分成幾部分,每一部分一個線程,而壓縮等級爲9的時候,我的原文件只被分成了6個部分,因此最多隻有6個線程同時工作。

xz的文件分塊,和源文件的大小以及壓縮等級有關,最終在我的環境下只分成了6各部分。嘗試分析了xz代碼的這部分邏輯,但是對我而言實在是有點複雜,看了一圈不覺明歷,所以暫時也不會改這個地方。

最終雖然rpm的多線程打包方案不算是一個很完美的解決,但是至少會比以前快出來不少。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章