Std:: mutex по сравнению с win32 CRITICAL_SECTION
как работает std::mutex
по сравнению с CRITICAL_SECTION
? это на уровне?
Мне нужен легкий объект синхронизации (не обязательно должен быть объектом interprocess) есть ли какой-либо класс STL, который близок к CRITICAL_SECTION
, кроме std::mutex
?
Ответы
Ответ 1
Пожалуйста, просмотрите мои обновления в конце ответа, ситуация резко изменилась с момента появления Visual Studio 2015. Ниже приведен оригинальный ответ.
Я сделал очень простой тест, и, согласно моим измерениям, std::mutex
примерно в 50-70 раз медленнее, чем CRITICAL_SECTION
.
std::mutex: 18140574us
CRITICAL_SECTION: 296874us
Изменить: после нескольких тестов оказалось, что это зависит от количества потоков (перегрузки) и количества ядер процессора. Как правило, std::mutex
работает медленнее, но насколько это зависит от использования. Ниже приведены обновленные результаты тестирования (протестированы на MacBook Pro с Core i5-4258U, Windows 10, Bootcamp):
Iterations: 1000000
Thread count: 1
std::mutex: 78132us
CRITICAL_SECTION: 31252us
Thread count: 2
std::mutex: 687538us
CRITICAL_SECTION: 140648us
Thread count: 4
std::mutex: 1031277us
CRITICAL_SECTION: 703180us
Thread count: 8
std::mutex: 86779418us
CRITICAL_SECTION: 1634123us
Thread count: 16
std::mutex: 172916124us
CRITICAL_SECTION: 3390895us
Ниже приведен код, создавший этот вывод. Скомпилирован с Visual Studio 2012, настройки проекта по умолчанию, конфигурация выпуска Win32. Обратите внимание, что этот тест может быть не совсем корректным, но он заставил меня подумать дважды, прежде чем переключать мой код с CRITICAL_SECTION
на std::mutex
.
#include "stdafx.h"
#include <Windows.h>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>
#include <iostream>
const int g_cRepeatCount = 1000000;
const int g_cThreadCount = 16;
double g_shmem = 8;
std::mutex g_mutex;
CRITICAL_SECTION g_critSec;
void sharedFunc( int i )
{
if ( i % 2 == 0 )
g_shmem = sqrt(g_shmem);
else
g_shmem *= g_shmem;
}
void threadFuncCritSec() {
for ( int i = 0; i < g_cRepeatCount; ++i ) {
EnterCriticalSection( &g_critSec );
sharedFunc(i);
LeaveCriticalSection( &g_critSec );
}
}
void threadFuncMutex() {
for ( int i = 0; i < g_cRepeatCount; ++i ) {
g_mutex.lock();
sharedFunc(i);
g_mutex.unlock();
}
}
void testRound(int threadCount)
{
std::vector<std::thread> threads;
auto startMutex = std::chrono::high_resolution_clock::now();
for (int i = 0; i<threadCount; ++i)
threads.push_back(std::thread( threadFuncMutex ));
for ( std::thread& thd : threads )
thd.join();
auto endMutex = std::chrono::high_resolution_clock::now();
std::cout << "std::mutex: ";
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(endMutex - startMutex).count();
std::cout << "us \n\r";
threads.clear();
auto startCritSec = std::chrono::high_resolution_clock::now();
for (int i = 0; i<threadCount; ++i)
threads.push_back(std::thread( threadFuncCritSec ));
for ( std::thread& thd : threads )
thd.join();
auto endCritSec = std::chrono::high_resolution_clock::now();
std::cout << "CRITICAL_SECTION: ";
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(endCritSec - startCritSec).count();
std::cout << "us \n\r";
}
int _tmain(int argc, _TCHAR* argv[]) {
InitializeCriticalSection( &g_critSec );
std::cout << "Iterations: " << g_cRepeatCount << "\n\r";
for (int i = 1; i <= g_cThreadCount; i = i*2) {
std::cout << "Thread count: " << i << "\n\r";
testRound(i);
Sleep(1000);
}
DeleteCriticalSection( &g_critSec );
// Added 10/27/2017 to try to prevent the compiler to completely
// optimize out the code around g_shmem if it wouldn't be used anywhere.
std::cout << "Shared variable value: " << g_shmem << std::endl;
getchar();
return 0;
}
Обновление 10/27/2017 (1):
Некоторые ответы говорят о том, что это не реалистичный тест или не представляет собой сценарий "реального мира". Это правда, этот тест пытается измерить накладные расходы std::mutex
, и он не пытается доказать, что разница невелика для 99% приложений.
Обновление 10/27/2017 (2):
Похоже, ситуация изменилась в пользу std::mutex
с Visual Studio 2015 (VC140). Я использовал VS2017 IDE, точно такой же код, как и выше, конфигурацию релиза x64, отключен режим оптимизации, и я просто переключил "Набор инструментов для платформы" для каждого теста. Результаты очень удивительны, и мне очень любопытно, что повешено в VC140.
![Производительность с 8 потоками, по платформе]()
Ответ 2
Тест от waldez здесь не реалистичен, он в основном имитирует 100% -ную конкуренцию. В общем, это именно то, чего вы не хотите в многопоточном коде. Ниже приведен модифицированный тест, который выполняет некоторые общие вычисления. Результаты, полученные с этим кодом, различны:
Tasks: 160000
Thread count: 1
std::mutex: 12096ms
CRITICAL_SECTION: 12060ms
Thread count: 2
std::mutex: 5206ms
CRITICAL_SECTION: 5110ms
Thread count: 4
std::mutex: 2643ms
CRITICAL_SECTION: 2625ms
Thread count: 8
std::mutex: 1632ms
CRITICAL_SECTION: 1702ms
Thread count: 12
std::mutex: 1227ms
CRITICAL_SECTION: 1244ms
Здесь вы можете видеть, что для меня (используя VS2013) цифры очень близки между std:: mutex и CRITICAL_SECTION. Обратите внимание, что этот код выполняет фиксированное количество задач (160 000), поэтому производительность улучшается в целом с большим количеством потоков. У меня здесь 12 ядер, поэтому я остановился в 12 лет.
Я не говорю, что это правильно или неправильно по сравнению с другим тестом, но он подчеркивает, что проблемы с синхронизацией обычно являются специфическими для домена.
#include "stdafx.h"
#include <Windows.h>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>
#include <iostream>
const int tastCount = 160000;
int numThreads;
const int MAX_THREADS = 16;
double g_shmem = 8;
std::mutex g_mutex;
CRITICAL_SECTION g_critSec;
void sharedFunc(int i, double &data)
{
for (int j = 0; j < 100; j++)
{
if (j % 2 == 0)
data = sqrt(data);
else
data *= data;
}
}
void threadFuncCritSec() {
double lMem = 8;
int iterations = tastCount / numThreads;
for (int i = 0; i < iterations; ++i) {
for (int j = 0; j < 100; j++)
sharedFunc(j, lMem);
EnterCriticalSection(&g_critSec);
sharedFunc(i, g_shmem);
LeaveCriticalSection(&g_critSec);
}
printf("results: %f\n", lMem);
}
void threadFuncMutex() {
double lMem = 8;
int iterations = tastCount / numThreads;
for (int i = 0; i < iterations; ++i) {
for (int j = 0; j < 100; j++)
sharedFunc(j, lMem);
g_mutex.lock();
sharedFunc(i, g_shmem);
g_mutex.unlock();
}
printf("results: %f\n", lMem);
}
void testRound()
{
std::vector<std::thread> threads;
auto startMutex = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numThreads; ++i)
threads.push_back(std::thread(threadFuncMutex));
for (std::thread& thd : threads)
thd.join();
auto endMutex = std::chrono::high_resolution_clock::now();
std::cout << "std::mutex: ";
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(endMutex - startMutex).count();
std::cout << "ms \n\r";
threads.clear();
auto startCritSec = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numThreads; ++i)
threads.push_back(std::thread(threadFuncCritSec));
for (std::thread& thd : threads)
thd.join();
auto endCritSec = std::chrono::high_resolution_clock::now();
std::cout << "CRITICAL_SECTION: ";
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(endCritSec - startCritSec).count();
std::cout << "ms \n\r";
}
int _tmain(int argc, _TCHAR* argv[]) {
InitializeCriticalSection(&g_critSec);
std::cout << "Tasks: " << tastCount << "\n\r";
for (numThreads = 1; numThreads <= MAX_THREADS; numThreads = numThreads * 2) {
if (numThreads == 16)
numThreads = 12;
Sleep(100);
std::cout << "Thread count: " << numThreads << "\n\r";
testRound();
}
DeleteCriticalSection(&g_critSec);
return 0;
}
Ответ 3
Я использую Visual Studio 2013.
Мои результаты в однопоточном использовании выглядят похожими на результаты waldez:
1 миллион вызовов блокировки/разблокировки:
CRITICAL_SECTION: 19 ms
std::mutex: 48 ms
std::recursive_mutex: 48 ms
Причина, по которой Microsoft изменила реализацию, - совместимость с С++ 11.
С++ 11 имеет 4 вида мьютексов в пространстве имен std:
- станд:: мьютекс
- станд:: timed_mutex
- станд:: recursive_mutex
- станд:: recursive_timed_mutex
Microsoft std:: mutex и все другие мьютексы - это обертки вокруг критического раздела:
struct _Mtx_internal_imp_t
{ /* Win32 mutex */
int type; // here MS keeps particular mutex type
Concurrency::critical_section cs;
long thread_id;
int count;
};
Как мне, std:: recursive_mutex должен полностью соответствовать критическому разделу. Таким образом, Microsoft должна оптимизировать свою реализацию, чтобы уменьшить количество процессоров и памяти.
Ответ 4
Я искал здесь тесты pthread vs critical section, так как мой результат оказался отличным от ответа waldez в отношении темы, я подумал, что было бы интересно поделиться.
Код - тот, который используется @waldez, изменен для добавления pthreads к сравнению, скомпилирован с GCC и без оптимизации. Мой процессор - AMD A8-3530MX.
Windows 7 Home Edition:
>a.exe
Iterations: 1000000
Thread count: 1
std::mutex: 46800us
CRITICAL_SECTION: 31200us
pthreads: 31200us
Thread count: 2
std::mutex: 171600us
CRITICAL_SECTION: 218400us
pthreads: 124800us
Thread count: 4
std::mutex: 327600us
CRITICAL_SECTION: 374400us
pthreads: 249600us
Thread count: 8
std::mutex: 967201us
CRITICAL_SECTION: 748801us
pthreads: 717601us
Thread count: 16
std::mutex: 2745604us
CRITICAL_SECTION: 1497602us
pthreads: 1903203us
Как вы можете видеть, разница изменяется в пределах статистической ошибки - иногда std:: mutex быстрее, иногда это не так. Что важно, я не вижу такой большой разницы, как исходный ответ.
Я думаю, может быть, причина в том, что когда ответ был отправлен, компилятор MSVC не был хорош с более новыми стандартами и отметил, что исходный ответ использовал версию с 2012 года.
Кроме того, из любопытства, тот же двоичный файл под Wine on Archlinux:
$ wine a.exe
fixme:winediag:start_process Wine Staging 2.19 is a testing version containing experimental patches.
fixme:winediag:start_process Please mention your exact version when filing bug reports on winehq.org.
Iterations: 1000000
Thread count: 1
std::mutex: 53810us
CRITICAL_SECTION: 95165us
pthreads: 62316us
Thread count: 2
std::mutex: 604418us
CRITICAL_SECTION: 1192601us
pthreads: 688960us
Thread count: 4
std::mutex: 779817us
CRITICAL_SECTION: 2476287us
pthreads: 818022us
Thread count: 8
std::mutex: 1806607us
CRITICAL_SECTION: 7246986us
pthreads: 809566us
Thread count: 16
std::mutex: 2987472us
CRITICAL_SECTION: 14740350us
pthreads: 1453991us
Код waldez с моими изменениями:
#include <math.h>
#include <windows.h>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>
#include <iostream>
#include <pthread.h>
const int g_cRepeatCount = 1000000;
const int g_cThreadCount = 16;
double g_shmem = 8;
std::mutex g_mutex;
CRITICAL_SECTION g_critSec;
pthread_mutex_t pt_mutex;
void sharedFunc( int i )
{
if ( i % 2 == 0 )
g_shmem = sqrt(g_shmem);
else
g_shmem *= g_shmem;
}
void threadFuncCritSec() {
for ( int i = 0; i < g_cRepeatCount; ++i ) {
EnterCriticalSection( &g_critSec );
sharedFunc(i);
LeaveCriticalSection( &g_critSec );
}
}
void threadFuncMutex() {
for ( int i = 0; i < g_cRepeatCount; ++i ) {
g_mutex.lock();
sharedFunc(i);
g_mutex.unlock();
}
}
void threadFuncPTMutex() {
for ( int i = 0; i < g_cRepeatCount; ++i ) {
pthread_mutex_lock(&pt_mutex);
sharedFunc(i);
pthread_mutex_unlock(&pt_mutex);
}
}
void testRound(int threadCount)
{
std::vector<std::thread> threads;
auto startMutex = std::chrono::high_resolution_clock::now();
for (int i = 0; i<threadCount; ++i)
threads.push_back(std::thread( threadFuncMutex ));
for ( std::thread& thd : threads )
thd.join();
auto endMutex = std::chrono::high_resolution_clock::now();
std::cout << "std::mutex: ";
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(endMutex - startMutex).count();
std::cout << "us \n";
g_shmem = 0;
threads.clear();
auto startCritSec = std::chrono::high_resolution_clock::now();
for (int i = 0; i<threadCount; ++i)
threads.push_back(std::thread( threadFuncCritSec ));
for ( std::thread& thd : threads )
thd.join();
auto endCritSec = std::chrono::high_resolution_clock::now();
std::cout << "CRITICAL_SECTION: ";
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(endCritSec - startCritSec).count();
std::cout << "us \n";
g_shmem = 0;
threads.clear();
auto startPThread = std::chrono::high_resolution_clock::now();
for (int i = 0; i<threadCount; ++i)
threads.push_back(std::thread( threadFuncPTMutex ));
for ( std::thread& thd : threads )
thd.join();
auto endPThread = std::chrono::high_resolution_clock::now();
std::cout << "pthreads: ";
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(endPThread - startPThread).count();
std::cout << "us \n";
g_shmem = 0;
}
int main() {
InitializeCriticalSection( &g_critSec );
pthread_mutex_init(&pt_mutex, 0);
std::cout << "Iterations: " << g_cRepeatCount << "\n";
for (int i = 1; i <= g_cThreadCount; i = i*2) {
std::cout << "Thread count: " << i << "\n";
testRound(i);
Sleep(1000);
}
getchar();
DeleteCriticalSection( &g_critSec );
pthread_mutex_destroy(&pt_mutex);
return 0;
}