博主简介:CSDN博客专家、CSDN平台优质创作者,获得2024年博客之星荣誉证书,高级开发工程师,数学专业,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。
技术合作请加本人wx(注明来自csdn):xt20160813
在现代软件开发中,多核处理器的普及使得并发编程成为提升应用性能的关键手段。C++ 作为一门高性能语言,提供了丰富的并发支持,但不当的使用同样可能导致性能瓶颈甚至程序错误。本文将深入探讨 C++ 并发性能优化的策略和实践,通过详细的示例,帮助开发者在项目中有效识别并解决并发带来的性能问题。
并发(Concurrency)指的是在同一时间段内,多个任务交替执行,以提高系统的吞吐量和资源利用率。而并行(Parallelism)则是指在同一时刻,多个任务同时执行,以缩短任务完成时间。虽然两者密切相关,但并发更强调任务的管理与调度,并行则强调同时执行。
自 C++11 起,C++ 标准库引入了一系列并发支持,包括线程(std::thread
)、互斥锁(std::mutex
)、条件变量(std::condition_variable
)等。此外,C++17 引入了并行算法,C++20 更进一步增强了协程(Coroutines)等特性。这些工具为开发者提供了构建高性能并发应用的基础。
在优化并发程序之前,首先需要识别性能瓶颈。以下是常见的并发性能问题和识别方法。
使用性能分析工具可以有效发现并发程序中的性能瓶颈。以下是几种常用的工具:
示例:使用 Perf 进行分析
编译程序时开启调试信息和优化选项
g++ -O2 -g -o my_app my_app.cpp -pthread
运行 Perf 进行性能分析
perf record -g ./my_app
生成报告
perf report
通过分析报告,可以识别出程序中消耗 CPU 时间较多的函数和代码段,进而定位性能瓶颈。
针对上述常见的并发性能问题,以下是几种有效的优化策略。
锁粒度指的是锁定的资源范围。锁粒度越细,允许的并发度越高,但管理锁的开销也可能增加。
优化方法:
示例:细化锁粒度
#include
#include
#include
class ThreadSafeVector {
public:
void push_back(int value) {
std::lock_guard<std::mutex> lock(mutex_);
data_.push_back(value);
}
int get(size_t index) const {
std::lock_guard<std::mutex> lock(mutex_);
return data_.at(index);
}
private:
std::vector<int> data_;
mutable std::mutex mutex_;
};
优化:
将整个容器的锁拆分为多个段锁,每个段锁保护容器的一部分。
#include
#include
#include
#include
class SegmentedThreadSafeVector {
public:
void push_back(int value) {
std::unique_lock<std::mutex> lock(mutex_);
data_.push_back(value);
}
int get(size_t index) const {
std::unique_lock<std::mutex> lock(mutex_);
return data_.at(index);
}
private:
std::vector<int> data_;
mutable std::mutex mutex_;
};
尽管在这个简单示例中锁粒度优化效果有限,但在复杂数据结构中,细化锁粒度可以显著提升并发性能。
无锁编程通过原子操作和无锁数据结构,避免使用互斥锁,从而减少锁竞争和上下文切换的开销。
优化方法:
std::atomic
提供的原子操作,确保线程安全的同时避免锁的开销。示例:使用原子变量
#include
#include
#include
#include
std::atomic<int> counter(0);
void increment(int num_iterations) {
for(int i = 0; i < num_iterations; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
const int num_threads = 4;
const int iterations = 1000000;
std::vector<std::thread> threads;
for(int i = 0; i < num_threads; ++i) {
threads.emplace_back(increment, iterations);
}
for(auto& t : threads) {
t.join();
}
std::cout << "Final counter value: " << counter.load() << std::endl;
return 0;
}
说明:
通过使用 std::atomic
,多个线程可以安全地对 counter
进行递增操作,无需互斥锁,显著提升性能。
频繁创建和销毁线程会带来较大的开销。使用线程池可以重用线程资源,减少线程管理的开销,提高任务处理效率。
优化方法:
示例:简单线程池实现
#include
#include
#include
#include
#include
#include
#include
#include
class ThreadPool {
public:
ThreadPool(size_t num_threads);
~ThreadPool();
// 提交任务
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
private:
// 工作者线程
std::vector<std::thread> workers_;
// 任务队列
std::queue<std::function<void()>> tasks_;
// 同步
std::mutex queue_mutex_;
std::condition_variable condition_;
bool stop_;
};
// 构造函数
ThreadPool::ThreadPool(size_t num_threads) : stop_(false) {
for(size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this]() {
while(true) {
std::function<void()> task;
{ // 获取任务
std::unique_lock<std::mutex> lock(this->queue_mutex_);
this->condition_.wait(lock,
[this]() { return this->stop_ || !this->tasks_.empty(); });
if(this->stop_ && this->tasks_.empty())
return;
task = std::move(this->tasks_.front());
this->tasks_.pop();
}
// 执行任务
task();
}
});
}
}
// 析构函数
ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
stop_ = true;
}
condition_.notify_all();
for(std::thread &worker: workers_)
worker.join();
}
// 提交任务
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
// 不允许在停止线程池后提交任务
if(stop_)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks_.emplace([task]() { (*task)(); });
}
condition_.notify_one();
return res;
}
// 使用示例
int main() {
ThreadPool pool(4);
std::vector<std::future<int>> results;
// 提交任务
for(int i = 0; i < 8; ++i) {
results.emplace_back(
pool.enqueue([i]() -> int {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return i*i;
})
);
}
// 获取结果
for(auto && result: results)
std::cout << result.get() << ' ';
std::cout << std::endl;
return 0;
}
说明:
通过线程池,多个任务可以复用固定数量的线程执行,避免了频繁创建和销毁线程的开销,提升了并发性能。
数据局部性指的是数据在内存中的分布对缓存性能的影响。在并发程序中,优化数据的缓存局部性,可以减少缓存未命中率,提高内存访问速度。
优化方法:
示例:避免伪共享
#include
#include
#include
#include
// 伪共享示例
struct SharedData {
std::atomic<int> counter1;
std::atomic<int> counter2;
};
int main() {
SharedData data;
data.counter1 = 0;
data.counter2 = 0;
auto increment1 = [&data]() {
for(int i = 0; i < 1000000; ++i) {
data.counter1.fetch_add(1, std::memory_order_relaxed);
}
};
auto increment2 = [&data]() {
for(int i = 0; i < 1000000; ++i) {
data.counter2.fetch_add(1, std::memory_order_relaxed);
}
};
std::thread t1(increment1);
std::thread t2(increment2);
t1.join();
t2.join();
std::cout << "Counter1: " << data.counter1 << "\nCounter2: " << data.counter2 << std::endl;
return 0;
}
优化:
通过填充无用数据避免 counter1
和 counter2
位于同一缓存行。
#include
#include
#include
#include
// 避免伪共享的结构
struct SharedData {
alignas(64) std::atomic<int> counter1;
alignas(64) std::atomic<int> counter2;
};
int main() {
SharedData data;
data.counter1 = 0;
data.counter2 = 0;
auto increment1 = [&data]() {
for(int i = 0; i < 1000000; ++i) {
data.counter1.fetch_add(1, std::memory_order_relaxed);
}
};
auto increment2 = [&data]() {
for(int i = 0; i < 1000000; ++i) {
data.counter2.fetch_add(1, std::memory_order_relaxed);
}
};
std::thread t1(increment1);
std::thread t2(increment2);
t1.join();
t2.join();
std::cout << "Counter1: " << data.counter1 << "\nCounter2: " << data.counter2 << std::endl;
return 0;
}
说明:
通过使用 alignas(64)
,确保每个计数器位于不同的缓存行,避免多个线程同时访问相邻数据导致的伪共享问题。
竞态条件和死锁不仅会导致程序错误,还会显著影响性能。良好的同步机制设计可以避免这些问题。
优化方法:
示例:避免死锁的锁获取顺序
#include
#include
#include
std::mutex mutex1;
std::mutex mutex2;
void thread_a() {
std::lock_guard<std::mutex> lock1(mutex1);
std::lock_guard<std::mutex> lock2(mutex2);
std::cout << "Thread A acquired both locks\n";
}
void thread_b() {
std::lock_guard<std::mutex> lock1(mutex1);
std::lock_guard<std::mutex> lock2(mutex2);
std::cout << "Thread B acquired both locks\n";
}
int main() {
std::thread t1(thread_a);
std::thread t2(thread_b);
t1.join();
t2.join();
return 0;
}
说明:
通过确保所有线程以相同的顺序获取锁,可以避免死锁的发生。
合理的任务划分和负载均衡可以确保所有线程都能充分利用 CPU 资源,避免某些线程空闲而其他线程过载。
优化方法:
示例:使用线程池进行动态任务调度
在前述线程池示例中,任务被动态分配到空闲线程上,实现了负载均衡。
高效的内存管理和缓存优化可以显著减少内存访问延迟,提升并发程序的整体性能。
优化方法:
示例:使用内存池进行内存管理
#include
#include
#include
template<typename T>
class MemoryPool {
public:
MemoryPool(size_t size = 1024) {
allocate_block(size);
}
~MemoryPool() {
for(auto block : blocks_)
::operator delete[](block);
}
T* allocate() {
if(free_list_.empty()) {
allocate_block(block_size_);
}
T* obj = free_list_.back();
free_list_.pop_back();
return obj;
}
void deallocate(T* obj) {
free_list_.push_back(obj);
}
private:
void allocate_block(size_t size) {
T* new_block = static_cast<T*>(::operator new[](size * sizeof(T)));
blocks_.push_back(new_block);
for(size_t i = 0; i < size; ++i)
free_list_.push_back(new_block + i);
}
std::vector<T*> blocks_;
std::vector<T*> free_list_;
size_t block_size_ = 1024;
};
// 使用示例
struct MyObject {
int data;
// ...
};
int main() {
MemoryPool<MyObject> pool;
// 分配对象
MyObject* obj1 = pool.allocate();
obj1->data = 42;
// 使用对象
std::cout << "Object data: " << obj1->data << std::endl;
// 释放对象
pool.deallocate(obj1);
return 0;
}
说明:
通过内存池管理对象的分配和释放,减少了频繁的堆分配操作,提高了内存管理效率,特别适用于高并发环境下的大量对象创建与销毁。
为了更直观地展示上述优化策略的应用,以下将通过一个高性能并行图像处理的案例,详细说明优化过程。
假设有一个简单的图像处理程序,需要对一幅大图像的每个像素进行亮度调整。
#include
#include
#include
#include
struct Pixel {
unsigned char r, g, b;
};
class Image {
public:
Image(size_t width, size_t height) : width_(width), height_(height), pixels_(width * height) {}
Pixel& at(size_t x, size_t y) { return pixels_[y * width_ + x]; }
size_t width() const { return width_; }
size_t height() const { return height_; }
private:
size_t width_;
size_t height_;
std::vector<Pixel> pixels_;
};
void adjust_brightness(Image& img, size_t start_y, size_t end_y, int brightness) {
for(size_t y = start_y; y < end_y; ++y) {
for(size_t x = 0; x < img.width(); ++x) {
Pixel& p = img.at(x, y);
p.r = std::min(static_cast<int>(p.r) + brightness, 255);
p.g = std::min(static_cast<int>(p.g) + brightness, 255);
p.b = std::min(static_cast<int>(p.b) + brightness, 255);
}
}
}
int main() {
size_t width = 4000;
size_t height = 3000;
Image img(width, height);
// 初始化图像数据(简化)
for(auto& p : img.pixels_) {
p.r = p.g = p.b = 100;
}
int brightness = 50;
size_t num_threads = std::thread::hardware_concurrency();
std::vector<std::thread> threads;
size_t rows_per_thread = height / num_threads;
for(size_t i = 0; i < num_threads; ++i) {
size_t start_y = i * rows_per_thread;
size_t end_y = (i == num_threads - 1) ? height : (i + 1) * rows_per_thread;
threads.emplace_back(adjust_brightness, std::ref(img), start_y, end_y, brightness);
}
for(auto& t : threads) {
t.join();
}
std::cout << "Brightness adjustment completed.\n";
return 0;
}
潜在问题:
针对上述问题,可以进行以下优化:
#include
#include
#include
#include
#include
#include
// 保持 Pixel 和 Image 结构不变
struct Pixel {
unsigned char r, g, b;
};
class Image {
public:
Image(size_t width, size_t height) : width_(width), height_(height), pixels_(width * height) {}
Pixel& at(size_t x, size_t y) { return pixels_[y * width_ + x]; }
size_t width() const { return width_; }
size_t height() const { return height_; }
std::vector<Pixel>& get_pixels() { return pixels_; }
private:
size_t width_;
size_t height_;
std::vector<Pixel> pixels_;
};
// 线程池类(简化)
class ThreadPool {
public:
ThreadPool(size_t num_threads);
~ThreadPool();
template<class F>
auto enqueue(F&& f) -> std::future<void>;
private:
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable condition_;
bool stop_;
};
// 线程池实现
ThreadPool::ThreadPool(size_t num_threads) : stop_(false) {
for(size_t i = 0; i < num_threads; ++i) {
workers_.emplace_back([this]() {
while(true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex_);
this->condition_.wait(lock,
[this]() { return this->stop_ || !this->tasks_.empty(); });
if(this->stop_ && this->tasks_.empty())
return;
task = std::move(this->tasks_.front());
this->tasks_.pop();
}
task();
}
});
}
}
ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
stop_ = true;
}
condition_.notify_all();
for(std::thread &worker: workers_)
worker.join();
}
template<class F>
auto ThreadPool::enqueue(F&& f) -> std::future<void> {
auto task = std::make_shared< std::packaged_task<void()> >(std::forward<F>(f));
std::future<void> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if(stop_)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks_.emplace([task]() { (*task)(); });
}
condition_.notify_one();
return res;
}
// 调整亮度函数
void adjust_brightness(Image& img, size_t start_y, size_t end_y, int brightness) {
for(size_t y = start_y; y < end_y; ++y) {
for(size_t x = 0; x < img.width(); ++x) {
Pixel& p = img.at(x, y);
p.r = std::min(static_cast<int>(p.r) + brightness, 255);
p.g = std::min(static_cast<int>(p.g) + brightness, 255);
p.b = std::min(static_cast<int>(p.b) + brightness, 255);
}
}
}
int main() {
size_t width = 4000;
size_t height = 3000;
Image img(width, height);
// 初始化图像数据(简化)
std::fill(img.get_pixels().begin(), img.get_pixels().end(), Pixel{100, 100, 100});
int brightness = 50;
size_t num_threads = std::thread::hardware_concurrency();
ThreadPool pool(num_threads);
std::vector< std::future<void> > futures;
size_t rows_per_task = height / (num_threads * 4); // 分成更多任务
for(size_t y = 0; y < height; y += rows_per_task) {
size_t end_y = std::min(y + rows_per_task, height);
futures.emplace_back(
pool.enqueue([&img, y, end_y, brightness]() {
adjust_brightness(img, y, end_y, brightness);
})
);
}
// 等待所有任务完成
for(auto &fut : futures)
fut.get();
std::cout << "Brightness adjustment completed.\n";
return 0;
}
优化效果分析:
通过上述优化策略和实战案例,我们可以总结出以下 C++ 并发性能优化的最佳实践:
总结:
C++ 并发编程在提升应用性能方面具有巨大潜力,但同时也带来了复杂性和挑战。通过理解并发基础、识别性能瓶颈、应用有效的优化策略,开发者可以构建高效、稳定的并发应用。最重要的是,持续进行性能分析和优化,确保应用在不同负载和环境下都能表现出色。
C++、并发编程、性能优化、多线程、线程池、无锁编程
本文版权归作者所有,未经允许,请勿转载。