Лучший способ конвертировать весь файл в нижний регистр в C - PullRequest
3 голосов
/ 25 июля 2011

Мне было интересно, есть ли действительно хорошее (производительное) решение, как конвертировать весь файл в нижний регистр в C. Я использую fgetc, чтобы преобразовать символ в нижний регистр и записать его в другой временный файл с помощью fputc. В конце я удаляю оригинал и переименовываю временный файл в старое имя оригинала. Но я думаю, что должно быть лучшее решение для этого.

Ответы [ 5 ]

4 голосов
/ 25 июля 2011

Это на самом деле не отвечает на вопрос (вики сообщества), но вот (более?) - оптимизированная функция для преобразования текста в нижний регистр:

#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,66 ГГц), используя GCC 4.6.0 и i686 (32-битную) Linux.Некоторые интересные наблюдения:

  • Это примерно на 75% быстрее, когда buffer выделяется с malloc, а не в стеке.
  • Это примерно на 65% быстрее при использовании условногоа не таблица преобразования.
3 голосов
/ 25 июля 2011

Обычно на больших входах вы можете получить немного быстрее, используя fread и fwrite для чтения и записи больших кусков ввода / вывода.Также вам, вероятно, следует преобразовать больший кусок (весь файл, если это возможно) в память, а затем записать все сразу.

edit: я только что вспомнил еще одну вещьИногда программы могут работать быстрее, если в качестве размера буфера вы выбираете простое число (по крайней мере, не степень 2).Кажется, я помню, что это связано со спецификой механизма кэширования.

3 голосов
/ 25 июля 2011

Я бы сказал, что ты ударился ногтем по голове.Временный файл означает, что вы не удаляете оригинал до тех пор, пока не убедитесь, что завершили его обработку, что означает, что в случае ошибки оригинал остается.Я бы сказал, что это правильный способ сделать это.

Как подсказывает другой ответ (если позволяет размер файла), вы можете выполнить отображение файла в памяти с помощью функции mmap, и он будет легко доступен в памяти (нет реальной разницы в производительности, если файл меньше размера страницы, так как он, вероятно, будет прочитан в память после того, как вы все равно впервые прочитаете)

1 голос
/ 25 июля 2011

Если вы обрабатываете большие файлы (например, размером в несколько мегабайт), и эта операция абсолютно критична по скорости, то, возможно, имеет смысл выйти за рамки того, о чем вы спрашивали.В частности, следует учитывать, что посимвольная операция будет выполняться менее эффективно, чем при использовании 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';даже утилиты оболочки, скорее всего, будут работать лучше, чем это.

Но я согласен, что если ваша потребность очень специального назначения (то есть что-то такое же четкое, как ввод ASCII для преобразования в верхний регистр), то цикл ручной работыкак указано выше, использование векторных наборов инструкций (таких как встроенные компоненты / сборка SSE, ARM NEON или PPC Altivec), вероятно, позволит значительно ускорить работу по сравнению с существующими утилитами общего назначения.

1 голос
/ 25 июля 2011

Ну, вы определенно можете ускорить это, , если , вы знаете, что такое кодировка символов.Так как вы используете Linux и C, я собираюсь выйти из положения и предположить, что вы используете ASCII.

В ASCII мы знаем, что AZ и az непрерывны и всегда на расстоянии 32.Таким образом, мы можем игнорировать проверки безопасности и проверки локали функции toLower () и делать что-то вроде этого:

(псевдокод) foreach (int) char c в файле: c - = 32.

Или, если могут быть заглавные и строчные буквы, выполните проверку, как если бы (c> 64 && c <91) // диапазон ASCII верхнего регистра, затем выполните вычитание и запишите его в файл. </p>

Кроме того, пакетная запись выполняется быстрее, поэтому я бы предложил сначала записать в массив, а затем сразу записать содержимое массива в файл.

Это должно быть значительно быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...