Производительность карты C ++ - Linux (30 секунд) против Windows (30 минут)! - PullRequest
6 голосов
/ 14 мая 2010

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

using namespace std;

vector<File*> gInputFileList; //Can contain duplicates, File has member sFilename
map<string, File*> gProcessedFileList; //Using map to avoid linear search costs

void processFile(File* pFile)
{
    File* pProcessedFile = gProcessedFileList[pFile->sFilename];
    if(pProcessedFile != NULL)
        return; //Already processed

    foo(pFile); //foo() is the action to do for each file
    gProcessedFileList[pFile->sFilename] = pFile;
}

void main()
{
    size_t n= gInputFileList.size(); //Using array syntax (iterator syntax also gives identical performance)
    for(size_t i=0; i<n; i++){
        processFile(gInputFileList[i]);
    }
}

Код работает правильно, но ...

Моя проблема в том, что когда размер ввода равен 1000, это занимает 30 минут - ПОЛОВИНУ ЧАСА - в Windows / Visual Studio 2008 Express. Для того же ввода требуется всего 40 секунд для запуска в Linux / gcc!

В чем может быть проблема? Действие foo () выполняется очень быстро, если используется отдельно. Должен ли я использовать что-то вроде вектора :: резерв для карты?

РЕДАКТИРОВАТЬ, ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ

Что делает foo (): 1. открывает файл 2. читает это в память 3. закрывает файл 4. содержимое файла в памяти анализируется 5. строит список токенов; Я использую вектор для этого.

Всякий раз, когда я ломаю программу (при запуске программы с набором входных файлов 1000+): стек вызовов показывает, что программа находится в середине std :: vector add.

Ответы [ 10 ]

18 голосов
/ 14 мая 2010

В Microsoft Visual Studio имеется глобальная блокировка при доступе к стандартной библиотеке C ++ для защиты от проблемы многопоточности в сборках отладки. Это может привести к большим ударам производительности. Например, наш полный тестовый код выполняется в Linux / gcc за 50 минут, тогда как в Windows VC ++ 2008 ему требуется 5 часов. Обратите внимание, что это снижение производительности не существует при компиляции в режиме Release с использованием неотладочной среды выполнения Visual C ++.

3 голосов
/ 14 мая 2010

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

2 голосов
/ 14 мая 2010

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

2 голосов
/ 14 мая 2010

Я очень сильно сомневаюсь, что ваша проблема производительности исходит от контейнеров STL.

Попробуйте исключить (закомментировать) вызов foo(pFile) или любого другого метода, который касается файловой системы. Хотя запуск foo(pFile) один раз может показаться быстрым, запуск его на 1000 различных файлов (особенно в моих файловых системах Windows, по моему опыту) может оказаться намного медленнее (например, из-за поведения кэша файловой системы).

EDIT

Ваш первоначальный пост утверждал, что были затронуты ОБА отладки и сборки выпуска. Теперь вы отзываете эту претензию.

Помните, что в сборках DEBUG:

  1. выполнение STL выполняет дополнительные проверки и утверждения
  2. куча операции (выделение памяти и т. д.) выполнять дополнительные проверки и утверждения; кроме того, при отладке строит куча с низкой фрагментацией is отключено (до 10 раз замедление выделения памяти)
  3. оптимизация кода не выполняется, что может привести к дальнейшему STL снижение производительности (STL много раз полагается на встраивание, разматывание цикла и т. д.)

При 1000 итерациях вы, вероятно, не будете затронуты вышеперечисленным (по крайней мере, не на уровне внешнего цикла), если только вы не используете STL / кучу INSIDE foo ().

1 голос
/ 14 мая 2010

Вы можете немного улучшить положение вещей, если вы бросите карту и разделите вектор. Это подразумевает переупорядочение списка входных файлов. Это также означает, что вы должны найти способ быстро определить, был ли файл уже обработан, возможно, удерживая флаг в классе File. Если можно изменить порядок списка файлов и если вы можете сохранить этот грязный флаг в объекте File, то вы можете повысить производительность с O (n log m) до O (n) для n файлов и m обработанных файлов.

#include <algorithm>
#include <functional>
// ...
vector<File*>::iterator end(partition(inputfiles.begin(), inputfiles.end(),
                                      not1(mem_fun(&File::is_processed))));
for_each(inputfiles.begin(), end, processFile);

Если вы не можете переупорядочить список файлов или не можете изменить объект File, тогда вы можете переключить map с помощью vector и затемнить каждый файл в списке входных файлов с помощью флажка второй вектор с тем же индексом. Это будет стоить вам O (n) места, но даст вам O (1) проверку на грязное состояние.

vector<File*> processed(inputfiles.size(), 0);

for( vector<File*>::size_type i(0); i != inputfiles.size(); ++i ) {
    if( processed[i] != 0 ) return;  // O(1)
    // ...
    processed[i] = inputfiles[i];    // O(1)
}

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

Я не ожидаю, что какой-либо из них даст решение для такого снижения производительности, но тем не менее.

1 голос
/ 14 мая 2010

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

Возможно, вы случайно создали алгоритм Shlemiel the Painter .

1 голос
/ 14 мая 2010

Попробуйте прокомментировать каждый блок или крупную операцию, чтобы определить, какая часть фактически вызвала разницу во времени выполнения в Linux и Windows. Я также не думаю, что это будет из-за карты STL. Проблема может быть внутри foo (). Это может быть связано с какой-то файловой операцией, так как это единственное, о чем я мог подумать, что в этом случае это будет дорогостоящим.

Вы можете вставлять вызовы clock () между операциями, чтобы получить представление о времени выполнения.

1 голос
/ 14 мая 2010

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

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

1 голос
/ 14 мая 2010

Я был бы изумлен, если бы проблемы с производительностью, которые вы видите, имели какое-либо отношение к классу map. Выполнение 1000 поисков и 1000 вставок должно занимать объединенное время порядка микросекунд. Что foo() делает?

0 голосов
/ 15 мая 2010

Если вы выполняете большую часть своей работы в Linux, я настоятельно рекомендую вам когда-либо компилировать только режим релиза в Windows. Это значительно облегчает жизнь, особенно если учесть, что все негибкие библиотеки Windows работают с головной болью.

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