【问题标题】:Most Efficient way to normalize 1MB text file?标准化 1MB 文本文件的最有效方法?
【发布时间】:2014-10-17 01:44:25
【问题描述】:

我有 1MB 的文本文件。 我想删除空格、换行符、制表符并将字符的大小写从小写转换为 1MB 文件的大写 4KB 迭代。

我已经写了这段代码:

for (i = 0, j= 0; i < size; ++i)
    {
        switch (Buffer[i])
        {
            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
            case 'g':
            case 'h':
            case 'i':
            case 'j':
            case 'k':
            case 'l':
            case 'm':
            case 'n':
            case 'o':
            case 'p':
            case 'q':
            case 'r':
            case 's':
            case 't':
            case 'u':
            case 'v':
            case 'w':
            case 'x':
            case 'y':
            case 'z':
                    NormalisedBuffer[j] = Buffer[i] - 32;
                    ++j;
                break;

            case ' ':
            case '\t':
            case '\r':
            case '\n':
                break;

            default:
                NormalisedBuffer[j] = Buffer[i];
                ++j;
        }
    }

有没有什么方法可以更省时、更有效地做到这一点?

【问题讨论】:

  • 你可以使用像islower()toupper()这样的函数。实现者可能已经以最有效的方式对这些进行了编码(它们可能被实现为宏)。
  • 用 C 来做吗?如果您使用的是 POSIX 平台(如 Linux 或 OSX),那么有一些 shell 命令可以为您完成。
  • 您实际测量过所花费的时间吗?我希望将 1MB 转换为上限或下限并去除空格将花费很少的时间,以至于几乎无法测量......
  • 你测量过加载 100 个文件但什么都不做的时间吗?除非你的代码极其丑陋,否则大部分时间都会花在文件 IO 上。
  • @enhzflep:在这样的开关中,编译器将根据表生成代码,或者可能是if/else 的短链 - 该集合最多三个。重新组织代码根本没有任何好处,编译器无论如何都不会按照您编写的顺序生成代码。

标签: c++ c


【解决方案1】:

虽然 OP 声称处理 100 个文件需要 12 秒,但我的测量速度要快得多。

TL;DR;

Time for OPs method: 0.006472 seconds
Faster method: 0.005364 seconds

首先,让我们定义衡量指标:我们忽略文件 I/O 所花费的时间,因为在我们关注的实施级别中无法避免它。

我们用这个接口定义数据处理函数:

int testcase (uint8_t* des, size_t deslen, const uint8_t* src, size_t srclen);

然后,我们测量 N 次迭代所花费的时间,为简单起见,我们选择 N=1:

T0 = chrono::system_clock::now();
test_base(des1, testdata_size, src, testdata_size);
T1 = chrono::system_clock::now();

一旦我们有了更科学的测量结果,我们现在就会对更快的方法有了客观的认识。 但是,我应该指出,没有在所有情况下都最快的通用算法,这就是为什么你有不同的算法。

对于这个特定的测试,我们应该考虑输入样本空间并设计一个处理,以便在统计上针对此类输入样本进行优化。

我假设输入样本是平面英语,因此大多数是 ASCII 字符,并且在小写中,很少有 TAB、SPACE 和换行符;如果不是这样,我的方法会比较慢。

现在,根据上述假设,我会跟踪处理后的字符,并将输出与 memcpy 组合在一起,这在这种特殊情况下会产生稍快的测量。


完整源码:用clang -lstdc++ -O2 a.cpp -o a编译

#include <chrono>
#include <iostream>
#include <unistd.h>
#include <math.h>

using namespace std;


int test_base(uint8_t* des, size_t deslen, const uint8_t* src, size_t srclen) {
    int i, j;
    if ( deslen < srclen ) return -1;
    for (i=0, j=0; i<srclen; ++i) {
        switch (src[i]) {
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
        case 'g':
        case 'h':
        case 'i':
        case 'j':
        case 'k':
        case 'l':
        case 'm':
        case 'n':
        case 'o':
        case 'p':
        case 'q':
        case 'r':
        case 's':
        case 't':
        case 'u':
        case 'v':
        case 'w':
        case 'x':
        case 'y':
        case 'z':
            des[j] = src[i] - 32;
            ++j;
            break;

        case ' ':
        case '\t':
        case '\r':
        case '\n':
            break;

        default:
            des[j] = src[i];
            ++j;
        }
    }
    return 0;
}

int testcase_1(uint8_t* des, size_t deslen, const uint8_t* src, size_t srclen) {
    const uint8_t* end = src + srclen;
    const uint8_t* head = src;
    size_t   len;

    if ( deslen < srclen ) return -1;
    for (; src < end; src++) {
        uint8_t value = *src;
        if ( value >= 'a' && value <='z' ) {
            size_t len = (size_t)(src - head);
            if ( len > 0 ) memcpy ( des, head, len );
            des[len] = value-32;
            des += len+1;
            head = src+1;
        } else if ( value == ' ' || value == '\t' || value == '\r' || value == '\n' ) {
            size_t len = (size_t)(src - head);
            if ( len > 0 ) memcpy ( des, head, len );
            des += len;
            head = src+1;
        } else {
            // Do Nothing
        }
    }
    return 0;
}

int main(int argc, char* argv[]) {
    chrono::time_point<chrono::system_clock>T0, T1;
    uint64_t duration;

    // Create test data
    uint8_t *src, *des1, *des2;
    const size_t testdata_size = 1024*1024; // 1MB
    src = new uint8_t[testdata_size];
    des1 = new uint8_t[testdata_size];
    des2 = new uint8_t[testdata_size];
    // TODO: seed
    for ( size_t i=0; i<testdata_size; i++ ) {
        src[i] = (uint8_t)rand();
    }
    // Put things in cache and realize the memory
    memset ( des1, 0, testdata_size);
    memset ( des2, 0, testdata_size);

    T0 = chrono::system_clock::now();
    test_base(des1, testdata_size, src, testdata_size);
    T1 = chrono::system_clock::now();
    duration = chrono::duration_cast<std::chrono::nanoseconds>(T1-T0).count();
    cout << "Duration: " << (duration/1.0E9) << endl;

    T0 = chrono::system_clock::now();
    testcase_1(des2, testdata_size, src, testdata_size);
    T1 = chrono::system_clock::now();
    duration = chrono::duration_cast<std::chrono::nanoseconds>(T1-T0).count();
    cout << "Duration: " << (duration/1.0E9) << endl;
    if ( memcmp ( des1, des2, testdata_size ) == 0 ) {
        cout << "Meaningless compare to prevent optimize!";
    }
    delete des2;
    delete des1;
    delete src;
    return 0;
}

【讨论】:

  • 无需避免 if (len &gt; 0): stackoverflow.com/questions/3751797/… - 它会在每个已处理字符的内部循环中保存一个 if
  • 为什么要计算len?它不会在每次成功迭代时简单地增加 1,否则不会?在我看来,您的 memcpy 仅用于“复制”单个字符。这对于交替 blocks 的“复制/处理”字符的场景可能很有用,但在这种情况下没有必要。
猜你喜欢
  • 2018-10-08
  • 2018-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-29
  • 2021-03-15
  • 1970-01-01
相关资源
最近更新 更多