1. 概要

本文对比了多线程环境下,ConcurrentQueue(无锁队列)、std::atomic_flagstd::mutex 三种同步机制的性能表现。测试场景包括插入(push)和删除(pop)操作,分别针对小数据项和大数据项进行评测。

测试目标:

  1. 比较 std::mutexstd::atomic_flag 的性能差异。参考文献: mutex 和 spin lock 的区别
  2. 测试多线程环境下的 concurrentqueue 队列性能。
  3. 测试单线程环境下的 readerwriterqueue 性能。
  4. 验证基于 C++ STL 利用 CAS 原子操作封装的无锁 list 的性能。参考文献: 无锁 list

结论

  • 对于小型数据结构(如 std::liststd::deque)的多线程操作,推荐使用 std::atomic_flag 替代 std::mutex,以提高性能。
  • 对于较大的数据结构,std::mutex 在保证线程安全的同时提供了更好的性能。
  • 新的业务代码可以根据需求适当使用无锁队列 ConcurrentQueue,尤其在处理大量数据时表现优秀。

完整测试代码: lock_test

2. 性能测试结果

2.1 测试一:30 线程并发,处理 1 万条 2KB 大小的数据(pushpop

测试结果
测试平台队列类型pushTime (ms)popTime (ms)
Linux-arm1ConcurrentQueue595.476328.856
atomic_flag412.675955.207
std::mutex946.301907.553
Linux-arm2ConcurrentQueue1584.1333.36
atomic_flag576.2091479.5
std::mutex1133.681107.63
Linux-arm3ConcurrentQueue1005.89244.84
atomic_flag355.606402.343
std::mutex597.448739.805
LinuxConcurrentQueue140.89980.4264
atomic_flag136.703136.91
std::mutex231.019213.732
WindowsConcurrentQueue200.119142.239
atomic_flag602.542482.394
std::mutex483.498306.393
结论:
  • Linux 平台
    • push 数据时,atomic_flag 优于 ConcurrentQueuemutex 最差。
    • pop 数据时,ConcurrentQueue 性能最佳。
  • Windows 平台
    • 在所有场景中,ConcurrentQueue 的表现最好。

2.2 测试二:30 线程并发,处理 1 万条 20KB 大小的数据(pushpop

测试结果
测试平台队列类型pushTime (ms)popTime (ms)
Linux-arm1ConcurrentQueue6936.41732.457
atomic_flag4256.777103.61
std::mutex5165.124044.51
Linux-arm2ConcurrentQueue18411.2942.713
atomic_flag5750.0711232.5
std::mutex7236.026573.35
Linux-arm3ConcurrentQueue5399.57247.965
atomic_flag2253.271285.47
std::mutex2625.551588.46
Linux-x86ConcurrentQueue2231.09183.022
atomic_flag1117.93715.601
std::mutex1288.95805.378
WindowsConcurrentQueue8047.595098.22
atomic_flag16736.226468.2
std::mutex21498.350173.6
结论:

随着数据项大小的增大,atomic_flag 的性能不再领先,反而有所下降,而 ConcurrentQueue 的性能持续优异,尤其在插入(push)操作中。

  • Linux 平台
    • push 数据时,atomic_flag 优于 std::mutexConcurrentQueue 最慢。
    • pop 数据时,ConcurrentQueue 性能最好。
  • Windows 平台
    • ConcurrentQueue 的表现始终最优。

3. 硬件配置

  • Linux-x86
CPU: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz (8 cores)
内存: 16GB
操作系统: Linux 5.4.0-47-generic #51~18.04.1-Ubuntu SMP x86_64
  • 测试机器配置 (Linux-arm1)
CPU: 96 cores, ARM architecture
内存: 64GB
操作系统: Linux 4.15.0-71-generic #4 SMP aarch64
  • 测试机器配置 (Linux-arm2)
CPU: 96 cores, ARM architecture
内存: 256GB
操作系统: Linux 4.15.0-71-generic #2 SMP aarch64
  • 测试机器配置 (Linux-arm3)
CPU: Unknown
内存: 256GB
操作系统: Linux 4.15.0-45-generic #48~16.04.1-Ubuntu SMP x86_64
  • Windows 10 x64 测试机配置
CPU: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
内存: 8GB (1867 MHz)

4. 其他说明

测试中曾尝试验证基于 C++ STL 利用 CAS 原子操作封装的无锁 list 的效果,但结果不尽如人意,可能与新版 GCC 优化有关。因此不推荐使用此方案。

5. 参考文献

6. 完整代码

#include <iostream>
#include <list>
#include <deque>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
#include <chrono>
#include "concurrentqueue.h"  // Include ConcurrentQueue library

// 定义小数据结构
struct SmallItem {
  int data[20];
};

// 定义大数据结构
struct LargeItem {
  int data[1024];
};

// 测试参数
const int NUM_THREADS = 30;
const int NUM_ITEMS = 10000;

// 测试函数模板
template<typename T>
void testPerformance(std::atomic_flag& lock, std::list<T>& container, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        while (lock.test_and_set(std::memory_order_acquire));
        container.push_back(item);
        lock.clear(std::memory_order_release);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        while (lock.test_and_set(std::memory_order_acquire));
        if (!container.empty()) {
          container.pop_front();
        }
        lock.clear(std::memory_order_release);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

template<typename T>
void testPerformance(std::mutex& mtx, std::list<T>& container, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        std::lock_guard<std::mutex> lock(mtx);
        container.push_back(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        std::lock_guard<std::mutex> lock(mtx);
        if (!container.empty()) {
          container.pop_front();
        }
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

template<typename T>
void testPerformance(moodycamel::ConcurrentQueue<T>& queue, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        queue.enqueue(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      T item;
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        queue.try_dequeue(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

int main() {
  std::vector<SmallItem> smallData(NUM_ITEMS);
  std::vector<LargeItem> largeData(NUM_ITEMS);

  std::cout << "Testing with std::atomic_flag and SmallItem\n";
  std::list<SmallItem> smallList;
  std::atomic_flag atomicFlag = ATOMIC_FLAG_INIT;
  testPerformance(atomicFlag, smallList, smallData);

  std::cout << "Testing with std::mutex and SmallItem\n";
  std::list<SmallItem> smallListMutex;
  std::mutex mtx;
  testPerformance(mtx, smallListMutex, smallData);

  std::cout << "Testing with ConcurrentQueue and SmallItem\n";
  moodycamel::ConcurrentQueue<SmallItem> smallQueue;
  testPerformance(smallQueue, smallData);

  std::cout << "Testing with std::atomic_flag and LargeItem\n";
  std::list<LargeItem> largeList;
  testPerformance(atomicFlag, largeList, largeData);

  std::cout << "Testing with std::mutex and LargeItem\n";
  std::list<LargeItem> largeListMutex;
  testPerformance(mtx, largeListMutex, largeData);

  std::cout << "Testing with ConcurrentQueue and LargeItem\n";
  moodycamel::ConcurrentQueue<LargeItem> largeQueue;
  testPerformance(largeQueue, largeData);

  return 0;
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐