在C中将整个文件转换为小写的最佳方法

时间:2011-07-24 22:47:00

标签: c linux lowercase

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

5 个答案:

答案 0 :(得分:4)

这并没有真正回答这个问题(社区维基),但这里有一个(过度?)优化的函数将文本转换为小写:

#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%。

答案 1 :(得分:3)

我会说你已经击中了头部。临时文件意味着您不会删除原件,直到您确定已完成处理它,这意味着错误原件仍然存在。我会说这是正确的做法。

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

答案 2 :(得分:3)

通过使用freadfwrite读取和写入大块输入/输出,您通常可以在大输入上获得更快的速度。你也应该把一个更大的块(如果可能的话,整个文件)转换成内存然后一次写下来。

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

答案 3 :(得分:1)

嗯,你绝对可以加快速度,如果你知道字符编码是什么。既然你正在使用Linux和C,那么我将在这里假设你正在使用ASCII。

在ASCII中,我们知道A-Z和a-z是连续的并且总是相隔32。所以,我们可以做的是忽略toLower()函数的安全检查和区域设置检查,并执行以下操作:

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

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

此外,批量写入速度更快,因此我建议先写入数组,然后立即将数组内容写入文件。

这应该更快。

答案 4 :(得分: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可能会比现有的通用工具实现显着的加速。