Search code examples
clinuxlowercase

Best way to convert whole file to lowercase in C


I was wondering if theres a realy good (performant) solution how to Convert a whole file to lower Case in C. I use fgetc convert the char to lower case and write it in another temp-file with fputc. At the end i remove the original and rename the tempfile to the old originals name. But i think there must be a better Solution for it.


Solution

  • If you're processing big files (big as in, say, multi-megabytes) and this operation is absolutely speed-critical, then it might make sense to go beyond what you've inquired about. One thing to consider in particular is that a character-by-character operation will perform less well than using SIMD instructions.

    I.e. if you'd use SSE2, you could code the toupper_parallel like (pseudocode):

    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
    }
    

    I.e. you'd use parallel comparisons to check which bytes are uppercase, and then mask both original value and 'uppercased' version (one with the mask, the other with the inverse) before you or them together to form the result.

    If you use mmap'ed file access, this could even be performed in-place, saving on the bounce buffer, and saving on many function and/or system calls.

    There is a lot to optimize when your starting point is a character-by-character 'fgetc' / 'fputc' loop; even shell utilities are highly likely to perform better than that.

    But I agree that if your need is very special-purpose (i.e. something as clear-cut as ASCII input to be converted to uppercase) then a handcrafted loop as above, using vector instruction sets (like SSE intrinsics/assembly, or ARM NEON, or PPC Altivec), is likely to make a significant speedup possible over existing general-purpose utilities.