如何加快crc32计算速度?

时间:2011-03-22 01:00:32

标签: c crc32

我正在尝试尽可能快地在linux上编写crc32实现,作为学习优化C的练习。我已经尽力了,但我无法在网上找到很多好资源。我甚至不确定我的缓冲区大小是否合理;它是通过反复实验选择的。

#include <stdio.h>
#define BUFFSIZE 1048567

const unsigned long int lookupbase = 0xEDB88320;
unsigned long int crctable[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
/* LONG LIST OF PRECALCULTED VALUES */
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D};

int main(int argc, char *argv[]){
    register unsigned long int x;
    int i;
    register unsigned char *c, *endbuff;
    unsigned char buff[BUFFSIZE];
    register FILE *thisfile=NULL;
    for (i = 1; i < argc; i++){
        thisfile = fopen(argv[i], "r");
        if (thisfile == NULL) {
            printf("Unable to open ");
        } else {
            x = 0xFFFFFFFF;
            c = &(buff[0]);
            endbuff = &(buff[fread(buff, (sizeof (unsigned char)), BUFFSIZE, thisfile)]);
            while (c != endbuff){
                while (c != endbuff){
                    x=(x>>8) ^ crctable[(x&0xFF)^*c];
                    c++;
                }
                c = &(buff[0]);
                endbuff = &(buff[fread(buff, (sizeof (unsigned char)), BUFFSIZE, thisfile)]);
            }
            fclose(thisfile);
            x = x ^ 0xFFFFFFFF;
            printf("%0.8X ", x);
        }
        printf("%s\n", argv[i]);
    }
    return 0;
}

提前感谢我可以阅读的任何建议或资源。

3 个答案:

答案 0 :(得分:3)

在Linux上?忘记register关键字,这只是编译器的建议,根据我对gcc的经验,这是浪费空间。 gcc 更多比能够为自己搞清楚。

我只是确保您使用疯狂的优化级别-O3进行编译,并检查一下。我已经看到gcc生成了该级别的代码,这花了我几个小时的时间来理解,所以这是偷偷摸摸的。

并且,在缓冲区大小上,尽可能大。即使使用缓冲,调用fread的成本仍然是成本,因此您做得越少越好。如果将缓冲区大小从1K增加到1M,您会看到一个巨大的改进,如果将其从1M增加到2M,则会有很大的改进,但即使是少量增加的性能也会增加。并且,2M不是您可以使用的上限,如果可能的话,我会将其设置为一个或多个千兆字节

然后,您可能希望将其置于文件级别(而不是main内)。在某些时候,堆栈将无法容纳它。

与大多数优化一样,您通常可以将空间换成时间。请记住,对于小文件(小于1M),您将看不到任何改进,因为无论您使用多大的缓冲区,仍然只有一个读取。如果加载过程需要花费更多时间来设置内存,您甚至可能会发现轻微的减速。

但是,因为这只适用于小文件(性能不是问题),它应该不重要。 这个问题的大型文件应该会有所改进。

而且我知道我不需要告诉这个(因为你表明你正在这样做),但无论如何我会为那些不知道的人提及:措施,不要猜!地面上堆满了那些通过猜测优化的人的尸体: - )

答案 1 :(得分:2)

您无法加速CRC计算的实际算术,因此您可以看到的区域是(a)读取文件和(b)循环的开销。

你正在使用一个非常大的缓冲区大小,这很好(但为什么它是一个奇数?)。使用read(2)系统调用(假设您使用类似unix的系统)而不是fread(3)标准库函数可以节省一个copy操作(将数据从fread的内部缓冲区复制到您的bufffer)。

对于循环开销,请查看loop unrolling


您的代码也有一些您可能想要消除的冗余。

  • sizeof (unsigned char)为1(根据C中的定义);无需明确计算

  • c = &(buff[0])完全等同于c = buff

这些变化都不会改善代码的性能(假设编译器很好),但它们会更清晰,更符合通常的C风格。

答案 2 :(得分:2)

你已经要求将三个值存储在寄存器中,但是标准x86只有四个通用寄存器:这对于最后一个寄存器来说是一个非常大的负担,这是我期望{{1实际上只会阻止您使用register来查找变量的地址。我认为现在任何现代编译器都不会将它用作提示。随意删除所有三种用途并重新定时应用程序。

由于您自己正在阅读大量文件,因此您也可以直接使用&fooopen(2),并删除幕后的所有标准IO处理。另一种常见的方法是将read(2)open(2)文件放入内存中:让操作系统将页面作为页面进行页面化是必需的。这可能允许您在进行计算时从磁盘中乐观地读取未来页面:这是一种常见的访问模式,也是操作系统设计者试图优化的模式之一。 (一次映射整个文件的简单机制确实对您可以处理的文件大小设置了上限,在32位平台上可能大约为2.5千兆字节,在64位平台上绝对是巨大的以块为单位映射文件将允许您处理任意大小的文件,即使在32位平台上也是如此,但代价是您现在已经读取的循环,但是用于映射。)

正如David Gelhar指出的那样,你正在使用一个奇数长度的缓冲区 - 这可能会使将文件读入内存的代码路径变得复杂。如果你想坚持从文件读取到缓冲区,我建议使用mmap(2)(两页内存)的倍数,因为在最后一个循环之前它不会有特殊情况。

如果你真的想要摆脱最后一点速度,并且不介意大幅增加预计算表的大小,你可以用16位块来查看文件,而不仅仅是8-比特块。通常,沿着16位对齐访问存储器比沿着8位对齐更快,并且通过循环将迭代次数减少一半,这通常会提高速度。当然,缺点是内存压力增加(65k条目,每个8字节,而不是4个字节中的每个只有256个条目),而更大的表更不可能完全适合CPU缓存。

我想到的最后一个优化想法是8192进入2个,3个或4个进程(或使用线程),每个进程都可以计算部分的crc32 的文件,然后在所有进程完成后合并最终结果。 crc32的计算密集程度可能不足以实际受益于尝试使用SMP或多核计算机中的多个核心,并且弄清楚如何组合crc32的部分计算可能不可行 - 我自己没有调查过它:) - - 但它可以回报这些努力,并且学习如何编写多进程或多线程软件是值得的。无论如何。