独立构建与编辑器模式下的Unity C ++ DLL性能下降

时间:2018-09-25 21:55:50

标签: c++ multithreading performance unity3d dll

摘要

我正在为Unity项目构建一个不受管理的C ++ Dll插件,其中该插件与2个传感器API交互,运行重复的传感器融合算法,并通过回调函数返回最终结果。该项目在Windows 10 64bit上运行。在我尝试将Unity项目构建为独立版本之前,所有功能都在Unity编辑器中顺利运行。在 build 模式下,传感器融合算法循环具有恒定的“打ic”,其中执行时间将在一到两次迭代中不断增加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 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内

    • 一个 FilterWrapper 类,其实例将初始化和管理:
      • Sensor1采样线程-生产者线程,将数据存储在FIFO缓冲区A中
      • Sensor2采样线程-生产者线程,将数据存储在FIFO缓冲区B中
      • 传感器数据处理线程-处理来自缓冲区A的原始数据,并将结果排队在FIFO缓冲区C中
      • 算法线程-使用者线程,将数据从FIFO缓冲区B&C中取出并运行算法
      • 所有线程将在具有停止条件的while循环中运行
    • 导出功能
      • 一个 initialize(FilterWrapper *&pWrapper,Callback cb)函数-通过 new 创建一个 FilterWrapper 对象并将指针传递出去,并传入回调函数。
      • 一个 stop(FilterWrapper *&pWrapper)函数-设置 FilterWrapper 对象中所有线程的停止条件,并使用 delete 。
  2. 在Unity方面

    • 使用[DllImport(“ MyDLL”)]
    • 导入 initialize() stop()函数
    • Awake()中调用 initialize()并传递回调函数
    • OnApplicationQuit()
    • 中调用 stop()
    • 使用私有的 IntPtr pWrapper 保留对 initialize()传递的 FilterWrapper 对象的引用。

问题

我首先在C ++控制台应用程序项目中开发和验证了算法和多线程,然后将这些类和函数复制到DLL项目,并编写了 FilterWrapper 类。在C ++控制台应用程序以及Unity编辑器模式下,算法循环中每次迭代的执行时间始终约为9 ms,而传感器数据处理循环中每次迭代的执行时间始终为12 ms。但是,在构建Unity项目时,执行时间可能经常分别飙升至30 ms和90 ms。

到目前为止我所做的事情

  1. 在DLL中分配一个控制台窗口,以便我可以监视调试信息。
  2. 使用std :: chrono :: steady_clock计时执行时间;在循环开始时检索数据,这样就不计算等待获取锁的时间。
  3. 使用std :: lock_guard和std :: mutex来确保对缓冲区的安全访问。
  4. 使用默认场景启动一个干净的Unity项目,唯一的增加是附加了导入和调用DLL的C#脚本;将所有构建设置保留为默认设置;确保将最新的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毫秒。

摘要

当作为DLL导入时,C ++代码在控制台应用程序或Unity编辑器模式下均可正常运行,但在将Unity项目构建到独立应用程序中时,则非常不稳定且运行缓慢。正如人们经常说的那样,“编辑器”模式会产生大量开销,因此人们希望在构建项目时性能通常会更好。看来我的情况恰恰相反。我已经排除了图形问题,因为Unity场景中实际上没有任何东西,并且我认为在构建项目时会有一些环境因素发生变化,但是我不确定要看哪里。

0 个答案:

没有答案