c++多线程模型速度问题

各位友友,现在我有一个c++程序,是跑模型的代码,现在的问题是我用一个初始化一个模型用一个线程跑速度在100ms,但是我初始化两个模型用两个模型并行计算的时候每个模型的速度就增加到了200ms,两个一起的化就差不多400-500ms了,这个问题怎么解决呢

img

img


#include "SamBox.h"
#include <opencv2/core/cuda.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
#include <regex>
#include <vector>
#include <thread>
#include <future>


// 找中心点
std::vector<cv::Point> find_center(cv::Mat img)
{
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(img, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    std::vector<cv::Point> pointList;
    double area = 0;

    for (const auto &cnt : contours)
    {
        area = cv::contourArea(cnt);
        if (area > 9000)
        {
            cv::Moments M = cv::moments(cnt);
            int cX = static_cast<int>(M.m10 / M.m00);
            int cY = static_cast<int>(M.m01 / M.m00);
            pointList.push_back(cv::Point(cX, cY));
        }
    }
    return pointList;
}

void processImages(const std::vector<cv::String>& fileNames, SamBox* samBox) {
    cv::Mat blendedImage;
    cv::Mat mask; 
    for (const auto& filename : fileNames) {
        double iou;
        cv::Rect box_crop;  
        cv::Rect box_rect;    
        cv::Mat img = cv::imread(filename, -1);
        size_t pos0 = filename.find_last_of("/");
        std::string name = filename.substr(pos0+1);
        std::string inputStr = filename;
        int channels = img.channels();
        if (channels == 3)
        {
            cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
        }
        // 使用正则表达式匹配数字
        std::regex pattern(R"(\d+)");
        std::smatch matches;
        std::vector<std::string> numbers;

        // 从字符串中找到所有匹配的数字,并保存到vector中 
        while (std::regex_search(inputStr, matches, pattern)) {
            numbers.push_back(matches.str());
            inputStr = matches.suffix();
        }
        // 存储后面的四个匹配的数字
        int number1 = 0, number2 = 0, number3 = 0, number4 = 0;
        if (numbers.size() >= 4) {
            number1 = std::stoi(numbers[numbers.size() - 4]);
            number2 = std::stoi(numbers[numbers.size() - 3]);
            number3 = std::stoi(numbers[numbers.size() - 2]);
            number4 = std::stoi(numbers[numbers.size() - 1]);
        } else {
            std::cout << "Not enough numbers found.";
            
        }
        cv::Rect box = {number1,number2,number3,number4};
        auto start = std::chrono::high_resolution_clock::now(); 
        // 计算原始矩形框的中心点
        cv::Point center = cv::Point(box.x + box.width / 2, box.y + box.height / 2);
        // 计算新的矩形框的宽度和高度
        int newWidth = static_cast<int>(box.width * 2);
        int newHeight = static_cast<int>(box.height * 1.5);

        // 计算新的矩形框的左上角坐标
        int newX = center.x - newWidth / 2;
        int newY = center.y - newHeight / 2;
        cv::Mat image;
        box_crop = {newX, newY, newWidth, newHeight};
        bool flag;
        if( newX < 0 || newY < 0 || newWidth > img.size().width || newHeight > img.size().height ){
            image = img;
            flag = false;
            box_rect = {box.x,box.y,box.width,box.height};
        }
        else
        {
            image = img(box_crop);
            box_rect = {box.x-newX,box.y-newY,box.width,box.height};
            flag = true;
        }

        auto results = samBox->segmentImgs(image, box_rect);

        mask = results.first;
        iou = results.second;
        //将crop传入模型输出的mask还回原图尺寸大小
        if(flag){
            cv::Mat result_mask = cv::Mat::zeros(img.size(), img.type());
            mask.copyTo(result_mask(box_crop));
            mask = result_mask;
        }
        // 查找轮廓
        std::vector<std::vector<cv::Point>> contours;
        std::vector<cv::Vec4i> hierarchy;
        cv::findContours(mask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
        //在图像上绘制矩形框
        cv::rectangle(img,box.tl(),box.br(), cv::Scalar(0, 255, 0), 5);
        cv::drawContours(img, contours, -1, cv::Scalar(0, 0, 255), 5);
        cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> duration = end - start;
        std::cout << "all_time_2: " << duration.count() << " seconds" << std::endl;
        cv::imwrite("/home/jacc/samC++/res_img/"+name, img);
        std::cout << "............................................" << std::endl;
    }
    // }
}



int main(int argc, char **argv)
{   
    std::string folderPath = "/home/jacc/samC++/img_tray/60/";
    std::string fileExtension = ".jpg";
    std::vector<cv::String> fileNames;
    cv::glob(folderPath + "*" + fileExtension, fileNames);

    std::string folderPath1 = "/home/jacc/samC++/img_tray/70/";
    std::string fileExtension1 = ".jpg";
    std::vector<cv::String> fileNames1;
    cv::glob(folderPath1 + "*" + fileExtension1, fileNames1);

    // 初始化两个模型
    SamBox* samBoxA = new SamBox();
    SamBox* samBoxB = new SamBox();
    samBoxA->initializeSam();
    samBoxB->initializeSam();
        
    // 启动两个线程并行处理不同类别的图像
    std::thread threadA(processImages, fileNames, samBoxA);
    std::thread threadB(processImages, fileNames1, samBoxB);

    // 等待两个线程完成
    threadA.join();
    threadB.join();

    // 释放模型资源
    delete samBoxA;
    delete samBoxB;
}

你是几核CPU的?执行慢不一定是线程对CPU资源的抢占问题,你的是opencv加载和写文件,瓶颈可能在读写io上,建议你在读写资源前后打印下时间
另外你的并行运算使用的是那种库mpi?并行执行的效率比多线程的效率高,你的并行化相当于是2个进程在同时跑,有自己独立的地址空间,而多线程快也是需要一个合理的线程模型的,所以我想知道你是需要一个设计来提升执行多个processImages任务的效率,还是只是做测试看看多线程的效率?

这个问题可能是由于线程之间的竞争条件导致的。当你初始化两个模型并行计算时,每个模型的速度增加到了200ms,这可能是因为两个线程同时访问和修改模型的状态和数据,导致了竞争条件和性能下降。
为了解决这个问题,你可以考虑使用线程同步和互斥锁来保护模型的状态和数据。例如,你可以在初始化模型之前,创建一个互斥锁,并在访问和修改模型的状态和数据时使用该锁来保护它们。这样,只有一个线程可以同时访问和修改模型,避免了竞争条件和性能下降。
以下是一个使用互斥锁保护模型状态和数据的示例代码:

#include <mutex>

std::mutex mutex;

void processImages(const std::vector<cv::String>& fileNames, SamBox* samBox) {
    cv::Mat blendedImage;
    cv::Mat mask; 
    for (const auto& filename : fileNames) {
        double iou;
        cv::Rect box_crop;  
        cv::Rect box_rect;    
        cv::Mat img = cv::imread(filename, -1);
        size_t pos0 = filename.find_last_of("/");
        std::string name = filename.substr(pos0+1);
        std::string inputStr = filename;
        int channels = img.channels();
        if (channels == 3)
        {
            cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
        }
        // 使用正则表达式匹配数字
        std::regex pattern(R"(\d+)");
        std::smatch matches;
        std::vector<std::string> numbers;
 
        // 从字符串中找到所有匹配的数字,并保存到vector中 
        while (std::regex_search(inputStr, matches, pattern)) {
            numbers.push_back(matches.str());
            inputStr = matches.suffix();
        }
        // 存储后面的四个匹配的数字
        int number1 = 0, number2 = 0, number3 = 0, number4 = 0;
        if (numbers.size() >= 4) {
            number1 = std::stoi(numbers[numbers.size() - 4]);
            number2 = std::stoi(numbers[numbers.size() - 3]);
            number3 = std::stoi(numbers[numbers.size() - 2]);
            number4 = std::stoi(numbers[numbers.size() - 1]);
        } else {
            std::cout << "Not enough numbers found.";
            
        }
        cv::Rect box = {number1,number2,number3,number4};
        auto start = std::chrono::high_resolution_clock::now(); 
        // 计算原始矩形框的中心点
        cv::Point center = cv::Point(box.x + box.width / 2, box.y + box.height / 2);
        // 计算新的矩形框的宽度和高度
        int newWidth = static_cast<int>(box.width * 2);
        int newHeight = static_cast<int>(box.height * 1.5);
 
        // 计算新的矩形框的左上角坐标
        int newX = center.x - newWidth / 2;
        int newY = center.y - newHeight / 2;
        cv::Mat image;
        box_crop = {newX, newY, newWidth, newHeight};
        bool flag;
        if( newX < 0 || newY < 0 || newWidth > img.size().width || newHeight > img.size().height ){
            image = img;
            flag = false;
            box_rect = {box.x,box.y,box.width,box.height};
        }
        else
        {
            image = img(box_crop);
            box_rect = {box.x-newX,box.y-newY,box.width,box.height};
            flag = true;
        }
 
        mutex.lock();
        auto results = samBox->segmentImgs(image, box_rect);
        mutex.unlock();
 
        mask = results.first;
        iou = results.second;
        //将crop传入模型输出的mask还回原图尺寸大小
        if(flag){
            cv::Mat result_mask = cv::Mat::zeros(img.size(), img.type());
            mask.copyTo(result_mask(box_crop));
            mask = result_mask;
        }
        // 查找轮廓
        std::vector<std::vector<cv::Point>> contours;
        std::vector<cv::Vec4i> hierarchy;
        cv::findContours(mask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
        //在图像上绘制矩形框
        cv::rectangle(img,box.tl(),box.br(), cv::Scalar(0, 255, 0), 5);
        cv::drawContours(img, contours, -1, cv::Scalar(0, 0, 255), 5);
        cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double> duration = end - start;
        std::cout << "all_time_2: " << duration.count() << " seconds" << std::endl;
        cv::imwrite("/home/jacc/samC++/res_img/"+name, img);
        std::cout << "............................................" << std::endl;
    }
    // }
}

在这个示例代码中,我们使用了一个互斥锁来保护模型的状态和数据。在访问和修改模型的状态和数据时,我们首先获取互斥锁,然后进行操作,最后释放互斥锁。这样,只有一个线程可以同时访问和修改模型,避免了竞争条件和性能下降。
请注意,使用互斥锁可能会增加程序的开销和延迟,因此你需要根据实际情况来决定是否使用它。如果你的程序对性能要求非常高,你可能需要考虑其他的解决方案,例如使用多线程同步原语(例如信号量和条件变量)或者使用并发编程框架(例如Boost库)。

模型是从硬盘加载的吧,这样的话,瓶颈在io读写不在cpu,同时启动越多,只会越慢。

试一试下面的方案呢?
资源分配和管理:确保每个线程都有其独立的资源,避免线程间的资源竞争。例如,如果模型需要访问同一内存区域,考虑为每个模型分配独立的内存空间。
线程同步:如果线程之间存在数据依赖,需要用到同步机制,如互斥锁(mutex)或条件变量。但请注意,过度的同步可能导致性能下降,因为它会阻止线程并行执行。
使用并行库:考虑使用专门为并行计算设计的库,如OpenMP或C++的并行算法库。这些库通常提供更高级的并行管理功能,比如动态调度和负载均衡。
优化模型加载:模型的加载和初始化可能是一个耗时的过程。考虑是否可以预加载模型,或者将模型的初始化与训练过程解耦。
硬件加速:如果模型计算非常耗时,考虑使用GPU或其他硬件加速器来加速计算。
代码剖析和优化:使用性能剖析工具(如gprof、perf等)找出代码中的瓶颈,并针对性的进行优化。
使用多进程:如果多线程效果不佳,考虑使用多进程来并行计算。多进程通常比多线程更容易管理,因为每个进程都有自己独立的内存空间。
调整线程数:过多的线程可能导致性能下降,因为线程的管理和切换本身就有开销。尝试调整线程的数量

引用 皆我百晓生 小程序回复内容作答:

在你的代码中,你使用了两个线程来并行处理不同类别的图像。然而,你的观测结果显示,在并行处理两个模型的情况下,速度变慢了。这是因为在多线程环境下,可能存在一些资源竞争和线程之间的同步问题,导致性能下降。

为了提高速度,你可以尝试以下几点优化:

  1. 调整线程数:你可以尝试调整线程数以找到最佳性能。有时,并不是线程越多越好,可能存在线程切换和上下文切换的开销。通过实验,找到最佳的线程数。

  2. 使用线程池:而不是创建和销毁线程,你可以使用线程池来管理线程。线程池重用线程,减少线程创建和销毁的开销。

  3. 使用异步执行任务:你可以使用std::async函数来异步执行任务,而不是显式创建线程。这样可以由系统自动管理线程资源。

  4. 减少资源竞争:在并行计算的情况下,确保避免多个线程同时访问和修改共享资源,这样可以减少资源竞争。例如,你可以使用互斥锁来保护共享变量的访问。

  5. 使用并行算法和数据结构:如果可能的话,尽量使用并行算法和数据结构来优化并行计算。例如,使用并行容器和算法可以更高效地处理并行计算。

尝试上述优化方法,并根据实际情况进行调整,以提高并行计算的速度。

这种情况可能是由于多个线程在并行计算时产生的资源竞争导致的。在并行计算中,如果多个线程同时访问同一份资源,可能会产生资源竞争,导致线程间互相等待,从而降低整体的计算速度。以下是一些可能的解决方案:

  1. 优化资源分配: 确保每个线程都有独立的资源,避免资源竞争。例如,如果多个线程都需要访问同一份数据,可以考虑将数据复制多份,每个线程访问自己的数据。
  2. 使用线程同步机制: 如果无法避免资源竞争,可以考虑使用线程同步机制,如互斥锁(mutex)或者条件变量(condition variable),确保每次只有一个线程访问共享资源。
  3. 调整线程数量: 有时候,增加线程数量并不一定能提高计算速度。如果线程数量过多,可能会导致线程切换的开销增加,从而降低整体的计算速度。可以尝试调整线程数量,找到最佳的并行度。
  4. 使用并行计算库: 有些并行计算库(如OpenMP、TBB等)可以自动处理一些并行计算的问题,如线程管理、资源分配等。可以考虑使用这些库来简化并行计算的复杂性。
  5. 分析代码性能: 使用性能分析工具(如gprof、perf等)分析代码的性能瓶颈,找出导致速度变慢的原因,然后针对这些瓶颈进行优化。
  6. 硬件资源限制: 检查你的硬件资源(如CPU、内存、磁盘等)是否存在瓶颈或限制,这些也可能影响到并行计算的速度。

注意,以上解决方案并非万能的,具体情况还需要根据你的代码和硬件环境来定制优化策略。

有个问题,你推理是在GPU还是CPU。
如果是CPU的话,那就是算力不足,两个模型竞争算法反而速度更慢。
如果是GPU的话,看下你加载两个模型的显存够不够,不够的话也会竞争导致速度慢,只有显存足够两个模型推理的时候,这个时候才是最快的。

结合GPT给出回答如下请题主参考
多线程的速度提升是有限度的,通常由以下几个因素决定:

  1. 线程切换开销。在多线程模型下,不同线程之间切换需要一定的开销,包括保存和恢复寄存器、更新CPU缓存、切换进程上下文等。当线程数量增加时,线程切换的开销也会增加。

  2. 线程间通信开销。多线程之间需要进行通信和同步,包括共享内存、消息传递等,这些都需要一定的开销。当线程数量增加时,线程间通信的开销也会增加。

  3. CPU资源的竞争。在多线程模型下,多个线程会竞争CPU资源,如果CPU资源不够,线程会被挂起等待。当线程数量增加时,CPU资源的竞争也会增加。

针对你的问题,初始化两个模型用两个线程计算的速度比初始化一个模型用一个线程计算的速度慢,可能是由于线程切换、线程间通信和CPU资源竞争等因素导致的。为了优化程序的性能,你可以考虑以下几点:

  1. 减少线程数量。如果线程数量太多,可能会导致上述问题,可以考虑采用线程池、协程等方式来减少线程数量。

  2. 优化线程切换和线程间通信。可以采用无锁数据结构、共享内存等方式来减少线程切换和线程间通信的开销。

  3. 优化算法和数据结构。如果能够优化算法和数据结构,可以减少计算量和数据访问量,从而提高程序的性能。

总之,多线程模型的性能优化需要综合考虑多方面因素,需要根据实际情况进行优化。

使用两个线程并行处理图像,每个线程都初始化一个模型并运行模型推理。然而,您遇到了性能问题,初始化两个模型后,处理速度变慢了。这可能是因为两个模型共享计算资源,导致性能下降。
你可以考虑使用线程池来管理线程,而不是为每个图像启动一个新线程。这可以减少线程启动和销毁的开销,并提高线程利用率。

援引讯飞星火:
这个问题可能是由于多线程之间的竞争导致的。在这种情况下,可以尝试使用互斥锁(mutex)来确保在同一时间只有一个线程访问模型。以下是修改后的代码:

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <future>

using namespace std;

// ... 其他代码不变 ...

std::mutex mtx; // 添加互斥锁

void processImages(const std::vector<cv::String>& fileNames, SamBox* samBox) {
    std::unique_lock<std::mutex> lock(mtx); // 使用互斥锁保护共享资源
    // ... 其他代码不变 ...
}

int main(int argc, char **argv)
{   
    // ... 其他代码不变 ...

    // 初始化两个模型
    SamBox* samBoxA = new SamBox();
    SamBox* samBoxB = new SamBox();
    samBoxA->initializeSam();
    samBoxB->initializeSam();
        
    // 启动两个线程并行处理不同类别的图像
    std::thread threadA(processImages, fileNames, samBoxA);
    std::thread threadB(processImages, fileNames1, samBoxB);
 
    // 等待两个线程完成
    threadA.join();
    threadB.join();
 
    // 释放模型资源
    delete samBoxA;
    delete samBoxB;
}

在这个修改后的代码中,我们为processImages函数添加了一个互斥锁mtx。在函数开始时,我们使用std::unique_lock来锁定互斥锁,以确保在同一时间只有一个线程可以访问模型。在函数结束时,互斥锁会自动解锁。这样可以避免多线程之间的竞争,提高程序的执行速度。

【以下回答由 GPT 生成】

根据问题描述和提供的代码,我无法完全理解问题的背景和问题的具体细节。但是,我可以提供一些建议来优化C++多线程模型的速度问题。

  1. 检查并行计算线程数:你可以查看并行计算时使用的线程数。如果只有两个模型,则线程数应为2。可以使用OpenCV中的setNumThreads()函数设置线程数。

  2. 确保线程安全:请确保在多线程环境中正确地使用互斥锁,以防止并发访问冲突。可以使用C++11中的std::mutex和相关函数来实现互斥锁。

  3. 并行计算数据依赖:在您提供的代码中,没有显示出模型初始化和模型计算之间是否存在数据依赖关系。如果存在数据依赖关系,则并行计算可能会导致计算顺序错误,从而导致速度下降。请确保在并行计算之前,所有的数据和依赖项都准备好,不会出现冲突。

  4. 内存管理:请确保在并行计算时适当地管理内存,在并行计算之间共享内存区域时要小心。如果内存不正确地共享或释放,可能会导致性能问题。

  5. 算法优化:评估模型初始化和模型计算的算法复杂度。可以尝试寻找更高效的算法或算法优化来提高速度。

  6. 多线程任务划分:如果模型计算工作量不平衡,可以尝试将工作平衡划分到不同的线程中。例如,将计算复杂度高的任务分配给单独的线程。

需要注意的是,根据提供的信息和代码片段,我无法提供完全准确的解决方案。为了更好地解决问题,请提供更多关于问题背景、代码和其他相关信息的细节。



【相关推荐】



如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^