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

3

我想知道如何在C语言中将整个文件转换为小写,要求高效。目前我的做法是使用fgetc将字符转换为小写,并使用fputc将其写入另一个临时文件中。最后删除原始文件并将临时文件重命名为原始文件的名称。但我认为一定有更好的解决方案。


3
为什么您想要在C语言中做这件事?像这样的任务适合使用脚本语言。 - user395760
他想要从那台电脑中挤出每一点性能。 - Jesus Ramos
我同意delnan的观点,但是回答你的问题——你认为你能改进什么?我认为你不能完全缓冲复制,因为你必须修改流。你可以在中间将其完全加载到内存中,以避免磁盘过度使用,但这是经典的内存与吞吐量之间的权衡,这并不是一个明显的改进。 - Merlyn Morgan-Graham
1
大小写转换取决于语言环境。您是否关心其他语言,或者 tr A-Z a-z 的等效方式是否足够? - Joey Adams
没有必要在调用 rename 之前使用 unlink 删除文件,因为 rename 会为您将旧文件移开。此外,虽然 fgetc 使用缓冲 I/O,但为什么不一次请求大块数据并最小化所需调用的数量呢? - unpythonic
显示剩余2条评论
5个回答

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的。

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

  • buffer使用malloc分配而不是在堆栈上分配时,速度大约快75%。
  • 使用条件语句而不是转换表时,速度大约快65%。

3

我认为你说得很对。临时文件意味着在确认处理完成之前不会删除原始文件,这意味着在错误情况下将保留原始文件。我认为这是正确的方式。

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


3
通常情况下,使用freadfwrite来读取和写入大块的输入/输出可以使处理大型输入数据更快一些。另外,您应该尽可能地将更大的一块(如整个文件)存储到内存中,然后一次性写入所有内容。
编辑:我刚想起来还有一件事。有时候,如果选择一个质数(至少不是2的幂),作为缓冲区大小,程序可能会更快。我记得这与缓存机制的具体细节有关。

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)手工制作循环,可能会比现有的通用实用程序提供显着的加速。


1

如果你知道字符编码,那么你肯定可以大大加快速度。由于你正在使用Linux和C语言,我敢猜测你在使用ASCII编码。

在ASCII编码中,我们知道A-Z和a-z是连续的,并且它们之间始终相差32。因此,我们可以忽略toLower()函数的安全检查和区域设置检查,像这样做:

(伪代码) foreach (int) char c in the file: c -= 32.

或者,如果可能存在大写和小写字母,请进行如下检查: if (c > 64 && c < 91) // 大写字母的ASCII范围 then do the subtract and write it out to the file.

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

这样应该会快得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接