Оптимизировать функцию для записи битов в файл

Вот функция, которая записывает n битов в двоичный файл.

Параметры:

  • Данные: битовая последовательность для записи в файл (младший бит справа)
  • Длина: количество бит для записи
  • OutFile: файл назначения.

Первая версия функции:

void WriteBitsToFile(unsigned long long Data, unsigned Length, std::ofstream & OutFile) {
    static unsigned long long BitBuffer = 0;
    static unsigned BitCounter = 0;

    for (unsigned i = Length; i --; ) {
        (BitBuffer <<= 1) |= ((Data >> i) & 0x1);
        BitCounter ++;

        if (BitCounter == 64) {
            OutFile.write((char *) & BitBuffer, sizeof(BitBuffer));
            BitCounter = 0;
        }
    }
}

Вторая версия:

void WriteBitsToFile(unsigned long long Data, unsigned Length, std::ofstream & OutFile) {
    static unsigned long long BitBuffer = 0;
    static unsigned FreeBitCounter = sizeof(BitBuffer) << 3;

    Data &= (1 << Length) - 1;

    if (FreeBitCounter > Length) {
        BitBuffer |= (Data << (FreeBitCounter -= Length));
    } else if (FreeBitCounter < Length) {
        BitBuffer |= (Data >> (Length -= FreeBitCounter));
        OutFile.write((char *) & BitBuffer, sizeof(BitBuffer));
        BitBuffer = Data << ((sizeof(BitBuffer) << 3) - Length);
        FreeBitCounter = (sizeof(BitBuffer) << 3) - Length;
    } else {
        BitBuffer |= Data;
        OutFile.write((char *) & BitBuffer, sizeof(BitBuffer));
        BitBuffer = 0; FreeBitCounter = (sizeof(BitBuffer) << 3);
    }
}

Оба они делают работу, но второй быстрее, чем первый. Любая идея сделать это еще быстрее?

Всем спасибо за помощь!


person Danilo Brambilla    schedule 05.12.2010    source источник


Ответы (6)


  1. Я бы начал с удаления статических переменных из тела вашей функции. Они немного медленнее, так как должны проверять свое состояние (уже инициализировано или нет) при каждом вызове функции. Просто переместите их из области действия функции.

  2. Почему вы используете такой короткий буфер? Вы уверены, что вам нужно записывать в файл каждый unsigned long long? Я бы предложил использовать что-то вроде unsigned char buffer[1024].

  3. Тогда вам следует подумать, как избавиться от других операторов if.

person Stas    schedule 05.12.2010
comment
Спасибо за ваш ответ. Мне нужны статические переменные для сохранения значения BitBuffer между вызовами. BitBuffer должен записываться в файл только тогда, когда он заполнен. Я попробую использовать более длинный буфер. Мне понадобится больше кода для этого. Есть ли проблемы с использованием unsigned long long buffer[1024]? Также мне понадобится код для очистки буфера, когда задание выполнено, но я не достиг 1024. Это может быть что-то вроде «если (длина == 0), затем сбросить». - person Danilo Brambilla; 05.12.2010
comment
Re: удаление статических переменных из тела функции. Они немного медленнее, так как должны проверять свое состояние (уже инициализировано или нет) при каждом вызове функции. Это верно только для статических переменных, которые имеют сложный инициализатор (например, вызов функции). Но в данном примере компилятор может вычислить начальные значения во время компиляции, поэтому он поместит их в инициализированный сегмент данных. Таким образом, нет уже инициализированной проверки. - person user9876; 05.12.2010
comment
Чтобы завершить комментарий @user: и эта стоимость ничто по сравнению с write вызовом. @Danilo использует больший буфер char (а не long, потому что с ним будет сложно справиться в последней операции flush). - person ruslik; 05.12.2010

Вместо вызова write() попробуйте следующее:

OutFile.rdbuf()->sputn((char *) & BitBuffer, sizeof(BitBuffer));
person Nim    schedule 05.12.2010

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

person Jay    schedule 05.12.2010
comment
Спасибо за ответ. Файл открывается и закрывается только один раз во всей программе. - person Danilo Brambilla; 05.12.2010

если я правильно вас понял, вы хотите записать length младших битов длинного целого числа без знака, которое вы получаете. Вы можете сохранить цикл по входным битам, маскируя необходимые биты:

unsigned long long mask = (1ull << length) - 1; // creates a mask of 'length' 1's
BitBuffer = Data & mask;

в качестве примечания, я не понимаю, почему ваш тест и запись находятся внутри цикла в первой версии.

person davka    schedule 05.12.2010
comment
Спасибо за ваш ответ. Вы правы в том, что делает функция. Проверка и запись внутри цикла необходимы, потому что BitBuffer должен быть записан только тогда, когда он заполнен. - person Danilo Brambilla; 05.12.2010
comment
@Danilo Brambilla: о, я вижу, вы вызываете эту функцию несколько раз и сбрасываете буфер в файл только тогда, когда он заполнен? Если это так, то действительно, как уже упоминал @user9876, было бы полезно, если бы вы так сказали. Как вы гарантируете, что последний фрагмент будет записан, если буфер еще не заполнен? - person davka; 05.12.2010
comment
В конце программы я вызову функцию с Data = 0 и Length = 64, чтобы принудительно записать последний фрагмент, заканчивающийся последовательностью 0 :-) - person Danilo Brambilla; 05.12.2010

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

/**
 * @brief Write some bits to a file.
 *
 * The bits are written MSB-first to a temporary 64-bit integer, which is
 * then written to the file in host byte-order.
 *
 * @param Data The data to write to the file.  Only the least-significant
 *             Length bits are written.  Of the bits that are written, the
 *             most significant bit is written first.
 * @param Length The length of the data to write, in bits.  Must be <= 64.
 * @param OutFile The file to write to
 *
 * @note This function is not thread-safe
 * @note This function stores some state in a static variable.  You must
 *       ensure that the total data written is a multiple of 64 bits, or
 *       some data will be lost.  You can only switch from one OutFile to
 *       another on a 64-bit boundry.
 */
void WriteBitsToFile(unsigned long long Data,
                     unsigned Length,
                     std::ofstream & OutFile)
{
   static unsigned long long BitBuffer = 0;
   static unsigned BitCounter = 0; 

   // Loop through input bits, one bit at a time
   for (int i = (int)Length; i >= 0; --i)
   {
      // Get the input bit
      unsigned long long NextBit = ((Data >> i) & 1);

      // Add it to the temporary buffer
      BitBuffer = ((BitBuffer << 1) | NextBit);
      BitCounter++;

      // If the temporary buffer is full, write it out
      if (BitCounter == 64)
      {
         OutFile.write((char *) & BitBuffer, sizeof(BitBuffer));
         BitCounter = 0;
      }
   }
}

Теперь, когда я понимаю, что вы пытаетесь сделать...

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

Одной из возможных оптимизаций является запись в гораздо больший буфер (я бы предложил не менее 4 КБ). Это означает, что вам не нужно вызывать write() так часто. Вызов write() может быть относительно медленным.

person user9876    schedule 05.12.2010
comment
предложенная вами оптимизация в целом очень верна, но здесь входные данные представляют собой одно целое число unsigned long long, поэтому нет необходимости накапливать выходные данные (а также сбрасывать буфер, как это делается в OP) - person davka; 05.12.2010
comment
Извините за не прокомментированный код. На самом деле я помещаю иногда более 40 МБ в файл. Я попробую с большим буфером. - person Danilo Brambilla; 05.12.2010

Вот один из способов сделать это с большим буфером. (на псевдо-C#)

const int wordSz= sizeof(unsigned long long)*8;

void WriteBitsToFile(unsigned long long Data, unsigned Length, std::ofstream & OutFile) { 
   static unsigned long long BitBuffer[BUFSZ+1] ={0};  
   static unsigned bitsSoFar = 0;

   Data &= (1 << Length) - 1; 
   int index = bitsSoFar/wordSz;
   int offset = bitsSoFar - (index*wordSz);
   BitBuffer[index]|=Data<<offset;
   int remainder = offset+length-wordSz;
   if (remainder > 0)
   {
      index++;
      BitBuffer[index]=Data>>(length-remainder);
   }
   bitsSoFar+=length;
   if (bitsPacked > BUFSZ*wordSz)
   {
      OutFile.write((char*)BitBuffer, BUFSZ*sizeof(unsigned long long));
      bitsSoFar-=BUFSZ*wordSz;
      BitBuffer[0]=BitBuffer[BUFSZ];
   }
}
person AShelly    schedule 06.12.2010