刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少,

其它类软件,非上述版软件
回复
科学之子
帖子: 2284
注册时间: 2013-05-26 6:58
系统: Debian 9

刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少,

#1

帖子 科学之子 » 2018-09-07 15:40

刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少
和常见用法不同的是,我这里用在内置硬盘,而不是USB硬盘之类的慢速移动设备上.

squashfs创建命令:

代码: 全选

sudo mksquashfs "./debian_stretch" "./debian_stretch.1m.squashfs" -comp lz4 -Xhc -b 1M -no-exports -noappend
initramfs挂载命令

代码: 全选

loopdev="$(losetup -f)"
echo 1024 > "/sys/block/$(basename ${loopdev})/queue/read_ahead_kb"
losetup --direct-io=on --read-only "${loopdev}" "${rootmnt}/debian_stretch.1m.squashfs"
echo 1024 > "/sys/block/$(basename ${loopdev})/queue/read_ahead_kb"
mount -r -t squashfs "${loopdev}" "${rootmnt}${ROOTDIR}/overlayfsdir/lower"
mount -t overlay -o "lowerdir=${rootmnt}${ROOTDIR}/overlayfsdir/lower,\
upperdir=${rootmnt}${ROOTDIR}/overlayfsdir/upper,\
workdir=${rootmnt}${ROOTDIR}/overlayfsdir/work" "overlay-root" "${rootmnt}"
需要注意initramfs默认自带的losetup不支持 --direct-io 选项,需要手动加上
hook脚本命令:

代码: 全选

rm "${DESTDIR}/sbin/losetup" 
rm "${DESTDIR}/bin/losetup" 
copy_exec "/sbin/losetup" "/sbin/losetup"
另见:https://manpages.debian.org/stretch/ini ... .8.en.html

不过GUI文件文件管理器没法挂载设备了,可能是squasshfs还没实现ACL的关系.
测试环境:
Linux-kernel 4.14.13
Debian Stretch
Fri Sep 7 15:46:40 CST 2018 补充{
需要注意这种用法(squashfs 的 block-size 设置成最大的1M)会显著增加page cache内存消耗,
推荐在initramfs阶段挂载squashfs前就开启zram
viewtopic.php?p=3209439#p3209439
}
科学之子
帖子: 2284
注册时间: 2013-05-26 6:58
系统: Debian 9

Re: 刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少,

#2

帖子 科学之子 » 2018-09-18 20:43

补充一个简单的benchmark和简单的原理分析(猜测)

代码: 全选

nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.gzip
19.14 13.34
nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.gzip
18.81 12.64
nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.gzip
19.06 12.79
nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.lz4
17.85 13.77
nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.lz4
17.50 12.79
nopti nospectre_v2 nospectre_v1 rw fastboot noresume root=/dev/disk/by-uuid/UUID rootdir=/fast-debian-stretch/test.lz4
17.83 13.24

lz4 is win!
创建命令:

代码: 全选

sudo mksquashfs /overlayfs ./test.lz4 -b 1M -comp lz4 -Xhc -no-exports -noappend
sudo mksquashfs /overlayfs ./test.gzip -b 1M -comp gzip -no-exports -noappend
sync
lz4在我的机器上表现最佳(只测试过lz4和gzip),xz 什么估计就更慢了)不知道在CPU更快和内存更大的机器上是否会出现相反效果(毕竟压缩率更高,如果解压很快的话就相当于预读很快很多).
"-b 1M"参数使用1M大小的数据块,因为机械硬盘特性就是顺序读取超快,一次性顺序读取128K(默认大小)和1M几乎没有什么差别,就算读取到的数据暂时用不到,也可能之后不久会用到,就算真的用不到,也会被内核的缓存算法淘汰,在我以前的测试中1M相比128K有微弱优势(从引导菜单开始计算,普通ext4需要约30秒进入桌面,LZ4HC 128K需要约20秒进入桌面,1M需要约18秒进入桌面,具体测试结果没保存,这段是凭记忆写的,仅供参考)
"-Xhc" 启用lz4hc,应该对解压速度没影响,而且能获得高的数据密度,更有利于放大加速效果
"-no-exports"个人不需要NFS,所以去掉NFS的相关支持,尽可能提高数据密度
"-noappend"不确定追加是否会影响加速效果和系统镜像完整性,所以不要追加.
上次由 科学之子 在 2018-10-23 0:44,总共编辑 1 次。
科学之子
帖子: 2284
注册时间: 2013-05-26 6:58
系统: Debian 9

Re: 刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少,

#4

帖子 科学之子 » 2018-09-27 20:12

又试了试结合e4rat获取系统启动(boot)所需文件的列表,为系统启动所需文件单独建立一个squashfs.
在我的环境下直接把root目录压缩成单个squashfs时,开机用时约17秒
而为启动所需文件单独建立一个squashfs同时配合一个已剔除启动所需文件的squashfs,开机用时约15秒.
结合e4rat提升稍微有点,但跟有无squashfs相比,提升很微弱.

分享一些命令和脚本:
利用e4rat获取系统启动所需文件

代码: 全选

awk '{print $3}' ./boot/initrd-common/startup.log | grep -e '^/usr/' -e '^/lib' -e '^/bin/' -e '^/sbin/' -e '^/etc/' -e '^/my_system_program/' |sudo tee hot-list
通过启动所需文件的列表创建一个只包含启动需文件的root目录

代码: 全选

sudo tar -cf - -T ../hot-list |sudo tar -xf - --touch
根据列表顺序touch每个启动所需文件(不确定这么做是否有用):

代码: 全选

#!/bin/sh
for x in $(cat ./hot-list); do
touch -c -a "./hot-root${x}"
done
删除原始squashfs中的启动所需文件.

代码: 全选

#!/bin/sh
for x in $(cat ./hot-list); do
rm "./cold-root${x}"
done
科学之子
帖子: 2284
注册时间: 2013-05-26 6:58
系统: Debian 9

Re: 刚试了squashfs配合overlayfs做root目录,感觉系统在机械硬盘上快不少,

#5

帖子 科学之子 » 2018-11-16 14:47

仓促发一些资料,既当备份又当分享,有时间再考虑仔细补充和调整(主要是一些试验用的代码和注释没去掉,但功能不影响)

根据e4rat获取的列表用python脚本生成squashfs的sort文件(参考:https://unix.stackexchange.com/a/474826/259284)
被排序后的squashfs启动速度大约14秒左右,相比把e4rat获取的列表单独创建一个squashfs与overlayfs结合,这种方式boot速度稍快一些,也不像套两层squashfs会出现莫名其妙的问题

代码: 全选

#!/bin/python3

import random
random_priorities=list(range(-32768,32768))
random_priorities_len=len(random_priorities)
random_priorities=random.sample(random_priorities,random_priorities_len)

priority=32767
i=0
try:
    while priority>=-32768:
        x=input().lstrip('./')
        entry=x.strip()+' '+str(priority-i)
        print(entry)
        i+=1
except EOFError:
    pass
一个针对squashfs改编的e4rat-preload-lite.c(从这个文件改编)
inode 排序在squashfs已经排序的情况下没有意义,所以去掉了.
不过我的用法是在initramfs里root目录挂载之后立即用chroot运行这个程序在后台(chroot "${rootmnt}" /sbin/e4rat-preload-lite &),这样启动init进程和预读是并行进行的.
使用这个程序预读的目的主要是加速进入桌面后的各种操作.
被排序处理过得squashfs配合1M的数据块本身就在boot上有很好的表现,预读本身可能会对boot造成很小的负面影响(能力有限,测不出具体多少,总之很小,难以测量)

代码: 全选

// Maintained by Simonas Kazlauskas, 2012.
//
// Original version written by John Lindgren11, 2011.
// It can be found on http://e4rat-l.bananarocker.org/
//
// Replacement for e4rat-preload, which was written by Andreas Rid, 2011.
#define _GNU_SOURCE
#define _DEFAULT_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define VERBOSE 0
#define LIST "/var/lib/e4rat/startup.log"
#define INIT "/bin/systemd"
#define MAX_EARLY 0
#define BLOCK 65536
#define BUF 1048576 // = 1 MiB

typedef struct {
    int dev;
    uint64_t inode;
    char *path;
} FileDesc;

static FileDesc **list = NULL;
//static FileDesc **sorted = NULL;
static int listlen = 0;

#ifndef strdup
char *strdup(const char *source);
#endif

//~ static int sort_cb(const void *_a, const void *_b){
    //~ // qsort helper for file list entries. Sorts by device and inode.
    //~ FileDesc *a = *(FileDesc **)_a;
    //~ FileDesc *b = *(FileDesc **)_b;

    //~ if(a->dev < b->dev){
        //~ return -1;
    //~ }
    //~ if(a->dev > b->dev){
        //~ return 1;
    //~ }
    //~ if(a->inode < b->inode){
        //~ return -1;
    //~ }
    //~ if(a->inode > b->inode){
        //~ return 1;
    //~ }
    //~ return 0;
//~ }


static void die(const char *msg){
    printf("Error: %s.\n", msg);
    exit(EXIT_FAILURE);
}


static void free_list(FileDesc **list){
    for (int i = 0; i < listlen; i++) {
        free(list[i]->path);
        free(list[i]);
    }
    free(list);
    list = NULL;
}


static FileDesc *parse_line(const char *line){
    // Parses a line from the file list. Returns NULL in case of parse errors.
    // Expected format of the line:
    //     (device) (inode) (path)
    // Example:
    //     2049 2223875 /bin/bash
    int dev = 0;

    //~ while(*line >= '0' && *line <= '9'){
        //~ dev = dev * 10 + (*line++ - '0');
    //~ }

    //~ if(*line++ != ' '){
        //~ return NULL;
    //~ }

    uint64_t inode = 0;
    //~ while(*line >= '0' && *line <= '9'){
        //~ inode = inode * 10 + ((*line++) - '0');
    //~ }
    //~ if(*line++ != ' '){
        //~ return NULL;
    //~ }

    FileDesc *f = malloc(sizeof(FileDesc));
    if(!f) die("Failed to allocate memory while parsing file list!");
    f->dev = dev;
    f->inode = inode;
    f->path = strdup(line);
    if(!f->path) die("Failed to allocate memory for file path");

    return f;
}


static void load_list(void){
    // Loads list and parses contents into FileDescs
    #if VERBOSE > 0
        printf("Loading %s.\n", LIST);
    #endif

    FILE *stream = fopen(LIST, "r");
    if (!stream) die(strerror(errno));

    int listsize = 0;
    char buf[4096];
    while(1){
        if(!fgets(buf, sizeof buf, stream)){
            break;
        }
        if(buf[0] && buf[strlen(buf) - 1] == '\n'){
            buf[strlen (buf) - 1] = 0;
        }
        FileDesc *f = parse_line(buf);
        if(!f){
            continue;
        }
        if(listlen >= listsize){
            listsize = listsize ? listsize * 2 : 256;
            list = realloc(list, sizeof(FileDesc *) * listsize);
        }
        list[listlen++] = f;
    }
    fclose(stream);

    list = realloc(list, sizeof(FileDesc *) * listlen);
    if(!list) die("Could not allocate memory for lists");
    //~ sorted = malloc(sizeof(FileDesc *) * listlen);
    //~ if(!list || !sorted) die("Could not allocate memory for lists");
    //~ memcpy(sorted, list, sizeof(FileDesc *) * listlen);
    //~ qsort(sorted, listlen, sizeof(FileDesc *), sort_cb);
}


static void load_inodes(const int a, const int b){
    //~ struct stat s;
    //~ for(int i = a; i < ((b < listlen) ? b : listlen); i++){
        //~ stat(sorted[i]->path, &s);
    //~ }
    
    struct stat s;
    for(int i = a; i < b && i < listlen; i ++){
		stat(list[i]->path, &s);
    }
}


static void exec_init(char **argv){
    #if VERBOSE > 0
        printf("Executing %s.\n", INIT);
    #endif

    switch(fork()){
        case -1:
            die(strerror(errno));
        case 0:
            return;
        default:
            execv(INIT, argv);
            die(strerror(errno));
    }
}


static void load_files(int a, int b){
	void *buf = malloc(BUF);
	if(!buf) die("Failed to allocate preload buffer");
    for(int i = a; i < b && i < listlen; i ++){
        int handle = open(list[i]->path, O_RDONLY);
        if(handle < 0){
            continue;
        }
        //readahead(handle,0,~0UL);
        while(read(handle, buf, BUF) > 0){}
        close(handle);
    }
    free(buf);
}

static void load_all(int a, int b){
	struct stat s;
	void *buf = malloc(BUF);
	if(!buf) die("Failed to allocate preload buffer");
    for(int i = a; i < b && i < listlen; i ++){
		stat(list[i]->path, &s);
        int handle = open(list[i]->path, O_RDONLY);
        if(handle < 0){
            continue;
        }
        //readahead(handle,0,~0UL);
        while(read(handle, buf, BUF) > 0){}
        close(handle);
    }
    free(buf);
}

int main(int argc, char **argv){
    int early_load = 0;

    load_list();
    #if VERBOSE > 0
        printf("Preloading %d files.\n", listlen);
    #endif

    //early_load = listlen * 0.33;
    //if(early_load > MAX_EARLY) early_load = MAX_EARLY;
    early_load = MAX_EARLY;

    // Preload a third of the list or MAX_EARLY files (whichever is smaller),
    // then start init.
    load_inodes(0, early_load);
    load_files(0, early_load);
    //~ load_all(0, early_load);
    if (getpid()==(pid_t)(1)){exec_init(argv);}

    // And continue preloading files further in chunks of BLOCK files.
    for(int i = early_load; i < listlen; i += BLOCK){
        load_inodes(i, i + BLOCK);
        load_files(i, i + BLOCK);
        //~ load_all(i, i + BLOCK);
    }

    free_list(list);
    // As sorted originally was a copy of list, it's contents are already
    // freed by the time we are freeing sorted list
    //free(sorted);
    exit(EXIT_SUCCESS);
}
回复