Jimmy Chen

A Programmer

(原创)zram算法比较以及zstd移植

  zram类似压缩饼干,是将RAM中暂时不用的部分RAM使用压缩算法压缩后,在写会RAM中,以达到RAM释放的功能。zram是一个块驱动,源码在kernel根目录下的drivers/block/zram下,相关源码这里不做分析,这篇就简单做一下压缩算法性能的对比。这次主要对比的算法是lzo、lz4和自行移植的zstd,不过目前自行移植的zstd算法还是有问题,尝试了各种方法都没办法fixed,所以就放出来当做zram算法移植指导嘛。后面有时间再讲这个bug fix掉吧。

前言

  要做对比,当然需要比较方法。这里用我司的手机作为测试工具了,手机的CPU为MSM8953,2G RAM,16G ROM。测试压缩读写速度的工具在AOSP的源码就有提供了,源码路径为system/extram/zram-perf,source lunch后通过mmm即可编译得到zram-perf工具。如果要比较算法压缩率的话,可以通过在手机上打开大型程序,然后查看/sys/block/zram0/mm_stat,进行比较。

  读写速度的获取,通过连续执行zram-perf三次,获取平均值。压缩率通过开启一个大型程序后连续读取两次mm_stat的数值,然后将程序放到后台后再开启另一大型程序,再次读入mm_stat的数值。mm_stat的各个数值代表如下:

orig_data_size
compr_data_size
mem_used_total
mem_limit
mem_used_max
zero_pages
num_migrated

第一个数值为原始数据大小,第二个数值我压缩后的数值大小,压缩率可以通过第一个数值除以第二个数值即可。

  如果这种测试手段存在问题的话,还请各位指出。

lzo

获取到的数据如下:

启动第一个大型程序后cat的数值:
412217344 156366519 169369600        0 233803776     7063        0
414195712 157096334 169889792        0 233803776     7071        0

启动第二个大型程序后cat的数值:
509906944 201929513 219283456        0 292343808     7523        0
515739648 204356988 221143040        0 292343808     7595        0

开机后放置一段时间后cat的数值:
640942080 274121046 287473664        0 287817728     7047        0

执行三次zram-perf获取的数据:
read: 134.039MB/s
write: 78.9562MB/s

read: 140.399MB/s
write: 84.6824MB/s

read: 140.615MB/s
write: 84.5762MB/s

lz4

获取到的数据如下:

启动第一个大型程序后cat的数值:
535990272 236916864 254332928        0 284073984     7496        0
538959872 239291561 256839680        0 284073984     7505        0

启动第二个大型程序后cat的数值:
467673088 187495555 211316736        0 339877888     7435        0
469475328 188378044 211808256        0 339877888     7435        0

开机后放置一段时间后cat的数值:
629051392 299621606 312655872        0 319049728     7012        0

执行三次zram-perf获取的数据:
read: 132.627MB/s
write: 87.2539MB/s

read: 137.859MB/s
write: 86.3278MB/s

read: 134.418MB/s
write: 86.6448MB/s

lzo vs lz4

  按照上面的数值来看,貌似两种压缩算法的读写速率相差不大,但是压缩率貌似是lzo更胜一筹。这里存疑,毕竟网上多有说lz4的性能更好,一方面可能是内核内置的lz4算法版本较低或者压缩级别较低,其次可能是因为测试手段有问题。

移植zstd

  zstd是Facebook开发的新一代压缩算法,在不牺牲压缩读写速度的前提下,拥有更高的压缩率。而且源码是开源的,所以可以将其移植到Linux内核上。只是博主现在移植后的zstd并不能很好的工作,所以这里贴出来和大家讨论讨论,另外看有没有大神指点下问题出在哪里了。

  要移植zstd首先是获取zstd的源码。zstd的源码,Facebook已经将其开源到GitHub上了,大家可以通过git clone https://github.com/facebook/zstd.git来下载zstd的源码。然后在zstd源码路径contrib/linux-kernel下有6个patch文件,我们将0001、0002和0005这三个patch文件复制到内核源码的根路径下,然后通过下面三条指令将patch打上:

1. patch -p1 < 0001-lib-Add-xxhash-module.patch
2. patch -p1 < 0002-lib-Add-zstd-modules.patch
3. patch -p1 < 0005-crypto-Add-zstd-support.patch

  打上上面三个patch后,还要做如下修改:

1.对crypto/zstd.c做如下修改:

diff --git a/crypto/zstd.c b/crypto/zstd.c
index 9a76b3e..c1bc62d 100644
--- a/crypto/zstd.c
+++ b/crypto/zstd.c
@@ -20,7 +20,6 @@
 #include <linux/net.h>
 #include <linux/vmalloc.h>
 #include <linux/zstd.h>
-#include <crypto/internal/scompress.h>
 
 
 #define ZSTD_DEF_LEVEL 3
@@ -111,24 +110,6 @@ static int __zstd_init(void *ctx)
    return ret;
 }
 
-static void *zstd_alloc_ctx(struct crypto_scomp *tfm)
-{
-   int ret;
-   struct zstd_ctx *ctx;
-
-   ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
-   if (!ctx)
-       return ERR_PTR(-ENOMEM);
-
-   ret = __zstd_init(ctx);
-   if (ret) {
-       kfree(ctx);
-       return ERR_PTR(ret);
-   }
-
-   return ctx;
-}
-
 static int zstd_init(struct crypto_tfm *tfm)
 {
    struct zstd_ctx *ctx = crypto_tfm_ctx(tfm);
@@ -142,12 +123,6 @@ static void __zstd_exit(void *ctx)
    zstd_decomp_exit(ctx);
 }
 
-static void zstd_free_ctx(struct crypto_scomp *tfm, void *ctx)
-{
-   __zstd_exit(ctx);
-   kzfree(ctx);
-}
-
 static void zstd_exit(struct crypto_tfm *tfm)
 {
    struct zstd_ctx *ctx = crypto_tfm_ctx(tfm);
@@ -177,13 +152,6 @@ static int zstd_compress(struct crypto_tfm *tfm, const u8 *src,
    return __zstd_compress(src, slen, dst, dlen, ctx);
 }
 
-static int zstd_scompress(struct crypto_scomp *tfm, const u8 *src,
-             unsigned int slen, u8 *dst, unsigned int *dlen,
-             void *ctx)
-{
-   return __zstd_compress(src, slen, dst, dlen, ctx);
-}
-
 static int __zstd_decompress(const u8 *src, unsigned int slen,
                 u8 *dst, unsigned int *dlen, void *ctx)
 {
@@ -205,13 +173,6 @@ static int zstd_decompress(struct crypto_tfm *tfm, const u8 *src,
    return __zstd_decompress(src, slen, dst, dlen, ctx);
 }
 
-static int zstd_sdecompress(struct crypto_scomp *tfm, const u8 *src,
-               unsigned int slen, u8 *dst, unsigned int *dlen,
-               void *ctx)
-{
-   return __zstd_decompress(src, slen, dst, dlen, ctx);
-}
-
 static struct crypto_alg alg = {
    .cra_name       = "zstd",
    .cra_flags      = CRYPTO_ALG_TYPE_COMPRESS,
@@ -224,18 +185,6 @@ static struct crypto_alg alg = {
    .coa_decompress     = zstd_decompress } }
 };
 
-static struct scomp_alg scomp = {
-   .alloc_ctx      = zstd_alloc_ctx,
-   .free_ctx       = zstd_free_ctx,
-   .compress       = zstd_scompress,
-   .decompress     = zstd_sdecompress,
-   .base           = {
-       .cra_name   = "zstd",
-       .cra_driver_name = "zstd-scomp",
-       .cra_module  = THIS_MODULE,
-   }
-};
-
 static int __init zstd_mod_init(void)
 {
@@ -243,18 +192,11 @@ static int __init zstd_mod_init(void)
-       int ret;
-
-       ret = crypto_register_alg(&alg);
-       if (ret)
-               return ret;
-
-   ret = crypto_register_scomp(&scomp);
-   if (ret)
-       crypto_unregister_alg(&alg);
-
-   return ret;
+   return crypto_register_alg(&alg);
 }
 
 static void __exit zstd_mod_fini(void)
 {
    crypto_unregister_alg(&alg);
-   crypto_unregister_scomp(&scomp);
 }
 
 module_init(zstd_mod_init);

2.其次在crypto的Makefile中查看是否有如下一行,如果没有就添加上

obj-$(CONFIG_CRYPTO_ZSTD) += zstd.o

3.接着需要到driver/block/zram目录下给zram添加zstd算法了,需要添加的代码可以模仿lz4进行添加,将下面的一个文件保存到driver/block/zram目录下,命名为zcomp_zstd.c

/*
 * Copyright (C) 2014 Sergey Senozhatsky.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/zstd.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>

#include "zcomp_zstd.h"

struct zstd_ctx {
    ZSTD_CCtx *cctx;
    ZSTD_DCtx *dctx;
    void *cwksp;
    void *dwksp;
};

struct zstd_ctx *ctx = NULL;

#define ZSTD_DEF_LEVEL 3

static ZSTD_parameters zstd_params(void)
{
    return ZSTD_getParams(ZSTD_DEF_LEVEL, 0, 0);
}

static int zstd_comp_init(struct zstd_ctx *ctx)
{
    int ret = 0;
    const ZSTD_parameters params = zstd_params();
    const size_t wksp_size = ZSTD_CCtxWorkspaceBound(params.cParams);

    ctx->cwksp = vzalloc(wksp_size);
    if (!ctx->cwksp) {
        ret = -ENOMEM;
        goto out;
    }

    ctx->cctx = ZSTD_initCCtx(ctx->cwksp, wksp_size);
    if (!ctx->cctx) {
        ret = -EINVAL;
        goto out_free;
    }
out:
    return ret;
out_free:
    vfree(ctx->cwksp);
    goto out;
}

static int zstd_decomp_init(struct zstd_ctx *ctx)
{
    int ret = 0;
    const size_t wksp_size = ZSTD_DCtxWorkspaceBound();

    ctx->dwksp = vzalloc(wksp_size);
    if (!ctx->dwksp) {
        ret = -ENOMEM;
        goto out;
    }

    ctx->dctx = ZSTD_initDCtx(ctx->dwksp, wksp_size);
    if (!ctx->dctx) {
        ret = -EINVAL;
        goto out_free;
    }
out:
    return ret;
out_free:
    vfree(ctx->dwksp);
    goto out;
}

static void zstd_comp_exit(struct zstd_ctx *ctx)
{
    vfree(ctx->cwksp);
    ctx->cwksp = NULL;
    ctx->cctx = NULL;
}

static void zstd_decomp_exit(struct zstd_ctx *ctx)
{
    vfree(ctx->dwksp);
    ctx->dwksp = NULL;
    ctx->dctx = NULL;
}

static int __zstd_init(void *ctx)
{
    int ret;

    ret = zstd_comp_init(ctx);
    if (ret)
        return ret;
    ret = zstd_decomp_init(ctx);
    if (ret)
        zstd_comp_exit(ctx);
    return ret;
}

static int __zstd_compress(const u8 *src, unsigned int slen,
               u8 *dst, unsigned int *dlen, void *ctx)
{
    size_t out_len;
    struct zstd_ctx *zctx = ctx;
    const ZSTD_parameters params = zstd_params();

    out_len = ZSTD_compressCCtx(zctx->cctx, dst, *dlen, src, slen, params);
    if (ZSTD_isError(out_len)) {
        ZSTD_ErrorCode errcode = ZSTD_getErrorCode(out_len);
        printk("zram: compress error code2 = %d", errcode);
        return -EINVAL;
    }
    *dlen = out_len;

    printk("zram: slen = %u, out_len = %zu\n", slen, out_len);

    return 0;
}

static int __zstd_decompress(const u8 *src, unsigned int slen,
                 u8 *dst, unsigned int *dlen, void *ctx)
{
    size_t out_len;
    struct zstd_ctx *zctx = ctx;

    out_len = ZSTD_decompressDCtx(zctx->dctx, dst, *dlen, src, slen);
    if (ZSTD_isError(out_len)) {
        ZSTD_ErrorCode errcode = ZSTD_getErrorCode(out_len);
        printk("zram: decompress error code2 = %d", errcode);
        return -EINVAL;
    }
    *dlen = out_len;

    printk("zram: slen = %u, out_len = %zu\n", slen, out_len);

    return 0;
}

static void *zcomp_zstd_create(void)
{
    void *ret;

    /*
     * This function can be called in swapout/fs write path
     * so we can't use GFP_FS|IO. And it assumes we already
     * have at least one stream in zram initialization so we
     * don't do best effort to allocate more stream in here.
     * A default stream will work well without further multiple
     * streams. That's why we use NORETRY | NOWARN.
     */
    ret = kzalloc(sizeof(*ctx), GFP_NOIO | __GFP_NORETRY |
                    __GFP_NOWARN);
    if (!ret)
        ret = __vmalloc(sizeof(*ctx),
                GFP_NOIO | __GFP_NORETRY | __GFP_NOWARN |
                __GFP_ZERO | __GFP_HIGHMEM,
                PAGE_KERNEL);

    if(ret)
        __zstd_init(ret);

    ctx = ret;
    return ret;
}

static void zcomp_zstd_destroy(void *private)
{
    zstd_comp_exit(private);
    zstd_decomp_exit(private);
    kvfree(private);
    ctx = NULL;
}

static int zcomp_zstd_compress(const unsigned char *src, unsigned char *dst,
        size_t *dst_len, void *private)
{
    /* return  : Success if return 0 */
    // lz4_compress(src, PAGE_SIZE, dst, dst_len, private);

    return __zstd_compress(src, PAGE_SIZE, dst, (unsigned int *)dst_len, ctx);
}

static int zcomp_zstd_decompress(const unsigned char *src, size_t src_len,
        unsigned char *dst)
{
    size_t dst_len = PAGE_SIZE;

    /* return  : Success if return 0 */
    return __zstd_decompress(src, src_len, dst, (unsigned int *)&dst_len, ctx);
}

struct zcomp_backend zcomp_zstd = {
    .compress = zcomp_zstd_compress,
    .decompress = zcomp_zstd_decompress,
    .create = zcomp_zstd_create,
    .destroy = zcomp_zstd_destroy,
    .name = "zstd",
};

将下面一个文件保存为zcomp_zstd.h

/*
 * Copyright (C) 2014 Sergey Senozhatsky.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#ifndef _ZCOMP_ZSTD_H_
#define _ZCOMP_ZSTD_H_

#include "zcomp.h"

extern struct zcomp_backend zcomp_zstd;

#endif /* _ZCOMP_ZSTD_H_ */

然后还要对driver/block/zram目录下做如下修改:

diff --git a/drivers/block/zram/Kconfig b/drivers/block/zram/Kconfig
index 6489c0f..9cdbcec 100755
--- a/drivers/block/zram/Kconfig
+++ b/drivers/block/zram/Kconfig
@@ -15,6 +15,16 @@ config ZRAM
 
          See zram.txt for more information.
 
+config ZRAM_ZSTD_COMPRESS
+       bool "Enable ZSTD algorithm support"
+       depends on ZRAM
+       select ZSTD_COMPRESS
+       select ZSTD_DECOMPRESS
+       default n
+       help
+         This option enables ZSTD compression algorithm support. Compression
+         algorithm can be changed using `comp_algorithm' device attribute.
+
 config ZRAM_LZ4_COMPRESS
        bool "Enable LZ4 algorithm support"
        depends on ZRAM
diff --git a/drivers/block/zram/Makefile b/drivers/block/zram/Makefile
index be0763f..d10a35a 100755
--- a/drivers/block/zram/Makefile
+++ b/drivers/block/zram/Makefile
@@ -1,5 +1,6 @@
 zram-y :=      zcomp_lzo.o zcomp.o zram_drv.o
 
 zram-$(CONFIG_ZRAM_LZ4_COMPRESS) += zcomp_lz4.o
+zram-$(CONFIG_ZRAM_ZSTD_COMPRESS) += zcomp_zstd.o
 
 obj-$(CONFIG_ZRAM)     +=      zram.o
diff --git a/drivers/block/zram/zcomp.c b/drivers/block/zram/zcomp.c
index 6fbb10c..5fcb3b5 100755
--- a/drivers/block/zram/zcomp.c
+++ b/drivers/block/zram/zcomp.c
@@ -19,6 +19,9 @@
 #ifdef CONFIG_ZRAM_LZ4_COMPRESS
 #include "zcomp_lz4.h"
 #endif
+#ifdef CONFIG_ZRAM_ZSTD_COMPRESS
+#include "zcomp_zstd.h"
+#endif
 
 /*
  * single zcomp_strm backend
@@ -48,6 +51,9 @@ static struct zcomp_backend *backends[] = {
 #ifdef CONFIG_ZRAM_LZ4_COMPRESS
        &zcomp_lz4,
 #endif
+#ifdef CONFIG_ZRAM_ZSTD_COMPRESS
+       &zcomp_zstd,
+#endif
        NULL
 };

4.最后在对应的kernel defconfig文件中添加如下内容,然后编译boot.img即可

CONFIG_CRYPTO_ZSTD=y
CONFIG_ZRAM_ZSTD_COMPRESS=y
CONFIG_ZSTD_COMPRESS=y
CONFIG_ZSTD_DECOMPRESS=y
CONFIG_XXHASH=y

5.启动手机后,可以通过cat /sys/block/zram0/comp_algorithm可以看到有zstd的出现,然后选中zstd即可。

zstd出现的问题

  目前移植的zstd还是有点问题的,但是博主自己分析了一周也不知道问题点出现在哪里,表示很郁闷。出现问题的现象就是:启用zstd后,打开应用使用一段时间后,cat /sys/block/zram0/mm_stat后会发现第一个数值和第二个数值相差无几,这表明数据没有被压缩到。所以就觉得异常的奇怪,因为代码也是参考zstd中的代码进行编写的,也看不出哪里有问题,奔溃。希望哪位大神知道原因可以给我留言。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: