Hi,

the last week I took some time to implement the first try of BSTR
caching in oleaut.c. On the one hand this will fix a bug, on the other
hand Wine could save some CPU cycles and can catch up with the speed of
the native implementation. 

Attached you will find the result of my considerations. It is not yet
ready for implementation but will reflect the "big picture" I thought
of. I would be happy to receive some feedback on this work especially
the following questions are still open:

- Is DllMain the right location for Initialization/Cleanup
- I'm totally confused about the task of how to write a testcase
  for this change.

Thanks for your help.

Markus
--- git/dlls/oleaut32/oleaut.c	2009-09-23 18:24:47.000000000 +0200
+++ wine/dlls/oleaut32/oleaut.c	2009-10-08 14:13:13.000000000 +0200
@@ -39,8 +39,6 @@
 
 WINE_DEFAULT_DEBUG_CHANNEL(ole);
 
-static BOOL BSTR_bCache = TRUE; /* Cache allocations to minimise alloc calls? */
-
 /******************************************************************************
  * BSTR  {OLEAUT32}
  *
@@ -69,6 +67,276 @@
  */
 
 /******************************************************************************
+ * BSTR caching
+ *
+ * NOTES
+ *  The following functions implement some basic operations to avoid permanent
+ *  heap allocation/deallocation when working with strings. The idea is to 
+ *  keep a list of allocated but no longer used memory for each thread. When
+ *  the application allocates new strings we can look at those lists to find
+ *  matching "free" memory and give it back. Thus one free/alloc cycle is 
+ *  saved. Additionally the lists are thread specific so concurrency can
+ *  be improved.
+ *
+ *  Windows standard behaviour is something like this: Keep 3 freelists of
+ *  about 6-8 entries. One is for memory areas less than 32 bytes, the second
+ *  is for areas between 32 and 64 bytes and the third is for strings larger
+ *  than 64 bytes. Memory is allocated with length aligned to 16 bytes
+ * 
+ *  We will do it in a similar way. Memory is allocated in "16 bytes steps".
+ *  Before freeing heap the memory size is determined. Freed strings with 
+ *  same heap sizes will be stored in containers. Each of them consists of 
+ *  a number of slots that are pointers  memory that is available for reuse. 
+ *  When allocating new memory we will check if the matching container can 
+ *  provide unused memory. If not the next container with 16 bytes larger 
+ *  memory sizes is taken into account. If this fails too memory for the
+ *  new BSTR will be allocated in the default way.
+ *
+ *  Containers are only build for small memory sizes (<=96 bytes and thus 
+ *  strings with up to 45 wide chars). Longer strings will not benefit very
+ *  much of this caching algorithm. They will be handled by an "overflow"
+ *  slot. Memory of this slot will be only assigned to subsequent allocation
+ *  calls if not too much memory is wasted. 
+ */
+
+#define CBSTR_SLOTS      30
+#define CBSTR_CONTAINERS  6
+
+typedef struct
+{
+    DWORD  MaxUsed[CBSTR_CONTAINERS];
+    DWORD* Slot[CBSTR_SLOTS];
+    DWORD* LargeString;
+    DWORD  LargeSize;
+} CBSTR_CacheStruct;
+
+static DWORD CBSTR_MinSlot[CBSTR_CONTAINERS] = { 0,  6, 12, 17, 22, 26 };
+static DWORD CBSTR_MaxSlot[CBSTR_CONTAINERS] = { 5, 11, 16, 21, 25, 29 };
+static BOOL  CBSTR_Active = TRUE;
+static DWORD CBSTR_Index;
+
+/******************************************************************************
+ * CBSTR_CalcMemory
+ *
+ * Determine the memory that is needed to allocate a string. This
+ * function will take care that the size is a multiple of 16.
+ */
+static inline DWORD CBSTR_CalcMemory(DWORD byteLen)
+{
+    /* BUGGY!! do not risk a overflow at UINT_MAX */
+    return ((byteLen + sizeof(WCHAR) + sizeof(DWORD) - 1) & 0xfffffff0) + 16;
+}
+
+
+/******************************************************************************
+ * CBSTR_ApplicationInit()
+ *
+ * Initialize BSTR caching for the application and all of its threads.
+ * This function is only called once
+ */
+static void CBSTR_ApplicationInit(void)
+{
+    CBSTR_Index = TlsAlloc();
+    if (CBSTR_Index==TLS_OUT_OF_INDEXES)
+    {
+        /* disable BSTR caching if something went wrong */
+        CBSTR_Active = FALSE;
+        FIXME("BSTR caching disabled due to missing TLS index\n");
+    } else {
+        FIXME("BSTR caching using TLS index %u\n", CBSTR_Index);
+    }
+}
+
+/******************************************************************************
+ * CBSTR_ApplicationFree()
+ * 
+ * Cleanup BSTR caching on application exit. Only called once.
+ */
+static void CBSTR_ApplicationFree(void)
+{
+    if (CBSTR_Index!=TLS_OUT_OF_INDEXES)
+    {
+        TlsFree(CBSTR_Index);
+        FIXME("BSTR caching freed TLS index %u\n", CBSTR_Index);
+    }
+}
+
+/******************************************************************************
+ * CBSTR_ThreadInit()
+ *
+ * Initialize cache structures for the current thread. Function is called
+ * if BSTR caching is active and the thread wants to free the first string
+ * that could be put into the freelists
+ */
+static CBSTR_CacheStruct* CBSTR_ThreadInit(void)
+{
+    CBSTR_CacheStruct *PCache;
+    int i;
+
+    PCache = HeapAlloc(GetProcessHeap(), 0, sizeof(CBSTR_CacheStruct));
+
+    if (PCache)
+    {
+        /* set everything to unused */
+        for (i=0;i<CBSTR_CONTAINERS;i++) 
+            PCache->MaxUsed[i] = CBSTR_MinSlot[i];
+        PCache->LargeSize = 0;
+
+        TlsSetValue(CBSTR_Index, PCache);
+
+        FIXME("BSTR caching initialized for this thread\n");
+
+        return PCache;
+    } else {
+        /* disable caching if something went wrong */
+        CBSTR_Active = FALSE;
+        FIXME("BSTR caching disabled due to memory allocation error\n");
+        return NULL;
+    } 
+}
+
+/******************************************************************************
+ * CBSTR_ThreadFree()
+ *
+ * Cleanup cache specific structures if a thread terminates
+ */
+static inline void CBSTR_ThreadFree(void)
+{
+    CBSTR_CacheStruct *PCache;
+    int i,j;
+
+    /* check if cache was initialized somewhere before */
+
+    if (CBSTR_Index==TLS_OUT_OF_INDEXES) return;
+    PCache = (CBSTR_CacheStruct *)TlsGetValue(CBSTR_Index); 
+    if (!PCache) return;
+
+    /* cleanup all heap areas that are still in use */
+
+    for (i=0;i<CBSTR_CONTAINERS;i++) 
+    {
+        for (j=CBSTR_MinSlot[i];j<PCache->MaxUsed[i];j++)
+        {
+            /* FIXME("freeing cache slot %u\n",j); */
+            HeapFree(GetProcessHeap(), 0, PCache->Slot[j]);
+        }
+    }
+
+    HeapFree(GetProcessHeap(), 0, PCache);
+}
+
+/******************************************************************************
+ * CBSTR_Alloc()
+ *
+ * Allocate memory for new BSTR. Hopefully we can satisfy the request from
+ * the internal freelists
+ */
+static inline DWORD* CBSTR_Alloc(DWORD AllocSize)
+{
+    CBSTR_CacheStruct *PCache;
+    DWORD Container;
+   
+    /* get thread specific BSTR cache */ 
+    PCache = (CBSTR_CacheStruct *)TlsGetValue(CBSTR_Index);
+
+    if (PCache && CBSTR_Active) {
+        Container = AllocSize >> 4;
+        if (Container>=CBSTR_CONTAINERS) 
+        {
+            /* When reusing large strings we have to ensure
+             * that not too much memory is wasted 
+             */
+            if (PCache->LargeSize>=AllocSize && PCache->LargeSize>>1<=AllocSize) 
+            {
+                /* FIXME("reusing memory of %u bytes for allocation of %u bytes\n",PCache->LargeSize, AllocSize); */
+                PCache->LargeSize = 0;
+                return PCache->LargeString;
+            } 
+        } else {
+            if (PCache->MaxUsed[Container]>CBSTR_MinSlot[Container]) 
+            {
+                /* slot with free memory found */
+                PCache->MaxUsed[Container]--;
+                /* FIXME("reusing memory of slot %u in container %u\n",PCache->MaxUsed[Container], Container); */
+                return PCache->Slot[PCache->MaxUsed[Container]];
+            } else {
+                /* try harder and waste some bytes in favour to speed */
+                Container++; 
+                if (Container<CBSTR_CONTAINERS && PCache->MaxUsed[Container]>CBSTR_MinSlot[Container])
+                {
+                    PCache->MaxUsed[Container]--;
+                    /* FIXME("reusing memory of slot %u in container %u (wasting 16 bytes)\n",PCache->MaxUsed[Container], Container); */
+                    return PCache->Slot[PCache->MaxUsed[Container]];
+                }
+            }
+        }
+    } 
+
+    /* Nothing found. Allocate new memory */
+    /* FIXME("allocating new memory with %u bytes\n", AllocSize); */
+    return HeapAlloc(GetProcessHeap(), 0, AllocSize);
+}
+
+/******************************************************************************
+ * CBSTR_Free()
+ *
+ */
+static void CBSTR_Free(DWORD *bufferPointer)
+{
+    CBSTR_CacheStruct *PCache;
+    DWORD AllocSize;
+    DWORD Slot;
+    DWORD Container;
+    
+    /* without caching release memory immediately */
+    if (!CBSTR_Active) 
+    {
+        HeapFree(GetProcessHeap(), 0, bufferPointer);
+        return;
+    }
+
+    PCache = (CBSTR_CacheStruct *)TlsGetValue(CBSTR_Index);
+    if (!PCache) 
+    {
+        PCache = CBSTR_ThreadInit();
+        if (!PCache) 
+        {
+            /* Something went wrong during initialization */
+            HeapFree(GetProcessHeap(), 0, bufferPointer);
+            return;
+        }
+    }
+  
+    AllocSize = HeapSize(GetProcessHeap(), 0, bufferPointer);
+    Container = AllocSize>>4;
+
+    if (Container>=CBSTR_CONTAINERS)
+    {
+        if (PCache->LargeSize!=0) 
+        {
+            HeapFree(GetProcessHeap(), 0, PCache->LargeString);
+            /* FIXME("deallocate large string heap of %u bytes\n",PCache->LargeSize); */
+        }
+        PCache->LargeString = bufferPointer;
+        PCache->LargeSize = AllocSize;
+        /* FIXME("remember freed large string of %u bytes\n",AllocSize); */
+    } else {
+        
+        Slot = PCache->MaxUsed[Container];
+        if (Slot>CBSTR_MaxSlot[Container])
+        {
+          Slot--; 
+          /* FIXME("container %u full, releasing slot %u\n",Container,Slot); */
+          HeapFree(GetProcessHeap(), 0, PCache->Slot[Slot]);
+        } else {
+          PCache->MaxUsed[Container]++;
+        }   
+        /* FIXME("remember free memory of %u bytes in slot %u\n",AllocSize,Slot); */
+        PCache->Slot[Slot] = bufferPointer;
+    }
+}
+
+/******************************************************************************
  *             SysStringLen  [OLEAUT32.7]
  *
  * Get the allocated length of a BSTR in wide characters.
@@ -188,10 +456,7 @@
 
     bufferPointer--;
 
-    /*
-     * Free the memory from its "real" origin.
-     */
-    HeapFree(GetProcessHeap(), 0, bufferPointer);
+    CBSTR_Free(bufferPointer);
 }
 
 /******************************************************************************
@@ -212,27 +477,17 @@
  */
 BSTR WINAPI SysAllocStringLen(const OLECHAR *str, unsigned int len)
 {
-    DWORD  bufferSize;
+    DWORD  stringSize;
     DWORD* newBuffer;
     WCHAR* stringBuffer;
 
     /* Detect integer overflow. */
     if (len >= ((UINT_MAX-sizeof(WCHAR)-sizeof(DWORD))/sizeof(WCHAR)))
 	return NULL;
-    /*
-     * Find the length of the buffer passed-in, in bytes.
-     */
-    bufferSize = len * sizeof (WCHAR);
-
-    /*
-     * Allocate a new buffer to hold the string.
-     * don't forget to keep an empty spot at the beginning of the
-     * buffer for the character count and an extra character at the
-     * end for the NULL.
-     */
-    newBuffer = HeapAlloc(GetProcessHeap(), 0,
-                          bufferSize + sizeof(WCHAR) + sizeof(DWORD));
 
+    stringSize = len * sizeof(WCHAR);
+    newBuffer = CBSTR_Alloc(CBSTR_CalcMemory(stringSize));
+    
     /*
      * If the memory allocation failed, return a null pointer.
      */
@@ -242,7 +497,7 @@
     /*
      * Copy the length of the string in the placeholder.
      */
-    *newBuffer = bufferSize;
+    *newBuffer = stringSize;
 
     /*
      * Skip the byte count.
@@ -255,9 +510,9 @@
      * buffer to nul if it is the case.
      */
     if (str != 0)
-      memcpy(newBuffer, str, bufferSize);
+      memcpy(newBuffer, str, stringSize);
     else
-      memset(newBuffer, 0, bufferSize);
+      memset(newBuffer, 0, stringSize);
 
     /*
      * Make sure that there is a nul character at the end of the
@@ -289,17 +544,21 @@
  */
 int WINAPI SysReAllocStringLen(BSTR* old, const OLECHAR* str, unsigned int len)
 {
+    DWORD stringSize;
+    DWORD *ptr;
+
     /* Detect integer overflow. */
     if (len >= ((UINT_MAX-sizeof(WCHAR)-sizeof(DWORD))/sizeof(WCHAR)))
 	return 0;
 
     if (*old!=NULL) {
-      DWORD newbytelen = len*sizeof(WCHAR);
-      DWORD *ptr = HeapReAlloc(GetProcessHeap(),0,((DWORD*)*old)-1,newbytelen+sizeof(WCHAR)+sizeof(DWORD));
+      stringSize = len*sizeof(WCHAR);
+      ptr = HeapReAlloc(GetProcessHeap(), 0, ((DWORD*)*old)-1,
+                        CBSTR_CalcMemory(stringSize));
       *old = (BSTR)(ptr+1);
-      *ptr = newbytelen;
+      *ptr = stringSize;
       if (str) {
-        memmove(*old, str, newbytelen);
+        memmove(*old, str, stringSize);
         (*old)[len] = 0;
       } else {
 	/* Subtle hidden feature: The old string data is still there
@@ -352,8 +611,8 @@
      * buffer for the character count and an extra character at the
      * end for the NULL.
      */
-    newBuffer = HeapAlloc(GetProcessHeap(), 0,
-                          len + sizeof(WCHAR) + sizeof(DWORD));
+
+    newBuffer = CBSTR_Alloc(CBSTR_CalcMemory(len));
 
     /*
      * If the memory allocation failed, return a null pointer.
@@ -443,7 +702,7 @@
  */
 void WINAPI SetOaNoCache(void)
 {
-  BSTR_bCache = FALSE;
+  CBSTR_Active = FALSE;
 }
 
 static const WCHAR	_delimiter[2] = {'!',0}; /* default delimiter apparently */
@@ -831,6 +1090,24 @@
  */
 BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
 {
+    /* initialize or cleanup BSTR caches */
+    switch (fdwReason)
+    {
+        case DLL_PROCESS_ATTACH: {
+            CBSTR_ApplicationInit(); 
+            break; 
+        }
+        case DLL_PROCESS_DETACH: {
+            CBSTR_ThreadFree();
+            CBSTR_ApplicationFree();
+            break;
+        }
+        case DLL_THREAD_DETACH: {
+            CBSTR_ThreadFree();
+            break;
+        } 
+    }
+   
     return OLEAUTPS_DllMain( hInstDll, fdwReason, lpvReserved );
 }
 


Reply via email to