【问题标题】:Why is MD5Sum so fast为什么 MD5Sum 这么快
【发布时间】:2013-09-09 18:00:34
【问题描述】:

我一直在研究 C/C++ 中的散列,并尝试在 Linux 中复制 md5sum 命令。分析源码后,似乎md5sum依赖于md5库的md5_stream。我已经将 md5.h 库中的 md5_stream 函数近似为下面的代码,它运行时间约为 13-14 秒。我尝试直接调用 md5_stream 函数并得到〜13-14秒。 md5sum 在 4 秒内运行。 GNU 人做了什么来提高代码的速度?

CoreUtils 源代码中提供了 md5.h/md5.c 代码。

#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>

#include <iostream>
#include <iomanip>
#include <fstream>
#include "md5.h"

#define BLOCKSIZE 32784

int main()
{
    FILE *fpinput, *fpoutput;

    if ((fpinput = fopen("/dev/sdb", "rb")) == 0) {
        throw std::runtime_error("input file doesn't exist");
    }

    struct md5_ctx ctx;
    size_t sum;

    char *buffer = (char*)malloc (BLOCKSIZE + 72);
    unsigned char *resblock = (unsigned char*)malloc (16);
    if (!buffer)
      return 1;

    md5_init_ctx (&ctx);
    size_t n;
    sum = 0;

    while (!ferror(fpinput) && !feof(fpinput)) {
        n = fread (buffer + sum, 1, BLOCKSIZE - sum, fpinput);
        if (n == 0){
            break;
        }
        sum += n;

        if (sum == BLOCKSIZE) {
            md5_process_block (buffer, BLOCKSIZE, &ctx);
            sum = 0;
        }
    }

    if (n == 0 && ferror (fpinput)) {
        free (buffer);
        return 1;
    }

    /* Process any remaining bytes.  */
    if (sum > 0){
      md5_process_bytes (buffer, sum, &ctx);
    }

    /* Construct result in desired memory.  */
    md5_finish_ctx (&ctx, resblock);
    free (buffer);

    for (int x = 0; x < 16; ++x){
        std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<uint16_t>(resblock[x]);
        std::cout << " ";
    }
    std::cout << std::endl;
    free(resblock);
    return 0;
}

编辑:Fedora 19 64 位中的默认 mkspec 问题。

【问题讨论】:

  • 当你构建你的代码时,你是在调试模式下构建的吗?您正在使用哪些优化级别?
  • 是gnuutils...源码有,可以自己看。
  • Tim:目前我正在 Qt 中构建。默认情况下,Qt 构建环境添加 -O2 构建标志。我在发布模式下编译和测试,所以调试库和钩子不应该干扰。 MarcB:如果我查看 md5sum 源代码,我显然知道它是 GNUUtils。我正在寻找有关他们如何或在何处优化构建的见解。
  • 您已经有了源代码,请查看 makefile 以确定使用了哪些编译器选项。
  • Fedora x64 和默认的 Qt 安装使用了错误的 mkspec 文件并且没有使用它应该使用的优化标志。 linux g++ (-O) 与 linux-64 g++(-O2/-O3)。我删除了通用的 linux mkspec,并建立了一个指向正确 linux-64 mkspec 的符号链接。一切都很好,并且优化得当。

标签: c++ performance md5 md5sum


【解决方案1】:

fread() 很方便,但如果您关心性能,请不要使用 fread()。 fread() 将从操作系统复制到 libc 缓冲区,然后复制到您的缓冲区。这种额外的复制会消耗 CPU 周期和缓存。

为了获得更好的性能,请使用open() 然后read() 以避免额外的副本。确保您的 read() 调用是块大小的倍数,但低于 CPU 缓存大小。

为了获得最佳性能,请使用mmap() 将磁盘直接映射到 RAM。

如果您尝试以下代码,它应该会更快。

//  compile  gcc  mmap_md5.c  -lgcrypt
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gcrypt.h>
#include <linux/fs.h> // ioctl

#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)


int main(int argc, char *argv[])
    {
        char *addr;
        int fd;
        struct stat sb;
        off_t offset, pa_offset;
        size_t length;
        ssize_t s;
        unsigned char digest[16];
        char digest_ascii[32+1] = {0,};
        int digest_length = gcry_md_get_algo_dlen (GCRY_MD_MD5);
        int i;

        if (argc < 3 || argc > 4) {
            fprintf(stderr, "%s file offset [length]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        fd = open(argv[1], O_RDONLY);
        if (fd == -1)
            handle_error("open");
        if (fstat(fd, &sb) == -1)           /* To obtain file size */
            handle_error("fstat");
        offset = atoi(argv[2]);
        pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);


        if (sb.st_mode | S_IFBLK ) {
            // block device. use ioctl to find length
            ioctl(fd, BLKGETSIZE64, &length);

        } else  {
            /* offset for mmap() must be page aligned */
            if (offset >= sb.st_size) {
                fprintf(stderr, "offset is past end of file size=%zd, offset=%d\n", sb.st_size, (int) offset);
                exit(EXIT_FAILURE);
            }
            if (argc == 4) {
                length = atoi(argv[3]);
                if (offset + length > sb.st_size)
                    length = sb.st_size - offset;
                /* Canaqt display bytes past end of file */
            } else {    /* No length arg ==> display to end of file */
                length = sb.st_size - offset;
            }
        }
        printf("length= %zd\n", length);
        addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                                MAP_PRIVATE, fd, pa_offset);
        if (addr == MAP_FAILED)
            handle_error("mmap");


        gcry_md_hash_buffer(GCRY_MD_MD5, digest, addr + offset - pa_offset, length);

        for (i=0; i < digest_length; i++) {
            sprintf(digest_ascii+(i*2), "%02x", digest[i]);
        }
        printf("hash=%s\n", digest_ascii);

        exit(EXIT_SUCCESS);
}

【讨论】:

  • 您实际测量过性能提升吗?在什么类型的文件系统上?对于顺序文件访问,我想知道 mmap 如何比缓冲读取执行得更好。我还期望顺序读取在绝大多数文件系统上比无缓冲读取执行得更好。
  • 原始发帖人正在检查块设备 /dev/sdb,因此文件系统类型无关紧要。文件系统位于块设备之上。我测试了我的与 md5sum 的性能,性能大致相同,我的示例比 md5sum 慢约 5%,但与原始海报相比不超过三倍。如果您想对 mmap 进行更多辩论。见stackoverflow.com/questions/45972/mmap-vs-reading-blocks
  • 遗憾的是,在这种情况下这是一个编译问题。感谢您的代码和见解。我一定会查看链接的文章。
【解决方案2】:

原来是 Qt mkspecs 中关于优化标志设置不正确的错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-14
    • 2018-03-22
    • 2021-12-19
    • 1970-01-01
    • 2014-02-05
    • 2016-05-03
    • 2014-08-12
    • 2016-12-14
    相关资源
    最近更新 更多