CPP: парсинг String Stream слишком медленный

Мой код cpp должен прочитать текстовый файл размером 7 МБ со значениями с плавающей запятой, разделенными пробелами. Анализ строковых значений в массив с плавающей запятой занимает около 6 секунд, что слишком много для моего варианта использования.

Я проверял в Интернете, и люди говорят, что обычно физический ввод-вывод требует времени. Чтобы устранить это, я одним выстрелом читаю файл в поток строк и использую его для синтаксического анализа с плавающей запятой. По-прежнему нет улучшения скорости кода. Есть идеи, как заставить его работать быстрее?

Вот мой код (заменил записи массива на dummy_f для простоты):

    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    #include "time.h"
    #include <sstream>
    using namespace std;

    int main()
    {
      ifstream testfile;
      string filename = "test_file.txt";
      testfile.open(filename.c_str());

      stringstream string_stream;
      string_stream << testfile.rdbuf();

      testfile.close();

      clock_t begin = clock();
      float dummy_f;

      cout<<"started stream at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;

      for(int t = 0; t < 6375; t++)
      {

           string_stream >> dummy_f;

           for(int t1 = 0; t1 < 120; t1++)
           {
               string_stream >> dummy_f;
           }
      }

      cout<<"finished stream at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;

      string_stream.str("");

      return 0;
     } 

Редактировать:

Вот ссылка на файл test_cases.txt https://drive.google.com/file/d/0BzHKbgLzf282N0NBamZ1VW5QeFE/view?usp=sharing

Пожалуйста, измените размер внутреннего цикла на 128 при работе с этим файлом (допущена опечатка)

Изменить: нашел способ заставить его работать. Объявлен dummy_f как строка и прочитан из потока строк как строковое слово. Затем используется atof для преобразования строки в число с плавающей запятой. Затраченное время составляет 0,4 секунды, что достаточно хорошо для меня.

  string dummy_f;
  vector<float> my_vector;
  for(int t = 0; t < 6375; t++)
  {

       string_stream >> dummy_f;
       my_vector.push_back(atof(dummy_f.c_str()));
       for(int t1 = 0; t1 < 128; t1++)
       {
           string_stream >> dummy_f;
            my_vector.push_back(atof(dummy_f.c_str()));
       }
  }

person masupial    schedule 27.08.2015    source источник
comment
Не измеряйте производительность в отладочных сборках.   -  person    schedule 27.08.2015
comment
Это с целыми числами, но вы должны изменить его для работы с числами с плавающей запятой: c" title="эффективное чтение очень большого текстового файла в c">stackoverflow.com/questions/26736742/   -  person NathanOliver    schedule 27.08.2015
comment
@Dieter Для выпуска требуется 6 секунд. Режим отладки занимает около 10 секунд. Вот что так озадачивает.   -  person masupial    schedule 27.08.2015
comment
Вы рассчитали время подразделов? Какие из них занимают больше всего времени?   -  person jaggedSpire    schedule 27.08.2015
comment
Вы должны зарезервировать место в stringstream или строке. Вы хотите свести к минимуму частоту изменения размеров структур данных.   -  person Thomas Matthews    schedule 27.08.2015
comment
Довольно сложно делать предложения, не имея возможности ЗАПУСТИТЬ свой код, поэтому не могли бы вы предоставить тестовый файл, который воспроизводит это?   -  person Mats Petersson    schedule 27.08.2015
comment
@ThomasMatthews: Вы имеете в виду бит, который находится ДО разбора строки?   -  person Mats Petersson    schedule 27.08.2015
comment
Согласно вашему циклу, вы не используете ввод-вывод (по крайней мере, не в файл). Форматирование требует времени, если только ваш файл не имеет фиксированного формата и вы не можете написать пользовательскую функцию ввода.   -  person Thomas Matthews    schedule 27.08.2015
comment
@MatsPetersson: Плохо, я руководствовался текстом вопроса, а не кодом.   -  person Thomas Matthews    schedule 27.08.2015
comment
Прочитайте весь буфер, затем попробуйте синхронизацию с sscanf, чтобы увидеть, видите ли вы аналогичную производительность.   -  person Brian Vandenberg    schedule 27.08.2015
comment
@MaSu: Вы пробовали развернуть цикл?   -  person Thomas Matthews    schedule 27.08.2015
comment
Рассматривали ли вы хранение поплавков в другом формате - например, необработанная память, каждое значение занимает 4 байта независимо от того, что + почти мгновенное чтение (в моем случае 27 мс).   -  person    schedule 27.08.2015
comment
Выложенный код, за вычетом #include <stdafx.h>, с файлом, содержащим 121 случайное число в строке 6375, считывается за 220 мс. Размер файла 6947965 байт. [На самом деле, 0,24 с в реальном времени, что и использует Windows]. И добавление некоторого кода, чтобы убедиться, что dummy_f во внутреннем цикле действительно используется, не меняет этого.   -  person Mats Petersson    schedule 27.08.2015
comment
@NathanOliver: Другая проблема. Гораздо больший файл.   -  person Mats Petersson    schedule 27.08.2015
comment
Привет всем, спасибо за ваш ответ. @NathanOliver: мне всего два месяца в cpp, поэтому я нашел вашу ссылку очень новой. Пройдёт и попробует.   -  person masupial    schedule 27.08.2015
comment
@Mats: я использую Visual Studio в Windows 7. Какая у вас env? В любом случае, как это может быть настолько медленнее?   -  person masupial    schedule 27.08.2015
comment
@MaSu вы планируете сборку выпуска или сборку отладки? Отладочная сборка будет намного медленнее, чем выпускная. Однако в сборке Release ваш тестовый код, опубликованный выше, может не дать вам значимых временных параметров, поскольку компилятор может распознать, что dummy_f никогда не используется, и оптимизировать целые фрагменты вашего кода.   -  person mattnewport    schedule 27.08.2015
comment
@mattnewport: я уверен, что использую релиз. Я включил cout в цикл и убедился, что он читает файл. (Кроме того, насколько мне известно, Visual Studio не выполняет такой оптимизации)   -  person masupial    schedule 27.08.2015
comment
Я почти уверен, что проблема не в коде. ~ 1 МБ в секунду только для анализа текста в переменных типа с плавающей запятой (в среднем 10 цифр на число) кажется необычно медленным для любой современной (я бы сказал, очень грубо около 10-летней) машины. Вы должны попробовать свой код на других системах и подтвердить результаты. Анализ, который вы делаете, не является такой вычислительно утомительной задачей.   -  person luk32    schedule 27.08.2015
comment
Кроме того, обратите внимание, что когда вы задаете вопрос, связанный с производительностью, чтобы получить осмысленные ответы, вам нужно быть очень точным в настройке и конфигурации, чтобы люди могли сделать хорошее предположение, в чем проблема и являются ли данные имеет смысл; или, что еще лучше, сделайте правильное профилирование, чтобы точно определить узкие места, чтобы люди могли помочь вам решить настоящую проблему. В противном случае вы, как и вы, получите кучу спекулятивных идей; +10 комментариев и ни одного ответа, потому что в настоящее время практически невозможно дать хороший комментарий.   -  person luk32    schedule 27.08.2015
comment
@MaSu: я использую Linux, поэтому, очевидно, это совершенно другая среда. Но кажется довольно странным, что среда выполнения Windows должна быть НАСТОЛЬКО хуже. Я почти уверен, что смогу написать что-то тривиальное, которое анализирует числа с плавающей запятой посимвольно из самого файла, и это будет НАМНОГО быстрее. По-моему, здесь что-то странное.   -  person Mats Petersson    schedule 27.08.2015
comment
Что произойдет, если вы замените dummy_f на std::string вместо float?   -  person Mats Petersson    schedule 27.08.2015
comment
@Mats пробежал за 0,2 секунды !! Значит, это какая-то проблема с перегрузкой оператора ››?   -  person masupial    schedule 27.08.2015
comment
Я не знаю ответа, но похоже, что любой метод, который среда выполнения С++ использует для анализа чисел, настолько неэффективен, что я бы назвал это ошибкой...   -  person Mats Petersson    schedule 27.08.2015
comment
Я изменил dummy_f на std::string, а затем сделал atof(dummy_f.c_str()) . Это работает. Сейчас время составляет 0,4 секунды, что для меня достаточно хорошо. Я не знаю, почему ›› анализ в Visual Studio выполняется недостаточно быстро. Спасибо!   -  person masupial    schedule 27.08.2015
comment
Обработка чисел с плавающей запятой в iostreams смехотворно медленная — это известная проблема со стандартной библиотекой VS. (STL в этом разговоре на Reddit — это Стефан Т. Лававей, сопровождающий стандартной библиотеки VS.)   -  person T.C.    schedule 28.08.2015


Ответы (3)


обновление: Обсуждение в комментариях с @Mats пришло к выводу, что накладные расходы на блокировку вряд ли имеют какое-либо отношение к этому, поэтому мы вернулись к исходной точке, объясняя, почему библиотека Visual C++ так медленно выполняет синтаксический анализ. плавает. Ваш образец тестового файла выглядел так, как будто это были в основном числа с величиной, близкой к 1,0, и ничего странного не происходило. (Согласно таблицам Агнера Фога, FPU Intel в Sandybridge и более поздних версиях в любом случае не имеет штрафа за производительность за денормалы.)

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


Каждый вызов cin >> dummy_f требует блокировки, чтобы убедиться, что другой поток не изменяет входной буфер в то же время. Чтение 4 или 8 чисел с плавающей запятой одновременно с помощью scanf("%f%f%f%f", &dummy_array[0], &dummy_array[1], ...) было бы немного более эффективным, если бы это было узким местом. (scanf также не лучший API для этого, так как ему нужен адрес каждого элемента массива в качестве аргумента функции. Однако развертывание с использованием нескольких преобразований в одном scanf все еще дает небольшой выигрыш в производительности.)

Вы пытаетесь обойти это с помощью строкового потока, который может быть или не быть эффективным. Это локальная переменная в функции, поэтому, если компилятор видит все функции и встраивает их, он не может заморачиваться с блокировкой. Не может быть никаких других потоков с доступом к этой переменной.

person Peter Cordes    schedule 27.08.2015
comment
Значит, блокировка не нужна, если вы запускаете ее в Linux, и не нужна для чтения строк в Windows? Или вы просто придумываете что-то, что звучит хорошо для вас, не читая комментарии? - person Mats Petersson; 27.08.2015
comment
Я бы сказал, что это очень неэффективная блокировка, если она добавляет 5,8 секунды ко времени чтения в виде строк, когда блокировка не оспаривается другим потоком. - person Mats Petersson; 27.08.2015
comment
@MatsPetersson: Я в основном придумываю вещи, основываясь на том, как должна работать стандартная библиотека / как она может быть реализована. IDK, если это правильное объяснение обнаружения того, что он медленный в Windows, быстрый в Linux. Потоки ввода-вывода IIRC, C stdio и C++ должны быть потокобезопасными, поэтому они либо должны выполнять блокировку, либо компилятор должен быть умным и выполнять оптимизацию всей программы, чтобы увидеть, что не может быть других потоков или обработчиков сигналов. . - person Peter Cordes; 27.08.2015
comment
Кстати, я думаю, что этот вопрос говорит о том, что С++ 11 НЕ является потокобезопасным в потоках (хотя речь идет о cout, а не о cin, я не могу представить, что cin и cout настолько разные): stackoverflow.com/questions/6374264/ - person Mats Petersson; 27.08.2015
comment
И я добавил std::mutex mtx вне внешнего цикла и std::lock_guard<std::mutex> lock(mtx) во внутреннем цикле. Общее время выполнения на 1 мс больше, чем время, которое я выполнял до добавления этого дополнительного кода. Это блокировка и разблокировка 771375 раз за одну миллисекунду. Что-то я сомневаюсь, что блокировки Microsoft в 5800 раз медленнее, чем Linux std::mutex. - person Mats Petersson; 28.08.2015

На моей машине с Linux это занимает всего ‹0,3 секунды, поэтому, если OP не ошибся при сборке Debug/Release, то это должна быть проблема, уникальная для Windows:

hidden$ cat read-float.cpp 
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int main() {
  ifstream fs("/tmp/xx.txt");
  vector<float> v;
  for (int i = 0; i < 6375; i++) {
    for (int j = 0; j < 129; j++) {
      float f;
      fs >> f;
      v.emplace_back(f);
    }
  }
  cout << "Read " << v.size() << " floats" << endl;
}
hidden$ g++ -std=c++11 read-float.cpp -O3
hidden$ time ./a.out 
Read 822375 floats

real    0m0.287s
user    0m0.279s
sys 0m0.008s

hidden$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 
person Kan Li    schedule 27.08.2015
comment
Это очень странно. Это проблема с использованием ›› при чтении потока во float, и это, по-видимому, происходит только в Windows. Чтение строки из потока и последующее использование atof решает проблему. - person masupial; 28.08.2015

Альтернативная реализация с использованием atof, которая работает в 3 раза быстрее, вставлена ​​ниже. На моем ноутбуке исходный строковый поток занимает 2,3 секунды, тогда как этот завершается менее чем за 0,8 секунды для того же количества поплавков.

static char filecontents[10*1024*1024];

int testfun2()
{
  ifstream testfile;
  string filename = "test_file.txt";
  testfile.open(filename.c_str());
  int numfloats=0;
  testfile.read(filecontents,10*1024*1024);
  size_t numBytesRead = testfile.gcount();
  filecontents[numBytesRead]='\0';
  testfile.close();

  clock_t begin = clock();
  float dummy_f;

  cout<<endl<<"started at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;

  char* p= filecontents;
  char* pend = p + numBytesRead;
  while(p<pend)
  {
      while(*p && (*p <= ' '))
      {
         ++p; //skip leading white space ,\r, \n
      }
      char* pvar = p;
      while(*p > ' ')
      {
        ++p; //skip over numbers
      }
      if(*p)
      {  *p = '\0';// shorter input makes atof faster.
        ++p;
      }
      if(*pvar)
      {
         dummy_f = atof(pvar);
         ++numfloats;
      }
      //cout << endl << dummy_f;
  }

  cout<<endl<< "finished at time "<<(double) (clock() - begin) /(double) CLOCKS_PER_SEC<<endl;

  cout << endl << "numfloats= " << numfloats;
  return numfloats;
 }
person rakeshdn    schedule 27.08.2015
comment
протестировал это против второй реализации (поток в строку для плавания с использованием atof), и эта кажется игрушкой быстрее. Однако вторая реализация в вопросе занимает на моей машине от 0,9 до 1,2 секунды, что странно. - person rakeshdn; 28.08.2015
comment
У меня есть много файлов (похожих на этот, но разных размеров), и код будет читать один из них в зависимости от ввода пользователя и т. д. Поэтому я не могу использовать массив символов с предопределенным размером. Но ничего страшного, я могу жить с этой небольшой разницей в скоростях. :) - person masupial; 28.08.2015