我想知道如何在C语言中将整个文件转换为小写,要求高效。目前我的做法是使用fgetc将字符转换为小写,并使用fputc将其写入另一个临时文件中。最后删除原始文件并将临时文件重命名为原始文件的名称。但我认为一定有更好的解决方案。
这并没有真正回答问题(社区百科),但是这里有一个过度优化的函数,将文本转换为小写:
#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%。我认为你说得很对。临时文件意味着在确认处理完成之前不会删除原始文件,这意味着在错误情况下将保留原始文件。我认为这是正确的方式。
如另一个答案所建议(如果文件大小允许),您可以通过mmap函数对文件进行内存映射,并使其随时可用于内存中(如果文件小于页面大小,则没有真正的性能差异,因为一旦您进行第一次读取,它可能会被读入内存)
fread
和fwrite
来读取和写入大块的输入/输出可以使处理大型输入数据更快一些。另外,您应该尽可能地将更大的一块(如整个文件)存储到内存中,然后一次性写入所有内容。如果你正在处理大文件(比如说,多兆字节),并且这个操作绝对是速度至关重要的话,那么超越你所询问的范围可能是有意义的。特别需要考虑的一件事情是,逐字符操作的性能将不如使用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)手工制作循环,可能会比现有的通用实用程序提供显着的加速。
如果你知道字符编码,那么你肯定可以大大加快速度。由于你正在使用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.
此外,批量写入更快,因此建议先将内容写入数组,然后一次性将数组的内容写入文件。
这样应该会快得多。
tr A-Z a-z
的等效方式是否足够? - Joey Adamsrename
之前使用unlink
删除文件,因为rename
会为您将旧文件移开。此外,虽然fgetc
使用缓冲 I/O,但为什么不一次请求大块数据并最小化所需调用的数量呢? - unpythonic