Boost::mutex 比没有互斥锁的程序花费的时间更少
Boost::mutex is taking less time than without mutex for a program
我执行了下面的程序,我创建了 100 个线程并发执行。
请注意这是一个示例程序。我知道下面的程序不需要多线程,但我的目的是测试互斥量。
class ThreadPool{
public:
ThreadPool(int num = 10);
~ThreadPool();
void AssignPool();
void doSometask();
void inc();
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
volatile int p_size;
int pool_sz;
boost::mutex io_mutex;// with boost lock
};
void ThreadPool::AssignPool()
{
std::cout<<std::endl<<"pool_sz="<<pool_sz<<std::endl;
for(int i=0;i<pool_sz;i++)
{
ioService.post(boost::bind(&ThreadPool::doSometask, this));
}
}
void ThreadPool::inc()
{
p_size++;
}
void ThreadPool::doSometask()
{
// boost::mutex::scoped_lock lock(io_mutex);
for(int i=0;i<10000;i++){
inc();
}
}
ThreadPool::ThreadPool(int num):p_size(0)
{
pool_sz = num;
work = new boost::asio::io_service::work(ioService);
for(int i =0;i<num;i++)
{
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService )) ;
}
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
}
int main()
{
ThreadPool p1(100);
p1.AssignPool();
}
案例一:
上面的程序是通过注释 "boost::mutex::scoped_lock lock(io_mutex);" 行即 "no mutex case" 来执行的。
该程序花费的时间是
real 0m1.386s
user 0m0.483s
sys 0m9.937s
案例 2:使用互斥锁:
但是,当我 运行 这个程序带有互斥锁时,即 "boost::mutex::scoped_lock lock(io_mutex);" 行。该程序花费的时间更少。
real 0m0.289s
user 0m0.067s
sys 0m0.230s
根据我对互斥锁的理解,程序应该比没有互斥锁花费更多的时间。这里出了什么问题??
在您的示例中,您将互斥锁锁定在 doSometask()
中,因此始终只有一个线程处于 运行ning 状态,并且它将在屈服于另一项任务之前完成 for 循环。因此,程序 运行 确实是串行的,不会发生缓存脱粒。
如果没有锁,所有线程在获得处理器时间时都会 运行,并且假设处理器的数量明显低于 100,所有级别的高速缓存阈值将在所有级别上进行(如 Bo Persson 在评论中写道),这将增加 运行 时间。
衡量锁定对 运行 时间影响的更好方法是 (a) 运行 的线程数与您的计算机拥有的内核数一样多,以便缓存因上下文而脱粒最小化开关,并且 (b) 将锁放入 ThreadPool::inc()
方法中,以便更频繁地进行同步。
作为奖励,您可以通过将 p_size
声明为 std::atomic<int>
(C++11) 来正确 运行 无锁方法,并查看基于互斥锁的同步与使用原子。
我既不是计算机科学家,也不是 OS 专家。
但是每当我尝试比较两个相似函数的性能时,我 运行 多次执行函数并比较平均值,而不是比较单次执行所花费的时间(我的这种方法是错误的,它对我大部分时间都有效时间。我愿意接受 input/comment 专家的意见)。我的想法是,当我使用 OS 时,资源(主要是处理器)没有完全分配给正在观察的应用程序。它们同时被许多其他进程共享。
我尝试对您的应用程序执行相同的操作,并在执行上述应用程序 1000 次后得到以下结果。
nomutex: 11.97 user | 5.76 system | 0:20.55 elapsed | 86% CPU
withmutex: 30.78 user | 8.78 system | 0:43.67 elapsed | 90% CPU
现在大多数设备都有多核 CPU 所以我在下面使用 link 强制 OS 只使用单核。
https://unix.stackexchange.com/a/23109
希望对您有所帮助。
如评论所述,"one at a time in an orderly fashion" 比每个人都冲进来要好,但不仅如此。最有可能的主要原因是你为每个线程在 doSometask()
中工作的时间对于现代 CPU 来说太少了。
更新您的 doSometask
以完成更多工作,并通过不断访问共享数据使您的线程减少相互冲突的依赖:
#include <iostream>
#include <chrono>
#include <atomic>
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
class ThreadPool
{
public:
ThreadPool(int num = 10, int cycles = 10000);
~ThreadPool();
void inc(volatile int* x);
void AssignPool();
void doSometask(volatile int* x);
void AssignPoolSync();
void doSometaskSync(volatile int* x);
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
std::atomic<int> p_size;
int *xsize;
int pool_sz, cycles;
boost::mutex io_mutex; // with boost lock
};
void ThreadPool::AssignPool()
{
for (int i = 0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometask, this, &xsize[i]));
}
void ThreadPool::AssignPoolSync()
{
for (int i=0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometaskSync, this, &xsize[i]));
}
void ThreadPool::inc(volatile int* x)
{
*x = *x + 1;
}
void ThreadPool::doSometask(volatile int* x)
{
for (int i=0; i<cycles; ++i)
{
inc(x);
if (i & 255 == 0)
p_size++; // access shared data evert 256 cycles
}
}
void ThreadPool::doSometaskSync(volatile int* x)
{
boost::mutex::scoped_lock lock(io_mutex);
doSometask(x);
}
ThreadPool::ThreadPool(int num, int cycles)
{
pool_sz = num;
p_size = 0;
this->cycles = cycles;
xsize = new int[num];
memset(xsize, 0, num * sizeof(int));
work = new boost::asio::io_service::work(ioService);
for (int i=0; i<pool_sz; ++i)
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
delete[] xsize;
}
int main(int argc, const char** argv)
{
const int C = argc>1 ? std::stoi(argv[1]) : 10000; // number of cycles
const int T = argc>2 ? std::stoi(argv[2]) : 100; // number of threads
const int N = argc>3 ? std::stoi(argv[3]) : 50; // number of times to time execution
long long t_min[2] = {0};
for (int i = 0; i<N*2; ++i)
{
auto t0 = std::chrono::high_resolution_clock::now();
{
Sleep(1);
ThreadPool pool(T, C);
if (i&1)
pool.AssignPoolSync();
else
pool.AssignPool();
}
auto t1 = std::chrono::high_resolution_clock::now();
t_min[i&1] = std::min(i>1 ? t_min[i&1] : (t1-t0).count(), (t1-t0).count());
}
printf("timeSync / time: %f\n", (t_min[1] + 0.0) / (t_min[0] + 0.0));
}
使用此测试,您可以更好地模拟实际工作:线程 运行 的作业大多是独立的,有时它们会访问共享数据。您还可以 运行 它使用不同的参数来更改每个线程 运行 的循环周期数和线程数。
这些是我在 4 核 pc 上 运行ning 时得到的样本结果:
test> test.exe 10000 100
timeSync / time: 1.027782
test> test.exe 500000 100
timeSync / time: 3.531433
换句话说,当每个线程只做 10000 个周期时,同步版本几乎和非同步一样快,但我将周期数增加到 500000,同步版本慢了 3.5 倍
我执行了下面的程序,我创建了 100 个线程并发执行。 请注意这是一个示例程序。我知道下面的程序不需要多线程,但我的目的是测试互斥量。
class ThreadPool{
public:
ThreadPool(int num = 10);
~ThreadPool();
void AssignPool();
void doSometask();
void inc();
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
volatile int p_size;
int pool_sz;
boost::mutex io_mutex;// with boost lock
};
void ThreadPool::AssignPool()
{
std::cout<<std::endl<<"pool_sz="<<pool_sz<<std::endl;
for(int i=0;i<pool_sz;i++)
{
ioService.post(boost::bind(&ThreadPool::doSometask, this));
}
}
void ThreadPool::inc()
{
p_size++;
}
void ThreadPool::doSometask()
{
// boost::mutex::scoped_lock lock(io_mutex);
for(int i=0;i<10000;i++){
inc();
}
}
ThreadPool::ThreadPool(int num):p_size(0)
{
pool_sz = num;
work = new boost::asio::io_service::work(ioService);
for(int i =0;i<num;i++)
{
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService )) ;
}
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
}
int main()
{
ThreadPool p1(100);
p1.AssignPool();
}
案例一: 上面的程序是通过注释 "boost::mutex::scoped_lock lock(io_mutex);" 行即 "no mutex case" 来执行的。 该程序花费的时间是
real 0m1.386s
user 0m0.483s
sys 0m9.937s
案例 2:使用互斥锁: 但是,当我 运行 这个程序带有互斥锁时,即 "boost::mutex::scoped_lock lock(io_mutex);" 行。该程序花费的时间更少。
real 0m0.289s
user 0m0.067s
sys 0m0.230s
根据我对互斥锁的理解,程序应该比没有互斥锁花费更多的时间。这里出了什么问题??
在您的示例中,您将互斥锁锁定在 doSometask()
中,因此始终只有一个线程处于 运行ning 状态,并且它将在屈服于另一项任务之前完成 for 循环。因此,程序 运行 确实是串行的,不会发生缓存脱粒。
如果没有锁,所有线程在获得处理器时间时都会 运行,并且假设处理器的数量明显低于 100,所有级别的高速缓存阈值将在所有级别上进行(如 Bo Persson 在评论中写道),这将增加 运行 时间。
衡量锁定对 运行 时间影响的更好方法是 (a) 运行 的线程数与您的计算机拥有的内核数一样多,以便缓存因上下文而脱粒最小化开关,并且 (b) 将锁放入 ThreadPool::inc()
方法中,以便更频繁地进行同步。
作为奖励,您可以通过将 p_size
声明为 std::atomic<int>
(C++11) 来正确 运行 无锁方法,并查看基于互斥锁的同步与使用原子。
我既不是计算机科学家,也不是 OS 专家。 但是每当我尝试比较两个相似函数的性能时,我 运行 多次执行函数并比较平均值,而不是比较单次执行所花费的时间(我的这种方法是错误的,它对我大部分时间都有效时间。我愿意接受 input/comment 专家的意见)。我的想法是,当我使用 OS 时,资源(主要是处理器)没有完全分配给正在观察的应用程序。它们同时被许多其他进程共享。
我尝试对您的应用程序执行相同的操作,并在执行上述应用程序 1000 次后得到以下结果。
nomutex: 11.97 user | 5.76 system | 0:20.55 elapsed | 86% CPU
withmutex: 30.78 user | 8.78 system | 0:43.67 elapsed | 90% CPU
现在大多数设备都有多核 CPU 所以我在下面使用 link 强制 OS 只使用单核。 https://unix.stackexchange.com/a/23109
希望对您有所帮助。
如评论所述,"one at a time in an orderly fashion" 比每个人都冲进来要好,但不仅如此。最有可能的主要原因是你为每个线程在 doSometask()
中工作的时间对于现代 CPU 来说太少了。
更新您的 doSometask
以完成更多工作,并通过不断访问共享数据使您的线程减少相互冲突的依赖:
#include <iostream>
#include <chrono>
#include <atomic>
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
class ThreadPool
{
public:
ThreadPool(int num = 10, int cycles = 10000);
~ThreadPool();
void inc(volatile int* x);
void AssignPool();
void doSometask(volatile int* x);
void AssignPoolSync();
void doSometaskSync(volatile int* x);
private:
boost::asio::io_service ioService;
boost::thread_group threadpool;
boost::asio::io_service::work * work;
std::atomic<int> p_size;
int *xsize;
int pool_sz, cycles;
boost::mutex io_mutex; // with boost lock
};
void ThreadPool::AssignPool()
{
for (int i = 0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometask, this, &xsize[i]));
}
void ThreadPool::AssignPoolSync()
{
for (int i=0; i<pool_sz; ++i)
ioService.post(boost::bind(&ThreadPool::doSometaskSync, this, &xsize[i]));
}
void ThreadPool::inc(volatile int* x)
{
*x = *x + 1;
}
void ThreadPool::doSometask(volatile int* x)
{
for (int i=0; i<cycles; ++i)
{
inc(x);
if (i & 255 == 0)
p_size++; // access shared data evert 256 cycles
}
}
void ThreadPool::doSometaskSync(volatile int* x)
{
boost::mutex::scoped_lock lock(io_mutex);
doSometask(x);
}
ThreadPool::ThreadPool(int num, int cycles)
{
pool_sz = num;
p_size = 0;
this->cycles = cycles;
xsize = new int[num];
memset(xsize, 0, num * sizeof(int));
work = new boost::asio::io_service::work(ioService);
for (int i=0; i<pool_sz; ++i)
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
}
ThreadPool::~ThreadPool()
{
delete work;
ioService.stop();
threadpool.join_all();
delete[] xsize;
}
int main(int argc, const char** argv)
{
const int C = argc>1 ? std::stoi(argv[1]) : 10000; // number of cycles
const int T = argc>2 ? std::stoi(argv[2]) : 100; // number of threads
const int N = argc>3 ? std::stoi(argv[3]) : 50; // number of times to time execution
long long t_min[2] = {0};
for (int i = 0; i<N*2; ++i)
{
auto t0 = std::chrono::high_resolution_clock::now();
{
Sleep(1);
ThreadPool pool(T, C);
if (i&1)
pool.AssignPoolSync();
else
pool.AssignPool();
}
auto t1 = std::chrono::high_resolution_clock::now();
t_min[i&1] = std::min(i>1 ? t_min[i&1] : (t1-t0).count(), (t1-t0).count());
}
printf("timeSync / time: %f\n", (t_min[1] + 0.0) / (t_min[0] + 0.0));
}
使用此测试,您可以更好地模拟实际工作:线程 运行 的作业大多是独立的,有时它们会访问共享数据。您还可以 运行 它使用不同的参数来更改每个线程 运行 的循环周期数和线程数。
这些是我在 4 核 pc 上 运行ning 时得到的样本结果:
test> test.exe 10000 100
timeSync / time: 1.027782
test> test.exe 500000 100
timeSync / time: 3.531433
换句话说,当每个线程只做 10000 个周期时,同步版本几乎和非同步一样快,但我将周期数增加到 500000,同步版本慢了 3.5 倍