Подтвердить что ты не робот

Загрузка файлов медленнее во втором запуске приложения с результирующим кодом

Описание приложения

У меня есть автономный инструмент для обработки данных. Этот инструмент загружает сотни тысяч файлов. Для каждого из них он выполняет некоторые вычисления, а когда записывает один индексный файл. Это все С++ (все IO - через стандартные объекты/функции библиотеки) и компилируется с таргетингом на Visual Studio 2013 amd64.

Производительность

В моем тестовом наборе данных содержится 115 757 файлов, которые необходимо обработать. Размер файлов составляет 731 МБ, а средний размер файла - 6 КБ.

  • Первый запуск: 12 секунд
  • Второй запуск: ~ 18 минут

Thats 90x slower! Второй запуск экстраполируется с одной минуты времени выполнения. Все работает после этого, как я уже догадался, одинаково медленны.

Сюрприз!

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

Свое же приложение, машина и исходные данные. Единственное отличие состоит в том, что одна папка была временно переименована.

До сих пор я могу воспроизвести этот 100% времени.

Профилирование

Естественно, следующим шагом был профиль. Я профилировал быстрый ход и медленный ход и сравнивал горячие точки. В медленной версии 86% приложения было потрачено на функцию с именем NtfsFindPrefix. Быстрая версия тратит на 0,4% своего времени здесь. Это стек вызовов:

Ntfs.sys!NtfsFindPrefix<itself>
Ntfs.sys!NtfsFindPrefix
Ntfs.sys!NtfsFindStartingNode
Ntfs.sys!NtfsCommonCreate
Ntfs.sys!NtfsCommonCreateCallout
ntoskrnl.exe!KySwitchKernelStackCallout
ntoskrnl.exe!KiSwitchKernelStackContinue
ntoskrnl.exe!KeExpandKernelStackAndCalloutEx
Ntfs.sys!NtfsCommonCreateOnNewStack
Ntfs.sys!NtfsFsdCreate
fltmgr.sys!FltpLegacyProcessingAfterPreCallbacksCompleted
fltmgr.sys!FltpCreate
ntoskrnl.exe!IopParseDevice
ntoskrnl.exe!ObpLookupObjectName
ntoskrnl.exe!ObOpenObjectByName
ntoskrnl.exe!NtQueryAttributesFile
ntoskrnl.exe!KiSystemServiceCopyEnd
ntdll.dll!NtQueryAttributesFile
KernelBase.dll!GetFileAttributesW
DataGenerator.exe!boost::filesystem::detail::status

Обратный вызов - это вызов exists. Он проверит для zipped-версии файла, не найдет его, а затем проверит для распакованного и найдет его.

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

Файл IO также показал, что продолжительность файлов "Создание" событий была в среднем МНОГО выше в медленной версии. 26 us vs 11704 us.

машина

  • Samsung SSD 830 Series
  • Intel i7 860
  • Windows 7 64 бит
  • Файловая система NTFS.
  • 32GB Ram

Резюме

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

Вопрос

Теперь, когда фоновая информация не работает, кто-нибудь узнает, что происходит и как ее исправить?

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

Является ли переименование недействительными страниц в памяти и вызывает их обновление до следующего запуска? Это ошибка в драйвере NTFS?

Спасибо за чтение!


Обновление!!

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

Мне также удалось воспроизвести эту проблему в небольшом приложении на С++, которое все тоже видят. Обратите внимание, что пример кода создаст 100k 6KB файлов на вашем компьютере в текущем каталоге. Может ли кто-нибудь еще его воспроизвести?

// using VS tr2 could replace with boost::filesystem
#include <filesystem>
namespace fs = std::tr2::sys;
//namespace fs = boost::filesystem;

#include <iostream>
#include <string>
#include <chrono>
#include <fstream>

void createFiles( fs::path outDir )
{
    // create 100k 6KB files with junk data in them. It doesn't matter that they are all the same.
    fs::create_directory( outDir );
    char buf[6144];
    for( int i = 0; i < 100000; ++i )
    {
        std::ofstream fout( outDir / fs::path( std::to_string( i ) ), std::ios::binary );
        fout.write( buf, 6144 );
    }

    fs::rename( outDir, fs::path( outDir.string() + "_tmp" ) );
    fs::rename( fs::path( outDir.string() + "_tmp" ), outDir );
}

int main( int argv, const char* argc[] )
{
    fs::path outDir = "out";

    if( !fs::exists( outDir ) )
        createFiles( outDir );

    auto start = std::chrono::high_resolution_clock::now();

    int counter = 0;
    for( fs::recursive_directory_iterator i( outDir ), iEnd; i != iEnd; ++i )
    {
        // test the non existent one, then the other
        if( !fs::exists( fs::path( i->path().string() + "z" ) ) && fs::exists( i->path() ) )
            counter += 1;

        if( counter % 100 == 0 )
            std::cout << counter << std::endl;
    }
    std::cout << counter << std::endl;

    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration< double, std::milli > s( end - start );

    std::cout << "Time Passed: " << s.count() << "ms" << std::endl;

    return 0;
}

Обновление 2

Я зарегистрировал проблему с MS здесь. Надеюсь, они могут помочь пролить свет на проблему.

4b9b3361

Ответ 1

Мое лучшее предположение: Windows Indexer создает блокировку файлов сразу после изменения, как сказал Деймон

NtfsFindPrefix уверен, что бит FCB приобретает и освобождает, но если код не является неправильным (в этом случае он должен не работать), почему он будет медленнее во втором запуске? Проводник Windows или служба индексирования делают какое-то "умное дерьмо" после того, как вы изменили файл в этой папке, например, сканирование всех файлов, чтобы настроить тип специализированной папки, и, возможно, сделать блокировку? Вы проверили, что произойдет, если вы подождите несколько минут, ничего не делая перед запуском во второй раз, или если вы убили Explorer и индексатора?

Я бы предложил создать папку, используемую специально для хранения данных для любой программы, которую вы пишете:

Пример

C:/Users/% имя_пользователя%/DataAnalysis

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

Я считаю, что в папке меню свойств есть опция отключения индексации папки. Если нет, следуйте инструкциям здесь, чтобы отключить индексирование этой папки и избежать проблем.

На странице будет предложено посетить Панель управления и перейти к Параметры индексирования. Там вы выберите Изменить и пройдите через вашу файловую систему и снимите выделение с созданной вами папки, чтобы не индексировать ее содержимое.

Сообщите мне, если это работает!

Ответ 2

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

Используйте FindFirstFileEx/FindNextFile, чтобы получить имена всех файлов в вашей рабочей папке. Загрузите результаты в std::vector (или в выбранный вами контейнер). Сортировать. Используйте std:: binary_search, чтобы проверить, существует ли определенный файл.

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