帮酷LOGO
  • 显示原文与译文双语对照的内容
Fast Base64 stream encoder/decoder in C99, with SIMD acceleration

  • 源代码名称:base64
  • 源代码网址:http://www.github.com/aklomp/base64
  • base64源代码文档
  • base64源代码下载
  • Git URL:
    git://www.github.com/aklomp/base64.git
  • Git Clone代码到本地:
    git clone http://www.github.com/aklomp/base64
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/aklomp/base64
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
  • 快速Base64流编码器/解码器

    This ( AVX2,NEON,aarch64/NEON,SSSE3,SSE4.1, SSE4.2, AVX ) 中的base64流编码/解码库的实现,带有SIMD和 OpenMP的加速。 它还包含用于编码/解码简单长度分隔字符串的包装函数。 这个库的目标是:

    • 快速;
    • 易于使用;
    • 典雅。

    在x86上,库执行运行时特征检测。 库第一次调用时,将为机器确定适当的编码/解码例程。 然后在程序的生命周期内记住它们。 if,SSSE3,SSE4.1,,library,library,library,library,library,library,library,library,library,library,library,library,library,library,library 。

    NEON支持在编译时硬编码到打开或者关闭,因为在ARM上无法使用可移植运行时特征检测。

    即使你的处理器不支持SIMD指令,这是一个非常快的库。 回退例程可以以在一轮内处理 32或者 64位输入,这取决于处理器宽度的单词。 在一些 64位 机器上,64位 例程甚至比SSSE3程序还要好。

    对作者的知识,在原版本的时候,这是提供vxml加速的惟一Base64库。 作者写了一篇文章,解释了一种可能的SIMD方法,用于编码/解码 Base64. 本文可以帮助找出代码正在做什么,以及为什么。

    显著的功能:

    • 在x86和ARM系统上使用SIMD向量处理非常快速;
    • 可以使用 OpenMP来实现更并行的加速;
    • 在其他 32或者 64位 平台上通过优化的例程非常快;
    • 读取/写入流数据块;
    • 不动态分配内存;
    • 使用pedantic选项编译的有效;
    • 进入和 threadsafe ;
    • 单元测试;
    • 使用达夫装置。

    确认

    最初的AVX2.NEON和 aarch64/NEON编解码器慷慨地由 Inkymail 提供,他们在它的fork 中也实现了一些额外的功能。 他们的工作正在缓慢地进入这个项目。

    他自己的代码在这里是

    OpenMP实现是由 Exalon Delft的渡轮 Toth ( @htot ) 添加的。

    建筑

    lib 目录包含实际库的代码。 在顶级目录中键入 make 将生成 lib/libbase64.obin/base64 。 第一个是一个单独的包含对象文件,你可以链接到自己的项目中。 第二种是独立的测试二进制代码,它与 base64 系统实用程序类似。

    使用这里库所需的匹配头文件在 include/libbase64.h 中。

    要只编译不带SIMD编解码器的"普通"库,请键入:

    make lib/libbase64.o

    可以通过指定 AVX2_CFLAGSNEON32_CFLAGSNEON64_CFLAGSSSSE3_CFLAGSSSE41_CFLAGSSSE42_CFLAGS 和/或者 AVX_CFLAGS 环境变量 来包含可选的SIMD编解码器。 在x86上的典型生成调用如下所示:

    AVX2_CFLAGS=-mavx2 SSSE3_CFLAGS=-mssse3 SSE41_CFLAGS=-msse4.1 SSE42_CFLAGS=-msse4.2 AVX_CFLAGS=-mavx make lib/libbase64.o

    AVX2

    要构建并包含AVX2编解码器,将 AVX2_CFLAGS 环境变量设置为一个值,在编译器中打开AVX2支持,通常是 -mavx2 。 例如:

    AVX2_CFLAGS=-mavx2 make

    只有在运行时特征检测显示目标机器支持AVX2时,才会使用编解码器。

    SSSE3

    要构建并包含SSSE3编解码器,将 SSSE3_CFLAGS 环境变量设置为一个值,在编译器中打开SSSE3支持,通常是 -mssse3 。 例如:

    SSSE3_CFLAGS=-mssse3 make

    只有在运行时特征检测显示目标机器支持SSSE3时,才会使用编解码器。

    霓虹灯

    这里库包含两个NEON编解码器: 一个用于常规 32位 手臂和一个 64位 AArch64的,它有两倍的vxml寄存器,并且可以以完全的64-byte 表查找。 这些编码解码器在 48-byte 块中编码,在大量的64-byte 块中解码,因此必须用 uint32/64编解码器进行扩充,以便在较小的输入范围 !

    使用 llvm/clang编译霓虹灯编解码器。 compiling编译的代码生成至少包含 Bug,并且生成的汇编代码质量低。 NEON内部是GCC已知的弱区域。 Clang做得更好。

    不幸的是,在运行时,NEON支持无法从用户的( 。mrc 指令具有特权) 中移植,因此使用NEON编码器的缺省值是在编译时确定的。 但是你可以做你自己的运行时检测。 你可以以包含霓虹编码器并使它的成为默认值,然后执行运行时检查,如果没有 BASE64_FORCE_PLAIN

    以下是你的选项:

    • 不包括霓虹灯支持;
    • 构建NEON支持并使它的成为默认,但是构建所有其他代码没有NEON标志,这样你就可以在运行时用 BASE64_FORCE_PLAIN 覆盖缺省
    • 用霓虹灯支撑一切,让它成为默认;
    • 使用NEON支持构建一切,但不要将它的作为默认的( 那是没有意义的) 。

    对于选项 1,根本不指定任何特定于neon的编译器标志,如下所示:

    CC=clang CFLAGS="-march=armv6" make

    对于选项 2,保持 CFLAGS plain,但将 NEON32_CFLAGS 环境变量设置为一个值,该值将生成NEON支持。 例如,下面的行将构建,级别的所有代码,除了霓虹编解码器以外,它构建在tmodel中。 它也将使霓虹灯编解码器成为默认。 对于ARMv6平台,在运行时使用 BASE64_FORCE_PLAIN 标志重写默认值。 没有 touched/neon代码。

    CC=clang CFLAGS="-march=armv6" NEON32_CFLAGS="-march=armv7 -mfpu=neon" make

    对于选项 3,将所有内容放入 CFLAGS 并使用存根,但 非空 。NEON32_CFLAGS 。 本示例适用于 树莓派 2B V1.1,,该支持 NEON:

    CC=clang CFLAGS="-march=armv7 -mtune=cortex-a7" NEON32_CFLAGS="-mfpu=neon" make

    要构建和包含NEON64编解码器,通常使用 CFLAGS 定义平台并将 NEON64_CFLAGS 设置为非空存根。 ( AArch64目标具有强制NEON64支持。) 示例:

    CC=clang CFLAGS="--target=aarch64-linux-gnu -march=armv8-a" NEON64_CFLAGS="" make

    OpenMP

    要启用 GCC,你需要使用 -fopenmp 构建。 这可以通过将 OPENMP 环境变量设置为 1 来实现。

    例如:

    OPENMP=1 make

    这将让编译器定义 _OPENMP,它又将把OpenMP优化的lib_openmp.c 包含到 lib.c 中。

    默认情况下,并行线程的数目等于处理器的内核数。 在quad内核上,将检测到hyperthreading内核,但超线程不会提高性能。

    要获取有关OpenMP的详细信息,请使用 OMP_DISPLAY_ENV=VERBOSE 启动程序,例如

    OMP_DISPLAY_ENV=VERBOSE test/benchmark

    要限制线程数量,可以使用 OMP_THREAD_LIMIT=n 启动程序,例如

    OMP_THREAD_LIMIT=2 test/benchmark

    启用了使用 OpenMP,SSSE3和AVX2运行基准的示例:

    make clean && OPENMP=1 SSSE3_CFLAGS=-mssse3 AVX2_CFLAGS=-mavx2 make && OPENMP=1 make -C test

    API参考

    字符串表示为指针和长度;它们不是零终止的。 这是一个有意识的设计决定。 由于输出可能包含合法零字节,所以在解码步骤中依赖零终止是没有意义的。 在编码步骤中,返回长度将在输出中节省调用 strlen()的开销。 如果你坚持尾部零,你可以在给定的偏移处轻松地添加它。

    标志

    一些API调用采用 flags 参数。 参数可以用于强制使用特定编解码器,即使该编解码器在当前构建中不是运算。 主要是为了测试目的,这在ARM上也有用,它的中唯一的方法来实现运行时霓虹检测是可以用的。 可以使用以下常量:

    • BASE64_FORCE_AVX2
    • BASE64_FORCE_NEON32
    • BASE64_FORCE_NEON64
    • BASE64_FORCE_PLAIN
    • BASE64_FORCE_SSSE3
    • BASE64_FORCE_SSE41
    • BASE64_FORCE_SSE42
    • BASE64_FORCE_AVX

    对于默认行为,将 flags 设置为 0,这是x86上运行时特征检测,ARM上的编译时间固定编解码器和其他平台的普通编解码器。

    编码

    base64_encode
    void base64_encode
     ( constchar *src
    , size_t srclen
    , char *out
    , size_t *outlen
    , int flags
     ) ;

    对给定长度的普通字符串进行编码的包装函数。 输出被写入 out,但没有尾随零。 输出长度以字节为单位写入 outlenout 中的缓冲区已经由调用方分配,且至少为 4/3的输入大小。

    base64_stream_encode_init
    void base64_stream_encode_init
     ( struct base64_state *state
    , int flags
     ) ;

    调用 base64_stream_encode() 以初始化状态之前调用这里方法。

    base64_stream_encode
    void base64_stream_encode
     ( struct base64_state *state
    , constchar *src
    , size_t srclen
    , char *out
    , size_t *outlen
     ) ;

    src 中将给定长度的数据块编码到 out 中的缓冲区中。 调用方负责分配足够大的缓冲区;它必须至少为 4/3的大小。 将写入的新字节数放入 outlen ( 函数启动时设置为零) 中。 不终止或者终止输出。

    base64_stream_encode_final
    void base64_stream_encode_final
     ( struct base64_state *state
    , char *out
    , size_t *outlen
     ) ;

    完成以前对 base64_stream_encode() 调用的输出。 如果合适,添加所需的end-of-stream标记。 outlen 被修改并且将包含在 out ( 这通常是零) 中写入的新字节数。

    解码

    base64_decode
    int base64_decode
     ( constchar *src
    , size_t srclen
    , char *out
    , size_t *outlen
    , int flags
     ) ;

    用于解码给定长度的普通字符串的包装函数。 输出被写入 out,但没有尾随零。 输出长度以字节为单位写入 outlenout 中的缓冲区已经由调用方分配,且至少为 3/4的输入大小。 成功返回 1,如果由于无效输入而发生解码错误,则返回 0 。 如果当前版本中未包含所选编解码器,则返回 -1

    base64_stream_decode_init
    void base64_stream_decode_init
     ( struct base64_state *state
    , int flags
     ) ;

    调用 base64_stream_decode() 以初始化状态之前调用这里方法。

    base64_stream_decode
    int base64_stream_decode
     ( struct base64_state *state
    , constchar *src
    , size_t srclen
    , char *out
    , size_t *outlen
     ) ;

    src 中将给定长度的数据块解码为 out 处的缓冲区。 调用方负责分配足够大的缓冲区;它必须至少为 3/4的大小。 将写入的新字节数放入 outlen ( 函数启动时设置为零) 中。 不终止输出。 如果所有内容都是好的,则返回 1,如果发现解码错误,则返回 0,如无效字符。 如果当前版本中未包含选定的编解码器,则返回 -1. 测试工具用于检查编解码器是否可以用于测试。

    示例

    将 static 字符串编码为base64并将输出输出到stdout的简单示例:

    #include<stdio.h>/* fwrite */#include"libbase64.h"intmain ()
    {
     char src[] = "hello world";
     char out[20];
     size_t srclen = sizeof(src) - 1;
     size_t outlen;
     base64_encode(src, srclen, out, &outlen, 0);
     fwrite(out, outlen, 1, stdout);
     return0;
    }

    标准输出的流编码标准输入的一个简单示例( 无错误检查等):

    #include<stdio.h>#include"libbase64.h"intmain ()
    {
     size_t nread, nout;
     char buf[12000], out[16000];
     struct base64_state state;
     // Initialize stream encoder:base64_stream_encode_init(&state, 0);
     // Read contents of stdin into buffer:while ((nread = fread(buf, 1, sizeof(buf), stdin))> 0) {
     // Encode buffer:base64_stream_encode(&state, buf, nread, out, &nout);
     // If there's output, print it to stdout:if (nout) {
     fwrite(out, nout, 1, stdout);
     }
     // If an error occurred, exit the loop:if (feof(stdin)) {
     break;
     }
     }
     // Finalize encoding:base64_stream_encode_final(&state, out, &nout);
     // If the finalizing resulted in extra output bytes, print them:if (nout) {
     fwrite(out, nout, 1, stdout);
     }
     return0;
    }

    有关 base64 实用工具的简单实现,请参阅 bin/base64.c 。 文件或者标准输入通过编码器/解码器输入,输出被写入标准输出。

    测试

    有关小型测试套件,请参见 tests/ 。 测试自动化与 Travis CI:

    Build Status

    基准测试

    基准测试可以使用内置基准程序运行,如下所示:

    make -C test benchmark <buildflags>&& test/benchmark

    它将为所有编译的编解码器运行一个编码和解码基准。

    下面的表格在随机机器上包含一些结果。 以 10MB/秒为单位,以字节表示的所有数字,舍入到最接近的整数。

    需要 *: 更新

    x86处理器

    处理器普通 enc Plain SSSE3 SSSE3
    i7-4771 @ 3.5 GHz8331111*3333*4444*TBDTBD4999*6666*
    i7-4770 @ 3.4 GHz DDR16001790303848994043*47965709*46816386*
    i7-4770 @ 3.4 GHz DDR1600 OPENMP 1线程1784304149454035*47765719*46616294*
    i7-4770 @ 3.4 GHz DDR1600 OPENMP 2线程3401572954897444*50038624*51058558*
    i7-4770 @ 3.4 GHz DDR1600 OPENMP 4线程4884709949177057*47997143*49027219*
    i7-4770 @ 3.4 GHz DDR1600 OPENMP 8线程5212884952849099*52899220*48499200*
    i7-4870HQ @ 2.5 GHz147130666721696270158267832811576
    i5-4590S @ 3.0 GHz17213004415557244190599941496212
    Xeon X5570 @ 2.93 GHz10971048*2077*2215*----
    Pentium4 @ 3.4 GHz528448*------
    Atom N270112208447417----
    AMD E-450370332*405*366*----
    英特尔 @ 500 MHz7992*152*172*----
    英特尔 @ 500 MHz OPENMP 2线程158184*300*343*----
    英特尔 @ 500 MHz ( x86-64 )97146197207*----
    英特尔 @ 500 MHz ( x86-64 ) 2线程193288389410*----

    ARM处理器

    处理器普通 enc NEON32 NEON32 NEON64 NEON64 dec
    树莓派 B+ V1.24640*----
    树莓派 2 B V1.110488*188116*--
    Apple iPhone传感器105689529432618--
    苹果iPhone传感器10611239--40983983

    PowerPC处理器

    普通处理器普通 dec
    PowerPC E6500 @ 1.8 GHz270265*

    i7-4770 @ 3.4 DDR1600,带有varrying缓冲区大小的基准: Benchmarks

    Note高速缓存大小和cache的缓存大小 is Plain faster faster AVX encoding比普通的AVX编码/解码 leading,因为线程创建开销小于 100 kB,decoding的吞吐量。 为了防止发生这种情况,lib_openmp.c 定义了 OMP_THRESHOLD 20000,需要至少 20000字节的缓冲区来启用多线程处理。

    许可证

    这个存储库是 BSD 2-clause 许可协议下的许可证。 查看许可证文件。




    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语