C++11使用多线程(线程池)计算相似度实现性能优化
需求:图像识别中,注册的样本多了会影响计算速度,成为性能瓶颈,其中一个优化方法就是使用多线程。例如,注册了了3000个特征,每个特征4096个float。可以把3000个特征比对放到4个线程中进行计算,然后再把结果进行合并。实现思路:
1. 根据系统性能和需求配置线程池的大小,创建线程池,将比较任务平均分配到各个线程
2. 工作线程启动后在一个condition_variable上wait,注意:锁的范围不能太大了,否则多个线程会变成串行
3. 调用者调用识别接口,接口更新目标特征,通知各个工作线程,在另外一个condition_variable上wait,并且满足完成计数器的值等于线程数
4. 工作线程完成后将计数器加一,并且通知调用线程
5. 调用线程收集到所有线程的结果后再对结果进行合并返回
后续:
1. 代码进行优化,更优雅的实现
测试结果:
| 线程数 | 时间 |
| 1 | 71362ms |
| 2 | 36292ms |
| 4 | 19420ms |
| 8 | 18465ms |
| 16 | 18433ms |
| 32 | 18842ms |
| 64 | 19324ms |
| 128 | 19388ms |
| 256 | 21853ms |
| 512 | 26150ms |
| 1024 | 35593ms |
代码如下:
#include <iostream>
#include <string>
#include <cstring>
#include <mutex>
#include <unordered_map>
#include <list>
#include <utility>
#include <algorithm>
#include <string>
#include <vector>
#include <thread>
#include <chrono>
using namespace std;
using namespace chrono;
double get_mold(const vector<double> &vec)
{
int n = vec.size();
double sum = 0.0;
for(int i = 0; i < n; ++i)
{
sum += vec[i] * vec[i];
}
return sqrt(sum);
}
double cosine_distance(const vector<double> &base, const vector<double> &target)
{
int n = base.size();
double tmp = 0.0;
for(int i = 0; i < n; ++i)
{
tmp += base[i] * target[i];
}
double simility = tmp / (get_mold(base) * get_mold(target));
return simility;
}
class Recognizer
{
public:
Recognizer(int num_threads) :
num_threads_(num_threads),
is_run_calculate_thread_(true),
is_doing_recognize(false),
result_count(0)
{
recognize_result = std::vector<RecognizeResult>(num_threads);
this->load_feature();
this->init_threads();
}
~Recognizer()
{
is_run_calculate_thread_ = false;
cv_.notify_all();
for(std::thread &th : threads_)
th.join();
}
int do_recognize(const vector<double> &feature);
private:
class CigaretteItem
{
public:
int cigarette_id_;
std::string cigarette_name_;
std::vector<double> feature_;
CigaretteItem(int cigarette_id, std::string cigarette_name, const std::vector<double> &cigarette_feature)
{
cigarette_id_ = cigarette_id;
cigarette_name_ = cigarette_name;
feature_ = std::vector<double>(cigarette_feature.size());
for(int i = 0; i < cigarette_feature.size(); i++)
{
feature_[i] = cigarette_feature[i];
}
}
};
class RecognizeResult
{
public:
int cigarette_id_;
std::string cigarette_name_;
double score_;
};
private:
int num_threads_;
bool is_run_calculate_thread_;
bool is_doing_recognize;
std::vector<CigaretteItem> ciagarette_list_;
std::vector<double> target_feature_;
std::mutex cv_mtx_;
std::condition_variable cv_;
std::vector<RecognizeResult> recognize_result;
std::vector<std::thread> threads_;
int result_count;
std::mutex result_count_mtx_;
std::mutex result_cv_mtx_;
std::condition_variable result_cv_;
private:
Recognizer(const Recognizer&) = delete;
Recognizer& operator=(const Recognizer&) = delete;
void load_feature();
void init_threads();
void calculate_most_similarity(const int thread_id, const int start_index, const int end_index);
};
void Recognizer::load_feature()
{
for(int i = 0; i < 3000; i++)
{
vector<double> fea = vector<double>(4096);
for(int i = 0; i < 4096; ++i)
fea[i] = (double)(rand() % 998 + 1) / 1000.00;
ciagarette_list_.emplace_back(i+1, "cigarette", fea);
}
}
void Recognizer::init_threads()
{
for(int i = 0; i < num_threads_; i++)
{
int step = this->ciagarette_list_.size() / this->num_threads_;
int start_index = i * step;
int end_index = (i+1) * step;
if(i == num_threads_ - 1){
end_index = ciagarette_list_.size();
}
std::cout << "thread" << i << " starts at " << start_index << "; ends at " << end_index << std::endl;
threads_.emplace_back(&Recognizer::calculate_most_similarity, this, i, start_index, end_index);
}
}
void Recognizer::calculate_most_similarity(const int thread_id, const int start_index, const int end_index)
{
while(is_run_calculate_thread_)
{
{
std::unique_lock<std::mutex> lock(cv_mtx_);
cv_.wait(lock);
}
//cout << "thread" << thread_id << " is running" << endl;
double max_score = -1.00;
int max_score_index = -1;
for(int i = start_index; i < end_index; ++i){
double score = cosine_distance(ciagarette_list_[i].feature_, target_feature_);
if(score > max_score)
{
max_score = score;
max_score_index = i;
}
}
recognize_result[thread_id].cigarette_id_ = ciagarette_list_[max_score_index].cigarette_id_;
recognize_result[thread_id].cigarette_name_ = ciagarette_list_[max_score_index].cigarette_name_;
recognize_result[thread_id].score_ = max_score;
{
std::unique_lock<std::mutex> lock(result_count_mtx_);
result_count += 1;
}
result_cv_.notify_one();
//std::cout << "thread" << thread_id << " finish one task" << endl;
}
//std::cout << "thread" << thread_id << " finished." << std::endl;
}
int Recognizer::do_recognize(const vector<double> &feature)
{
if(is_doing_recognize)
return -1;
is_doing_recognize = true;
this->target_feature_ = feature;
//cout << "cv_.notify_all()" << endl;
cv_.notify_all();
std::unique_lock<std::mutex> lock(result_cv_mtx_);
result_cv_.wait(lock, [this](){return this->num_threads_ == this->result_count;});
//std::cout << "all threads finish computing similarity" << endl;
int max_score_cigarette_id = -1;
std::string max_score_cigarette_name = "";
double max_score = -1.0;
for(int i = 0; i < num_threads_; ++i)
{
if(recognize_result[i].score_ > max_score)
{
max_score_cigarette_id = recognize_result[i].cigarette_id_;
max_score_cigarette_name = recognize_result[i].cigarette_name_;
max_score = recognize_result[i].score_;
}
}
//cout << "cigarette_id=" << max_score_cigarette_id << ", cigarette_name=" << max_score_cigarette_name << ", score=" << max_score << endl;
this->result_count = 0;
is_doing_recognize = false;
return 0;
}
int main(void)
{
Recognizer recognizer{1024};
std::this_thread::sleep_for(std::chrono::seconds(1));
const int loops = 400;
auto start_time = system_clock::now();
for(int i = 0; i < loops; i++)
{
//cout << endl;
std::vector<double> target_feature = std::vector<double>(4096);
for(int i = 0; i < 4096; ++i)
{
//target_feature[i] = (double)(rand() % 998 + 1) / 1000.000;
target_feature[i] = (double)(i % 1000 + 1) / 1000.00;
}
recognizer.do_recognize(target_feature);
//if((i+1) % 20 == 0)
// cout << "i=" << i << endl;
}
auto end_time = system_clock::now();
auto duration = duration_cast<milliseconds>(end_time - start_time);
cout << "eplased_time:" << duration.count() << "ms" << endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
相关文章:
C++11使用多线程(线程池)计算相似度实现性能优化
需求:图像识别中,注册的样本多了会影响计算速度,成为性能瓶颈,其中一个优化方法就是使用多线程。例如,注册了了3000个特征,每个特征4096个float。可以把3000个特征比对放到4个线程中进行计算,然…...
【测绘程序设计】——平面坐标转换
测绘工程中经常遇到平面坐标转换——比如,北京54(或西安80)平面坐标转换成CGCS2000平面坐标、工程独立坐标系平面坐标转换成CGCS2000平面坐标等,常用转换模型包括:①三参数法(2平移+1旋转);②四参数法(赫尔默特法,2平移+1旋转+1尺度);③六参数法(仿射变换法,2平移…...
五子棋的设计与实现
术:Java等摘要:五子棋是一种两人对弈的纯策略型棋类游戏,非常容易上手,老少皆宜。为了更好的推广五子棋,研究简单的人工智能方式,运用Java开发五子棋游戏。主要包含了人机对战,棋盘初始化&#…...
大数据项目软硬件选择
目录 一.技术选型 二.系统数据流程设计 三.框架版本选型 如何选择Apache/CDH/HDP版本...
redis数据结构的适用场景分析
1、String 类型的内存空间消耗问题,以及选择节省内存开销的数据类型的解决方案。 为什么 String 类型内存开销大? 图片 ID 和图片存储对象 ID 都是 10 位数,我们可以用两个 8 字节的 Long 类型表示这两个 ID。因为 8 字节的 Long 类型最大可以…...
同步、异步、全双工、半双工的区别
1、通讯 1.1 并行通讯 定义:一条信息的各位数据被同时传送的通讯方式称为并行通讯; 特点: 各个数据位同时发送,传送速度快、效率高,但有多少数据位就需要多少根数据线,因此传送成本高,并且只…...
ClickHouse 与 Amazon S3 结合?一起来探索其中奥秘
目录ClickHouse 简介ClickHouse 与对象存储ClickHouse 与 S3 结合的三种方法示例参考架构小结参考资料ClickHouse 简介ClickHouse 是一种快速的、开源的、用于联机分析(OLAP)的列式数据库管理系统(DBMS),由俄罗斯的Yan…...
【Spark分布式内存计算框架——Structured Streaming】1. Structured Streaming 概述
前言 Apache Spark在2016年的时候启动了Structured Streaming项目,一个基于Spark SQL的全新流计算引擎Structured Streaming,让用户像编写批处理程序一样简单地编写高性能的流处理程序。 Structured Streaming并不是对Spark Streaming的简单改进…...
【Windows】【Linux】---- Java证书导入
问题: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 无法找到请求目标的有效证书路径 一、Windows—java证书导入 1、下载证书到本地(以下…...
【Linux学习】菜鸟入门——gcc与g++简要使用
一、gcc/g gcc/g是编译器,gcc是GCC(GUN Compiler Collection,GUN编译器集合)中的C编译器;g是GCC中的C编译器。使用g编译文件时会自动链接STL标准库,而gcc不会自动链接STL标准库。下面简单介绍一下Linux环境下(Windows差…...
Cadence Allegro 导出Bill of Material Report详解
⏪《上一篇》 🏡《总目录》 ⏩《下一篇》 目录 1,概述2,Assigned Functions Report作用3,Assigned Functions Report示例4,Assigned Functions Report导出方法4.1,方法14.2,方法2B站关注“硬小二”浏览更多演示视频...
localStorage线上问题的思考
一、背景: localStorage作为HTML5 Web Storage的API之一,使用标准的键值对(Key-Value,简称KV)数据类型主要作用是本地存储。本地存储是指将数据按照键值对的方式保存在客户端计算机中,直到用户或者脚本主动清除数据&a…...
什么是DNS域名解析
什么是DNS域名解析?因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,得到该主机名对应的IP地址的过程叫做域名解析。正向解析:…...
Cadence Allegro 导出Assigned Functions Report详解
⏪《上一篇》 🏡《总目录》 ⏩《下一篇》 目录 1,概述2,Assigned Functions Report作用3,Assigned Functions Report示例4,Assigned Functions Report导出方法4.1,方法14.2,方法2B站关注“硬小二”浏览更多演示视频...
Python中Opencv和PIL.Image读取图片的差异对比
近日,在进行深度学习进行推理的时候,发现不管怎么样都得不出正确的结果,再仔细和正确的代码进行对比了后发现原来是Python中不同的库读取的图片数组是有差异的。 image np.array(Image.open(image_file).convert(RGB)) image cv2.imread(…...
win10 WSL2 使用Ubuntu配置与安装教程
Win10 22H2ubuntu 22.04ROS2 文章目录一、什么是WSL2二、Win10 系统配置2.1 更新Windows版本2.2 Win10系统启用两个功能2.3 Win10开启BIOS/CPU开启虚拟化(VT)(很关键)2.4 下载并安装wsl_update_x64.msi2.5 PowerShell安装组件三、PowerShell安装Ubuntu3.…...
LeetCode每日一题(28. Find the Index of the First Occurrence in a String)
Given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. Example 1: Input: haystack “sadbutsad”, needle “sad” Output: 0 Explanation: “sad” occurs at index 0 and…...
Android 圆弧形 SeekBar
效果预览package com.gcssloop.widget;import android.annotation.SuppressLint;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graph…...
java 字典
java 字典 数据结构总览 Map Map 描述的是一种映射关系,一个 key 对应一个 value,可以添加,删除,修改和获取 key/value,util 提供了多种 Map HashMap: hash 表实现的 map,插入删除查找性能都是 O(1)&…...
【企业服务器LNMP环境搭建】mysql安装
MySQL安装步骤: 1、相关说明 1.1、编译参数的说明 -DCMAKE_INSTALL_PREFIX安装到的软件目录-DMYSQL_DATADIR数据文件存储的路径-DSYSCONFDIR配置文件路径 (my.cnf)-DENABLED_LOCAL_INFILE1使用localmysql客户端的配置-DWITH_PARTITION_STORAGE_ENGINE使mysql支持…...
机器学习在资产管理中的应用:从数据到投资组合的端到端框架
1. 项目概述:当机器学习遇见资产管理如果你在资产管理行业待过,或者对量化投资感兴趣,那你肯定不止一次想过:那些复杂的市场数据、财报、新闻,能不能让机器来帮我们分析,甚至做出决策?firmai/ma…...
GPU资源利用率监测与优化实战指南
1. GPU资源利用率监测基础解析在超算中心和AI训练集群中,GPU资源利用率(GPU_UTIL)是衡量计算效率的核心指标。这个看似简单的百分比背后,实际上反映了GPU内部多个执行单元的综合活跃状态。通过NVIDIA的DCGM(Data Cente…...
KG与LLM:大模型时代的智能规划
这些文章给出的“推荐思路”可以浓缩成一句话 先用 Planner 产出 subgoal dependency acceptance criteria。再让 Router 判断每个子任务该走 向量RAG、KG、数据库还是工具。对需要关系、多跳、时序、因果的问题,用 KG / event graph 做结构化检索,而…...
别再死记硬背Paxos了!用“希腊城邦法案”的故事,5分钟搞懂分布式共识核心
从古希腊议会到区块链:用人类文明史解锁分布式共识的本质 想象一下公元前5世纪的雅典城邦,五百人议会正在为是否建造新战舰争论不休。议员们需要达成一致,但有人中途离席、有人突然反对、甚至传令官可能送错消息——这像极了今天分布式系统中…...
Claude智能优化器:提升AI应用开发效率的提示词工程中间件
1. 项目概述与核心价值 最近在折腾AI应用开发,特别是围绕Claude API做各种自动化工具时,发现一个挺普遍的问题:直接调用Claude API返回的答案,有时候会显得有点“啰嗦”或者“不够聚焦”。比如你让它写一段代码,它可能…...
从零构建ESP32+ILI9341触摸屏LVGL交互界面实战
1. 硬件选型与连接指南 第一次接触ESP32和ILI9341触摸屏时,最让我头疼的就是如何正确选择硬件并完成连接。经过多次实践,我总结出一套适合新手的硬件配置方案。ESP32开发板建议选择带有USB转串口芯片的版本,比如ESP32-DevKitC,这样…...
美国通信业去监管趋势下的技术生态变革与产业应对策略
1. 从“去监管”信号看美国通信业格局重塑 2017年初,当阿吉特派伊(Ajit Pai)正式接任美国联邦通信委员会(FCC)主席时,他的一项早期举措——为广播公司和有线电视运营商削减文书工作规定——几乎在所有人的预…...
从理论到实践:LQR在二自由度云台控制系统中的参数整定与仿真验证
1. LQR控制器的工程实践意义 二自由度云台在工业自动化、智能监控等领域应用广泛,但传统PID控制往往难以兼顾快速响应和稳定性的双重需求。LQR(线性二次型调节器)作为现代控制理论中的经典方法,通过优化目标函数实现对系统的精确控…...
网络安全入门:2026年转行网络安全完整路径图
网络安全入门:2026 年转行网络安全完整路径图 导语:2026 年,网络安全人才缺口达 150 万,平均薪资较传统 IT 岗位高出 30%。但 70% 的转行者因路径不清晰而失败。本文详解 2026 年转行网络安全的完整路径:学习路线、证…...
农文旅融合实践:六亩半如何以草莓采摘+植物染色激活乌鲁木齐亲子游市场
一、行业背景随着文旅产业复苏和乡村振兴战略深入推进,乌鲁木齐及周边地区的农文旅融合项目迎来新的发展机遇。根据相关行业观察,融合农业采摘与非遗文化体验的"农文旅"模式正成为新趋势,为城市居民提供了差异化的周末游选择。五月…...
