我为课堂写了这个汉明编码代码。为什么这么慢?

时间:2014-03-24 03:00:26

标签: c++ file operating-system hamming-code

我为我的OS类编写了这个:

#include <iostream>
#include <fstream>

//encodes a file using the (8,4) Hamming Code.
//usage : HammingEncode.out < inputFile > outputFile 
int main() {
    unsigned char const codebook[] = {0x00, 0x1E, 0x2D, 0x33, 0x4B, 0x55, 0x66, 0x78, 0x87, 0x99, 0xAA, 0xB4, 0xCC, 0xD2, 0xE1, 0xFF};
    unsigned char in, nextByte;
    unsigned char const leftMask = 0xF0, rightMask = 0x0F;

    in = std::cin.get();
    while (!std::cin.eof()) {
        nextByte = (in & leftMask) >> 4;
        std::cout << codebook[nextByte];
        nextByte = in & rightMask;
        std::cout << codebook[nextByte];
        in = std::cin.get();
    }
}

然后我决定在旧的Testamenet上测试它的速度(只是为了看看)。这是我们用Java教授的数据结构类的标准测试文件,我们可以对其进行排序,并且霍夫曼基本上没时间对其进行编码,但这需要相当长的时间进行编码。发生了什么事?

4 个答案:

答案 0 :(得分:6)

std::cin在文本模式下打开,因此它一直在寻找各种要注意的事物(如换行符等)。

鉴于std::cin输入流不断嗅闻,我不会感到惊讶,它需要更长的时间,但看起来确实有点过分。以下内容绕过iostream并直接使用FILE流可能会达到您的预期效果:

#include <cstdlib>
#include <cstdio>

int main(int argc, char *argv[])
{
    static unsigned char const codebook[] =
    {
        0x00, 0x1E, 0x2D, 0x33, 0x4B, 0x55, 0x66, 0x78,
        0x87, 0x99, 0xAA, 0xB4, 0xCC, 0xD2, 0xE1, 0xFF
    };

    for (int c = std::fgetc(stdin); c!=EOF; c=std::fgetc(stdin))
    {
        std::fputc(codebook[c >> 4], stdout);
        std::fputc(codebook[c & 0x0F], stdout);
    }

    return EXIT_SUCCESS;
}

我在加载了az范围内的字符的10MB随机文件中测试了上面的完全代码,使用{{1}时结果非常长}和std::cin。直接使用std::cout流,差异是巨大。本答案中的所有代码均使用FILE优化Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)进行了测试。

使用-O3

FILE

使用time ./hamming < bigfile.txt > bigfile.ham real 0m1.855s user 0m1.812s sys 0m0.041s std::cin

std::cout

time ./hamming < bigfile.txt > bigfile.ham real 0m23.819s user 0m7.416s sys 0m16.377s std::cinstd::cout 一起使用

std::cout.sync_with_stdio(false);

总之, ouch 。值得注意的是在系统中花费的时间。如果我有机会使用time ./hamming < bigfile.txt > bigfile.ham real 0m24.867s user 0m7.705s sys 0m17.118s std::istream::get()方法更新此内容,但老实说,我并不期待任何奇迹。除非有一些魔法(对我而言,不是对他人)从put()转换 off io xlat的方式,std::cin流可能是一个合理的选择。我还没有调查过FILE std::cin是否也是一个可行的选择,但它可能也有希望。


修改:使用rdbuf()

使用streambuf迭代器类有一个显着的改进,因为它基本上绕过了所有内联slat垃圾,但它仍然不如std::istreambuf_iterator<char>流有效:

FILE

结果:

#include <iostream>
#include <cstdlib>
#include <cstdio>

int main(int argc, char *argv[])
{
    static unsigned char const codebook[] =
    {
        0x00, 0x1E, 0x2D, 0x33, 0x4B, 0x55, 0x66, 0x78,
        0x87, 0x99, 0xAA, 0xB4, 0xCC, 0xD2, 0xE1, 0xFF
    };

    std::istreambuf_iterator<char> cin_it(std::cin), cin_eof;
    std::for_each(cin_it, cin_eof, [](char c)
        {
          std::cout.put(static_cast<char>(codebook[static_cast<unsigned char>(c) >> 4]));
          std::cout.put(static_cast<char>(codebook[static_cast<unsigned char>(c) & 0x0F]));
        });

    return EXIT_SUCCESS;
}

请注意,time ./hamming < bigfile.txt > bigfile.ham real 0m6.062s user 0m5.795s sys 0m0.053s 现在与system流结果相当,但FILE中其余iostream模板的开销似乎是一个痛点(但仍然比另一个{更好} {1}}尝试)。你赢了一些,你输了一些= P


编辑:无缓冲的系统IO

为了完全公平,绕过所有运行时缓冲并完全依赖系统调用来做这种疯狂,以下值得注意:

user

正如您所料,结果是可怕的:

iostream

答案 1 :(得分:2)

通过进行2次小改动,我接近一个数量级的改进。

  1. 添加std::ios_base::synch_with_stdio(false)(没有明显的差异,但影响通常是特定于实施的)
  2. 在写入之前缓冲输出(这是最大的区别)
  3. 更新的代码如下所示:

    int main()
    {
    
        //encodes a file using the (8,4) Hamming Code.
        //usage : HammingEncode.out < inputFile > outputFile 
            unsigned char const codebook[] = { 0x00, 0x1E, 0x2D, 0x33, 0x4B, 0x55, 0x66, 0x78, 0x87, 0x99, 0xAA, 0xB4, 0xCC, 0xD2, 0xE1, 0xFF };
            unsigned char in, nextByte;
            unsigned char const leftMask = 0xF0, rightMask = 0x0F;
            std::stringstream os;
    
            std::ios_base::sync_with_stdio(false);
            in = std::cin.get();
            while (std::cin) {
                nextByte = (in & leftMask) >> 4;
                os.put(codebook[nextByte]);
                nextByte = in & rightMask;
                os.put(codebook[nextByte]);
                in = std::cin.get();
            }
            std::cout << os.rdbuf();
    }
    

    <强>更新

    我尝试了另外一个实现 - 使用基础std::streambuf

    在我的系统上,原始代码需要 14秒处理完整的King James圣经 - 大约4.3 MB

    原始尝试中的代码需要 2.1秒来处理。

    此新实施需要 0.71秒来处理同一文档。

    int main()
    {
    
        //encodes a file using the (8,4) Hamming Code.
        //usage : HammingEncode.out < inputFile > outputFile 
        unsigned char const codebook[] = { 0x00, 0x1E, 0x2D, 0x33, 0x4B, 0x55, 0x66, 0x78, 0x87, 0x99, 0xAA, 0xB4, 0xCC, 0xD2, 0xE1, 0xFF };
        unsigned char in, nextByte;
        unsigned char const leftMask = 0xF0, rightMask = 0x0F;
        std::stringstream os;
    
        std::ios_base::sync_with_stdio(false);
    
    std::streambuf * pbuf = std::cin.rdbuf();
        do {
            in = pbuf->sgetc();
            nextByte = (in & leftMask) >> 4;
            os << codebook[nextByte];
            nextByte = in & rightMask;
            os << codebook[nextByte];
        } while (pbuf->snextc() != EOF);
        std::cout << os.rdbuf();
    }
    

答案 2 :(得分:1)

C ++ iostreams对于效率相当低有不好的看法,尽管不同的数字表明它是相当的质量实施问题,而不是iostream的劣势。

无论如何,只是为了确保它不像慢速硬盘,你可以比较执行时间,例如。

cat file1 > file2

cource cat会更快一些,因为它不会使数据大小翻倍。

然后尝试将您的代码的效率与以下内容进行比较:

#include <stdio.h>
#include <unistd.h>

int main()
{
    unsigned char buffer[1024*1024]; // 1MB buffer should be enough

    while (!eof(STDIN_FILENO)){
        size_t len = read(STDIN_FILENO, &buffer[0], 1024*1024);
        write(STDOUT_FILENO, &buffer[0], len);
        write(STDOUT_FILENO, &buffer[0], len);
    }

    return 0;
}

编辑:

对不起,我的不好。尝试

#include <stdio.h>
#include <unistd.h>

int main()
{
    unsigned char buffer[1024*1024]; // 1MB buffer should be enough

    size_t len = read(STDIN_FILENO, &buffer[0], 1024*1024);

    while(len > 0){
        write(STDOUT_FILENO, &buffer[0], len);
        write(STDOUT_FILENO, &buffer[0], len);
        len = read(STDIN_FILENO, &buffer[0], 1024*1024);
    }

    return 0;
}

答案 3 :(得分:0)

如果我理解正确,对于您读取的每个字节,则写入2个字节。因此输出文件将是输入的双倍大小。如果您的输入足够大 - 总IO(读+ 2 *写)时间将是显着的。

在霍夫曼编码中并非如此 - 因为你的写作通常比你读的少,而且总的IO时间会低得多。

修改 正如 Blorgbeard 所说,它可能是缓冲的不同之处。 C ++也做缓冲,但可能是默认缓冲区比Java要小得多。此外,HDD磁头应该在一个位置读取文件之间不断跳转然后在另一个位置写入 - 这一事实会显着影响整体IO性能。

在任何情况下,编码都应该以块的形式完成,以确保顺序读取和写入大块