3

我想知道是否有一个非常好的(高性能)解决方案如何在 C 中将整个文件转换为小写。我使用 fgetc 将 char 转换为小写并使用 fputc 将其写入另一个临时文件。最后,我删除了原始文件并将临时文件重命名为旧的原始名称。但我认为必须有更好的解决方案。

4

5 回答 5

4

这并不能真正回答问题(社区 wiki),但这是一个(过度?)优化的函数,用于将文本转换为小写:

#include <assert.h>
#include <ctype.h>
#include <stdio.h>

int fast_lowercase(FILE *in, FILE *out)
{
    char buffer[65536];
    size_t readlen, wrotelen;
    char *p, *e;
    char conversion_table[256];
    int i;

    for (i = 0; i < 256; i++)
        conversion_table[i] = tolower(i);

    for (;;) {
        readlen = fread(buffer, 1, sizeof(buffer), in);
        if (readlen == 0) {
            if (ferror(in))
                return 1;
            assert(feof(in));
            return 0;
        }

        for (p = buffer, e = buffer + readlen; p < e; p++)
            *p = conversion_table[(unsigned char) *p];

        wrotelen = fwrite(buffer, 1, readlen, out);
        if (wrotelen != readlen)
            return 1;
    }
}

当然,这不是 Unicode 感知的。

我使用 GCC 4.6.0 和 i686(32 位)Linux 在 Intel Core 2 T5500 (1.66GHz) 上对此进行了基准测试。一些有趣的观察:

  • buffer当分配malloc在堆栈上而不是在堆栈上时,它的速度大约是 75% 。
  • 使用条件而不是转换表的速度大约快 65%。
于 2011-07-25T01:47:03.833 回答
3

我会说你一针见血。临时文件意味着您在确定完成处理之前不会删除原始文件,这意味着在出错时原始文件仍然存在。我会说这是正确的做法。

正如另一个答案所建议的(如果文件大小允许),您可以通过 mmap 函数对文件进行内存映射,并使其在内存中随时可用(如果文件小于页面大小,则没有真正的性能差异,因为它可能无论如何,一旦您进行第一次读取,就会被读入内存)

于 2011-07-24T22:48:43.343 回答
3

fread通过使用和fwrite读取和写入大块输入/输出,您通常可以在大输入上更快一点。此外,您可能应该将更大的块(如果可能,整个文件)转换为内存,然后一次将其全部写入。

编辑:我只记得一件事。有时,如果您选择素数(至少不是 2 的幂)作为缓冲区大小,程序会更快。我似乎记得这与缓存机制的细节有关。

于 2011-07-24T22:51:04.593 回答
1

如果您正在处理大文件(例如数兆字节的大文件)并且此操作绝对是速度关键,那么超出您所询问的内容可能是有意义的。需要特别考虑的一件事是,逐字符操作的性能不如使用 SIMD 指令好。

即,如果您使用 SSE2,您可以编写toupper_parallel类似的代码(伪代码):

for (cur_parallel_word = begin_of_block;
     cur_parallel_word < end_of_block;
     cur_parallel_word += parallel_word_width) {
    /*
     * in SSE2, parallel compares are either about 'greater' or 'equal'
     * so '>=' and '<=' have to be constructed. This would use 'PCMPGTB'.
     * The 'ALL' macro is supposed to replicate into all parallel bytes.
     */
    mask1 = parallel_compare_greater_than(*cur_parallel_word, ALL('A' - 1));
    mask2 = parallel_compare_greater_than(ALL('Z'), *cur_parallel_word);
    /*
     * vector op - and all bytes in two vectors, 'PAND'
     */
    mask = mask1 & mask2;
    /*
     * vector op - add a vector of bytes. Would use 'PADDB'.
     */
    new = parallel_add(cur_parallel_word, ALL('a' - 'A'));
    /*
     * vector op - zero bytes in the original vector that will be replaced
     */
    *cur_parallel_word &= !mask;           // that'd become 'PANDN'
    /*
     * vector op - extract characters from new that replace old, then or in.
     */
    *cur_parallel_word |= (new & mask);    // PAND / POR
}

即,您将使用并行比较来检查哪些字节是大写的,然后在您或它们一起形成结果之前屏蔽原始值和“大写”版本(一个带有掩码,另一个带有反面)。

如果您使用 mmap 文件访问,这甚至可以就地执行,节省反弹缓冲区,并节省许多函数和/或系统调用。

当您的起点是逐个字符的“fgetc”/“fputc”循环时,有很多需要优化的地方;即使是 shell 实用程序也很有可能表现得比这更好。

但我同意,如果您的需求非常特殊(即像将 ASCII 输入转换为大写的内容一样清晰),那么使用矢量指令集(如 SSE 内在函数/程序集或 ARM NEON,或 PPC Altivec),可能会比现有的通用实用程序显着加速。

于 2011-07-25T08:52:22.770 回答
1

好吧,如果你知道字符编码是什么,你肯定可以加快速度。由于您使用的是 Linux 和 C,我将在这里冒昧地假设您使用的是 ASCII。

在 ASCII 中,我们知道 AZ 和 az 是连续的,并且总是相隔 32。因此,我们可以做的是忽略 toLower() 函数的安全检查和语言环境检查,并执行以下操作:

(伪代码)foreach (int) char c 在文件中:c -= 32。

或者,如果可能有大写和小写字母,请检查 if (c > 64 && c < 91) // 大写 ASCII 范围,然后进行减法并将其写入文件。

此外,批量写入速度更快,所以我建议先写入数组,然后一次性将数组的内容写入文件。

这应该相当快。

于 2011-07-24T22:59:39.400 回答