mirror of
https://github.com/FreeRDP/FreeRDP.git
synced 2024-11-30 21:33:34 +08:00
winpr: greatly improved NtCurrentTeb performance
Use pthread_setspecific/pthread_getspecific to store/retrieve the thread environment block (TEB). Use pthread_once to trigger the creation of that data from within NtCurrentTeb. This allows us to get rid of the process environment block stuff which was only used to provide serialized access to a thread table in order to retrieve the TEB. NtCurrentTeb is currently only as a per-thread storage location for the last error value used by SetLastError and GetLastError. Also made the TestErrorSetLastError CTest a bit more demanding. It makes sure the 4 threads run for at least 2 seconds. Each thread constantly calls SetLastError with a random value and checks if GetLastError returns the same value again. The total amount of these iterations is calculated in order to measure the performance. This change increases the NtCurrentTeb performance by roughly 50% on linux and by several thousand percent (yes) on Mac OS X. Thanks for watching.
This commit is contained in:
parent
cca9774c5e
commit
6f9a8dbc1e
@ -1,45 +1,56 @@
|
||||
/**
|
||||
* CTest for winpr's SetLastError/GetLastError
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
* Copyright 2013 Thinstuff Technologies GmbH
|
||||
* Copyright 2013 Norbert Federa <nfedera@thinstuff.at>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/interlocked.h>
|
||||
|
||||
#include <winpr/error.h>
|
||||
|
||||
static int status = 0;
|
||||
|
||||
static DWORD errors[4] =
|
||||
{
|
||||
ERROR_INVALID_DATA,
|
||||
ERROR_BROKEN_PIPE,
|
||||
ERROR_INVALID_NAME,
|
||||
ERROR_BAD_ARGUMENTS
|
||||
};
|
||||
LONG *pLoopCount = NULL;
|
||||
BOOL bStopTest = FALSE;
|
||||
|
||||
static void* test_error_thread(void* arg)
|
||||
{
|
||||
int id;
|
||||
DWORD error;
|
||||
DWORD dwErrorSet;
|
||||
DWORD dwErrorGet;
|
||||
|
||||
id = (int) (size_t) arg;
|
||||
|
||||
error = errors[id];
|
||||
|
||||
SetLastError(error);
|
||||
|
||||
Sleep(10);
|
||||
|
||||
error = GetLastError();
|
||||
|
||||
if (error != errors[id])
|
||||
{
|
||||
printf("GetLastError() failure (thread %d): Expected: 0x%04X, Actual: 0x%04X\n",
|
||||
id, errors[id], error);
|
||||
|
||||
if (!status)
|
||||
status = -1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
do {
|
||||
dwErrorSet = (DWORD)rand();
|
||||
SetLastError(dwErrorSet);
|
||||
if ((dwErrorGet = GetLastError()) != dwErrorSet)
|
||||
{
|
||||
printf("GetLastError() failure (thread %d): Expected: 0x%04X, Actual: 0x%04X\n",
|
||||
id, dwErrorSet, dwErrorGet);
|
||||
if (!status)
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
InterlockedIncrement(pLoopCount);
|
||||
} while (!status && !bStopTest);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -60,11 +71,18 @@ int TestErrorSetLastError(int argc, char* argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
pLoopCount = _aligned_malloc(sizeof(LONG), sizeof(LONG));
|
||||
*pLoopCount = 0;
|
||||
|
||||
threads[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_error_thread, (void*) (size_t) 0, 0, NULL);
|
||||
threads[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_error_thread, (void*) (size_t) 1, 0, NULL);
|
||||
threads[2] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_error_thread, (void*) (size_t) 2, 0, NULL);
|
||||
threads[3] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) test_error_thread, (void*) (size_t) 3, 0, NULL);
|
||||
|
||||
// let the threads run for at least 2 seconds
|
||||
Sleep(2000);
|
||||
bStopTest = TRUE;
|
||||
|
||||
WaitForSingleObject(threads[0], INFINITE);
|
||||
WaitForSingleObject(threads[1], INFINITE);
|
||||
WaitForSingleObject(threads[2], INFINITE);
|
||||
@ -84,6 +102,14 @@ int TestErrorSetLastError(int argc, char* argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*pLoopCount < 4)
|
||||
{
|
||||
printf("Error: unexpected loop count\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Completed %lu iterations.\n", *pLoopCount);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
* Windows Native System Services
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
* Copyright 2013 Thinstuff Technologies GmbH
|
||||
* Copyright 2013 Norbert Federa <nfedera@thinstuff.at>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -34,170 +36,35 @@
|
||||
|
||||
#include <winpr/crt.h>
|
||||
|
||||
/**
|
||||
* The current implementation of NtCurrentTeb() is not the most efficient
|
||||
* but it's a starting point. Beware of potential performance bottlenecks
|
||||
* caused by multithreaded usage of SetLastError/GetLastError.
|
||||
*/
|
||||
static pthread_once_t _TebOnceControl = PTHREAD_ONCE_INIT;
|
||||
static pthread_key_t _TebKey;
|
||||
|
||||
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static PPEB g_ProcessEnvironmentBlock = NULL;
|
||||
|
||||
static void NtThreadEnvironmentBlockFree(PTEB teb);
|
||||
static void NtProcessEnvironmentBlockFree(PPEB peb);
|
||||
|
||||
static PTEB NtThreadEnvironmentBlockNew()
|
||||
static void _TebDestruct(void *teb)
|
||||
{
|
||||
PTEB teb = NULL;
|
||||
pthread_key_t key;
|
||||
|
||||
teb = (PTEB) malloc(sizeof(TEB));
|
||||
|
||||
if (teb)
|
||||
{
|
||||
ZeroMemory(teb, sizeof(TEB));
|
||||
|
||||
/**
|
||||
* We are not really using the key, but it provides an automatic way
|
||||
* of calling NtThreadEnvironmentBlockFree on thread termination for
|
||||
* the current Thread Environment Block.
|
||||
*/
|
||||
|
||||
pthread_key_create(&key, (void (*)(void*)) NtThreadEnvironmentBlockFree);
|
||||
pthread_setspecific(key, (void*) teb);
|
||||
}
|
||||
|
||||
return teb;
|
||||
}
|
||||
|
||||
static void NtThreadEnvironmentBlockFree(PTEB teb)
|
||||
{
|
||||
DWORD index;
|
||||
PPEB peb = NULL;
|
||||
|
||||
peb = teb->ProcessEnvironmentBlock;
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
for (index = 0; index < peb->ThreadArraySize; index++)
|
||||
{
|
||||
if (peb->Threads[index].ThreadEnvironmentBlock == teb)
|
||||
{
|
||||
peb->Threads[index].ThreadId = 0;
|
||||
peb->Threads[index].ThreadEnvironmentBlock = NULL;
|
||||
peb->ThreadCount--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!peb->ThreadCount)
|
||||
{
|
||||
NtProcessEnvironmentBlockFree(peb);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
free(teb);
|
||||
}
|
||||
|
||||
static PPEB NtProcessEnvironmentBlockNew()
|
||||
static void _TebInitOnce(void)
|
||||
{
|
||||
PPEB peb = NULL;
|
||||
|
||||
peb = (PPEB) malloc(sizeof(PEB));
|
||||
|
||||
if (peb)
|
||||
{
|
||||
ZeroMemory(peb, sizeof(PEB));
|
||||
|
||||
peb->ThreadCount = 0;
|
||||
peb->ThreadArraySize = 64;
|
||||
peb->Threads = (THREAD_BLOCK_ID*) malloc(sizeof(THREAD_BLOCK_ID) * peb->ThreadArraySize);
|
||||
|
||||
if (peb->Threads)
|
||||
{
|
||||
ZeroMemory(peb->Threads, sizeof(THREAD_BLOCK_ID) * peb->ThreadArraySize);
|
||||
}
|
||||
}
|
||||
|
||||
return peb;
|
||||
}
|
||||
|
||||
static void NtProcessEnvironmentBlockFree(PPEB peb)
|
||||
{
|
||||
if (peb)
|
||||
{
|
||||
free(peb->Threads);
|
||||
free(peb);
|
||||
}
|
||||
|
||||
g_ProcessEnvironmentBlock = NULL;
|
||||
}
|
||||
|
||||
PPEB NtCurrentPeb(void)
|
||||
{
|
||||
PPEB peb = NULL;
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
if (!g_ProcessEnvironmentBlock)
|
||||
g_ProcessEnvironmentBlock = NtProcessEnvironmentBlockNew();
|
||||
|
||||
peb = g_ProcessEnvironmentBlock;
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
return peb;
|
||||
pthread_key_create(&_TebKey, _TebDestruct);
|
||||
}
|
||||
|
||||
PTEB NtCurrentTeb(void)
|
||||
{
|
||||
DWORD index;
|
||||
int freeIndex;
|
||||
DWORD ThreadId;
|
||||
PPEB peb = NULL;
|
||||
PTEB teb = NULL;
|
||||
|
||||
peb = NtCurrentPeb();
|
||||
|
||||
ThreadId = (DWORD) pthread_self();
|
||||
|
||||
freeIndex = -1;
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
for (index = 0; index < peb->ThreadArraySize; index++)
|
||||
if (pthread_once(&_TebOnceControl, _TebInitOnce) == 0)
|
||||
{
|
||||
if (!peb->Threads[index].ThreadId)
|
||||
if ((teb = pthread_getspecific(_TebKey)) == NULL)
|
||||
{
|
||||
if (freeIndex < 0)
|
||||
freeIndex = (int) index;
|
||||
}
|
||||
|
||||
if (peb->Threads[index].ThreadId == ThreadId)
|
||||
{
|
||||
teb = peb->Threads[index].ThreadEnvironmentBlock;
|
||||
break;
|
||||
teb = malloc(sizeof(TEB));
|
||||
if (teb)
|
||||
{
|
||||
ZeroMemory(teb, sizeof(TEB));
|
||||
pthread_setspecific(_TebKey, teb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!teb)
|
||||
{
|
||||
if (freeIndex >= 0)
|
||||
{
|
||||
teb = NtThreadEnvironmentBlockNew();
|
||||
peb->Threads[freeIndex].ThreadEnvironmentBlock = teb;
|
||||
peb->Threads[freeIndex].ThreadId = ThreadId;
|
||||
peb->ThreadCount++;
|
||||
|
||||
teb->ProcessEnvironmentBlock = peb;
|
||||
teb->LastErrorValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
return teb;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user