Падение производительности DLL-библиотеки Unity C ++ в режиме автономной сборки или редактирования - PullRequest
0 голосов
/ 26 сентября 2018

Аннотация

Я создаю неуправляемый плагин C ++ Dll для проекта Unity, где плагин взаимодействует с двумя API-интерфейсами датчиков, запускает алгоритм объединения повторяющихся датчиков и возвращает окончательные результаты с помощью функции обратного вызова.Проект работает на Windows 10 64bit.В редакторе Unity все шло гладко, пока я не попытался собрать проект Unity в автономной версии.В режиме build цикл алгоритма слияния сенсоров имел постоянные «икоты», при которых время выполнения будет постоянно увеличиваться в 10 раз за одну или две итерации.

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

Соответствующий псевдокод

Функции Dll:

extern "C" {
    void start(FilterWrapper *& pWrapper, Callback cb) {
        pWrapper = new FilterWrapper();
        // code registers callback
    }

    void stop(FilterWrapper *& pWrapper) {
        pWrapper->stopFilter();
        delete pWrapper;
        pWrapper = NULL;
    }
}

FilterWrapper Class

Class FilterWrapper
{
public:
    FilterWrapper();
    ~FilterWrapper();
    void stopFilter();

private:
    void sampleSensor1();
    void sampleSensor2();
    void processData();
    void runAlgorithm();

    bool stop_condition = false;
    std::thread thread1,thread2,thread3,thread4;
    std::deque<float> bufferA,bufferB,bufferC;
    std::mutex mtxA,mtxB,mtxC;
};

FilterWrapper::FilterWrapper() {
    thread1 = std::thread(&FilterWrapper::sampleSensor1,this);
    thread2 = std::thread(&FilterWrapper::sampleSensor2,this);
    thread3 = std::thread(&FilterWrapper::processData,this);
    thread4 = std::thread(&FilterWrapper::runAlgorithm,this);
}

void FilterWrapper::stopFilter() {
    stop_condition = true;
    if (thread1.joinable()) thread1.join();
    // same for other threads ...
}

void FilterWrapper::sampleSensor1() {
    while(!stop_condition) {
        // code sample data
        std::lock_guard<std::mutex> lck(mtxA);
        bufferA.push_back(data);
    }
}

void FilterWrapper::sampleSensor2() {
    while(!stop_condition) {
        // code sample data
        std::lock_guard<std::mutex> lck(mtxB);
        bufferB.push_back(data);
    }
}

void FilterWrapper::processData() {
    while(!stop_condition) {
        float data;
        {
            std::lock_guard<std::mutex> lck(mtxA);
            if (bufferA.empty()) continue;
            data = bufferA.front();
            bufferA.pop_front();
        }

        // code process data...

        std::lock_guard<std::mutex> lck(mtxC);
        bufferC.push_back(data);
    }
}

void FilterWrapper::runAlgorithm() {
    while(!stop_condition) {
        float data1, data2;
        {
            std::lock_guard<std::mutex> lck(mtxB);
            if (bufferB.empty()) continue;
            data1 = bufferB.front();
            bufferB.pop_front();
        }
        {
            std::lock_guard<std::mutex> lck(mtxC);
            if (bufferC.empty()) continue;
            data1 = bufferC.front();
            bufferC.pop_front();
        }

        std::chrono::stead_clock::time_point t_start = std::chrono::stead_clock::now();
        // run the algorithm with data1 and data2 ...
        std::chrono::stead_clock::time_point t_end = std::chrono::stead_clock::now();
        std::chrono::duration<float,std::milli> dur = t_end-t_start;
        std::cout << "algorithm time: "  << dur.count() << "\n";
    }
 }

Структура проекта

  1. Внутри DLL

    • A FilterWrapper Класс, экземпляр которого будет инициализироваться и управлять:
      • Sensor1поток выборки - поток производителя, сохраняет данные в буфере FIFO A
      • поток выборки Sensor2 - поток данных, сохраняет данные в буфере FIFO B
      • поток обработки данных датчика - обрабатывает необработанные данные избуфера A и поставьте в очередь результаты в буфере FIFO C
      • Поток алгоритма - потребительский поток, извлеките данные из буфера FIFO B & C и запустите алгоритм
      • Все потоки будут работать во время циклов с остановкойусловие
    • Функции для экспорта
      • и инициализация (FilterWrapper * & pWrapper, Callback cb) функция - для создания FilterWrapper объект через новый и передать указатель, и передать функцию обратного вызова.
      • a stop (FilterWrapper * & pWrapper) функция - для установки условий остановки для всех потоков в FilterWrapper объект и освободить указатель с помощью delete .
  2. На стороне Unity

    • Импортируйте функции initialize () и stop () , используя [DllImport ("MyDLL")] *
    • Call initialize () in Awake () и передача функции обратного вызова
    • Вызов stop () in OnApplicationQuit ()
    • Использование личного IntPtr pWrapper для хранения ссылки на объект FilterWrapper , переданный initialize () .

Проблема

Сначала я разработал и проверил алгоритм и многопоточность в проекте консольного приложения C ++, затем скопировал классы и функции в проект DLL и написал FilterWrapper Класс.В консольном приложении C ++, а также в режиме редактора Unity время выполнения каждой итерации в цикле алгоритма постоянно составляет около 9 мс, а время каждой итерации в цикле обработки данных датчика - 12 мс.Однако при создании проекта Unity время выполнения может часто достигать 30 мс и 90 мс соответственно.

То, что я уже сделал

  1. выделяет консольное окно в DLL, чтобы я мог отслеживать отладочную информацию.
  2. use std :: chrono ::устойчивый_час, чтобы время выполнения;данные извлекаются в начале цикла, поэтому время ожидания получения блокировок НЕ учитывается.
  3. используйте std :: lock_guard и std :: mutex для обеспечения безопасного доступа к буферам.
  4. запустите чистый проект Unity со сценой по умолчанию, и единственным дополнением является присоединение скрипта C #, который импортирует и вызывает DLL;оставить все настройки сборки по умолчанию;убедитесь, что последняя сборка DLL скопирована в папку Plugin.
  5. поэкспериментируйте с приоритетами процессов и потоков: установите приоритет процесса на HIGH_PRIORITY_CLASS (на один уровень ниже приоритета в реальном времени, чтобы не влиять на стабильность системы) и установите приоритет потока на THREAD_PRIORITY_HIGHEST (несмотря на имя, а также на один уровень ниже критичного по времени приоритета).
  6. экспериментировать с ручной настройкой схожести потоков (я в отчаянии);распределить каждый поток на один логический процессор (у меня достаточно логических процессоров).
  7. достаточно долго запустить редактор и автономную сборку, чтобы обеспечить согласованное наблюдение.
  8. в DLL, закомментировать код алгоритма изамените его на код, просто динамически выделяя большой байтовый массив (uint8_t * pByteArr = new uint8_t [1200000]), memcpy () добавьте в него мусор и освободите его (delete [] pByteArr).В режиме редактора на моей машине это занимает примерно 0,18 мс.В автономной сборке он часто достигает 5 мс.

Сводка

Часть кода на C ++ работает нормально либо в консольном приложении, либо в режиме Unity Editor, когда импортируется как DLL, но оченьнестабильный и медленный, когда проект Unity создается в автономном приложении.Поскольку люди часто говорят, что режим редактора требует много времени, можно ожидать, что производительность обычно выше при создании проекта.Кажется, мой случай совершенно противоположный.Я исключил проблему с графикой, поскольку на сцене Unity действительно ничего нет, и я думаю, что есть некоторые факторы среды, которые меняются при сборке проекта, но я не уверен, где искать.

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