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:
Norbert Federa 2013-10-11 19:34:23 +02:00
parent cca9774c5e
commit 6f9a8dbc1e
2 changed files with 67 additions and 174 deletions

View File

@ -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;
}

View File

@ -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;
}