Аннотация
Я создаю неуправляемый плагин 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";
}
}
Структура проекта
Внутри 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 .
На стороне 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 мс соответственно.
То, что я уже сделал
- выделяет консольное окно в DLL, чтобы я мог отслеживать отладочную информацию.
- use std :: chrono ::устойчивый_час, чтобы время выполнения;данные извлекаются в начале цикла, поэтому время ожидания получения блокировок НЕ учитывается.
- используйте std :: lock_guard и std :: mutex для обеспечения безопасного доступа к буферам.
- запустите чистый проект Unity со сценой по умолчанию, и единственным дополнением является присоединение скрипта C #, который импортирует и вызывает DLL;оставить все настройки сборки по умолчанию;убедитесь, что последняя сборка DLL скопирована в папку Plugin.
- поэкспериментируйте с приоритетами процессов и потоков: установите приоритет процесса на HIGH_PRIORITY_CLASS (на один уровень ниже приоритета в реальном времени, чтобы не влиять на стабильность системы) и установите приоритет потока на THREAD_PRIORITY_HIGHEST (несмотря на имя, а также на один уровень ниже критичного по времени приоритета).
- экспериментировать с ручной настройкой схожести потоков (я в отчаянии);распределить каждый поток на один логический процессор (у меня достаточно логических процессоров).
- достаточно долго запустить редактор и автономную сборку, чтобы обеспечить согласованное наблюдение.
- в DLL, закомментировать код алгоритма изамените его на код, просто динамически выделяя большой байтовый массив (uint8_t * pByteArr = new uint8_t [1200000]), memcpy () добавьте в него мусор и освободите его (delete [] pByteArr).В режиме редактора на моей машине это занимает примерно 0,18 мс.В автономной сборке он часто достигает 5 мс.
Сводка
Часть кода на C ++ работает нормально либо в консольном приложении, либо в режиме Unity Editor, когда импортируется как DLL, но оченьнестабильный и медленный, когда проект Unity создается в автономном приложении.Поскольку люди часто говорят, что режим редактора требует много времени, можно ожидать, что производительность обычно выше при создании проекта.Кажется, мой случай совершенно противоположный.Я исключил проблему с графикой, поскольку на сцене Unity действительно ничего нет, и я думаю, что есть некоторые факторы среды, которые меняются при сборке проекта, но я не уверен, где искать.