VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/string/strcache.cpp@ 46203

Last change on this file since 46203 was 46203, checked in by vboxsync, 12 years ago

Missed one PAGE_SIZE.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.1 KB
Line 
1/* $Id: strcache.cpp 46203 2013-05-21 23:10:26Z vboxsync $ */
2/** @file
3 * IPRT - String Cache.
4 */
5
6/*
7 * Copyright (C) 2009-2013 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include <iprt/strcache.h>
32#include "internal/iprt.h"
33
34#include <iprt/alloca.h>
35#include <iprt/asm.h>
36#include <iprt/assert.h>
37#include <iprt/critsect.h>
38#include <iprt/err.h>
39#include <iprt/list.h>
40#include <iprt/mem.h>
41#include <iprt/once.h>
42#include <iprt/param.h>
43#include <iprt/string.h>
44
45#include "internal/strhash.h"
46#include "internal/magics.h"
47
48
49/*******************************************************************************
50* Defined Constants And Macros *
51*******************************************************************************/
52/** Special NIL pointer for the hash table. It differs from NULL in that it is
53 * a valid hash table entry when doing a lookup. */
54#define PRTSTRCACHEENTRY_NIL ((PRTSTRCACHEENTRY)~(uintptr_t)1)
55
56/** Calcuates the increment when handling a collision.
57 * The current formula makes sure it's always odd so we cannot possibly end
58 * up a cyclic loop with an even sized table. It also takes more bits from
59 * the length part. */
60#define RTSTRCACHE_COLLISION_INCR(uHashLen) ( ((uHashLen >> 8) | 1) )
61
62/**
63 * The RTSTRCACHEENTRY size threshold at which we stop using our own allocator
64 * and switch to the application heap, expressed as a power of two.
65 *
66 * Using a 1KB as a reasonable limit here.
67 */
68#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
69# define RTSTRCACHE_HEAP_THRESHOLD_BIT 10
70#else
71# define RTSTRCACHE_HEAP_THRESHOLD_BIT 9
72#endif
73/** The RTSTRCACHE_HEAP_THRESHOLD_BIT as a byte limit. */
74#define RTSTRCACHE_HEAP_THRESHOLD RT_BIT_32(RTSTRCACHE_HEAP_THRESHOLD_BIT)
75
76#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
77/**
78 * The RTSTRCACHEENTRY size threshold at which we start using the merge free
79 * list for allocations, expressed as a power of two.
80 */
81# define RTSTRCACHE_MERGED_THRESHOLD_BIT 6
82
83/** The number of bytes (power of two) that the merged allocation lists should
84 * be grown by. Must be much greater than RTSTRCACHE_MERGED_THRESHOLD. */
85#define RTSTRCACHE_MERGED_GROW_SIZE _32K
86#endif
87
88/** The number of bytes (power of two) that the fixed allocation lists should
89 * be grown by. */
90#define RTSTRCACHE_FIXED_GROW_SIZE _32K
91
92/** The number of fixed sized lists. */
93#define RTSTRCACHE_NUM_OF_FIXED_SIZES 10
94
95
96/** Validates a string cache handle, translating RTSTRCACHE_DEFAULT when found,
97 * and returns rc if not valid. */
98#define RTSTRCACHE_VALID_RETURN_RC(pStrCache, rc) \
99 do { \
100 if ((pStrCache) == RTSTRCACHE_DEFAULT) \
101 { \
102 int rcOnce = RTOnce(&g_rtStrCacheOnce, rtStrCacheInitDefault, NULL); \
103 if (RT_FAILURE(rcOnce)) \
104 return (rc); \
105 (pStrCache) = g_hrtStrCacheDefault; \
106 } \
107 else \
108 { \
109 AssertPtrReturn((pStrCache), (rc)); \
110 AssertReturn((pStrCache)->u32Magic == RTSTRCACHE_MAGIC, (rc)); \
111 } \
112 } while (0)
113
114
115
116/*******************************************************************************
117* Structures and Typedefs *
118*******************************************************************************/
119/**
120 * String cache entry.
121 */
122typedef struct RTSTRCACHEENTRY
123{
124 /** The number of references. */
125 uint32_t volatile cRefs;
126 /** The lower 16-bit hash value. */
127 uint16_t uHash;
128 /** The string length (excluding the terminator).
129 * If this is set to RTSTRCACHEENTRY_BIG_LEN, this is a BIG entry
130 * (RTSTRCACHEBIGENTRY). */
131 uint16_t cchString;
132 /** The string. */
133 char szString[8];
134} RTSTRCACHEENTRY;
135AssertCompileSize(RTSTRCACHEENTRY, 16);
136/** Pointer to a string cache entry. */
137typedef RTSTRCACHEENTRY *PRTSTRCACHEENTRY;
138/** Pointer to a const string cache entry. */
139typedef RTSTRCACHEENTRY *PCRTSTRCACHEENTRY;
140
141/** RTSTCACHEENTRY::cchString value for big cache entries. */
142#define RTSTRCACHEENTRY_BIG_LEN UINT16_MAX
143
144/**
145 * Big string cache entry.
146 *
147 * These are allocated individually from the application heap.
148 */
149typedef struct RTSTRCACHEBIGENTRY
150{
151 /** List entry. */
152 RTLISTNODE ListEntry;
153 /** The string length. */
154 uint32_t cchString;
155 /** The full hash value / padding. */
156 uint32_t uHash;
157 /** The core entry. */
158 RTSTRCACHEENTRY Core;
159} RTSTRCACHEBIGENTRY;
160AssertCompileSize(RTSTRCACHEENTRY, 16);
161/** Pointer to a big string cache entry. */
162typedef RTSTRCACHEBIGENTRY *PRTSTRCACHEBIGENTRY;
163/** Pointer to a const big string cache entry. */
164typedef RTSTRCACHEBIGENTRY *PCRTSTRCACHEBIGENTRY;
165
166
167/**
168 * A free string cache entry.
169 */
170typedef struct RTSTRCACHEFREE
171{
172 /** Zero value indicating that it's a free entry (no refs, no hash). */
173 uint32_t uZero;
174 /** Number of free bytes. Only used for > 32 byte allocations. */
175 uint32_t cbFree;
176 /** Pointer to the next free item. */
177 struct RTSTRCACHEFREE *pNext;
178} RTSTRCACHEFREE;
179AssertCompileSize(RTSTRCACHEENTRY, 16);
180AssertCompileMembersAtSameOffset(RTSTRCACHEENTRY, cRefs, RTSTRCACHEFREE, uZero);
181AssertCompileMembersAtSameOffset(RTSTRCACHEENTRY, szString, RTSTRCACHEFREE, pNext);
182/** Pointer to a free string cache entry. */
183typedef RTSTRCACHEFREE *PRTSTRCACHEFREE;
184
185#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
186
187/**
188 * A free string cache entry with merging.
189 *
190 * This differs from RTSTRCACHEFREE only in having a back pointer for more
191 * efficient list management (doubly vs. singly linked lists).
192 */
193typedef struct RTSTRCACHEFREEMERGE
194{
195 /** Marker that indicates what kind of entry this is, either . */
196 uint32_t uMarker;
197 /** Number of free bytes. Only used for > 32 byte allocations. */
198 uint32_t cbFree;
199 /** Pointer to the main node. NULL for main nodes. */
200 struct RTSTRCACHEFREEMERGE *pMain;
201 /** The free list entry. */
202 RTLISTNODE ListEntry;
203 /** Pads the size up to the minimum allocation unit for the merge list.
204 * This both defines the minimum allocation unit and simplifies pointer
205 * manipulation during merging and splitting. */
206 uint8_t abPadding[ARCH_BITS == 32 ? 44 : 32];
207} RTSTRCACHEFREEMERGE;
208AssertCompileSize(RTSTRCACHEFREEMERGE, RT_BIT_32(RTSTRCACHE_MERGED_THRESHOLD_BIT));
209/** Pointer to a free cache string in the merge list. */
210typedef RTSTRCACHEFREEMERGE *PRTSTRCACHEFREEMERGE;
211
212/** RTSTRCACHEFREEMERGE::uMarker value indicating that it's the real free chunk
213 * header. Must be something that's invalid UTF-8 for both little and big
214 * endian system. */
215# define RTSTRCACHEFREEMERGE_MAIN UINT32_C(0xfffffff1)
216/** RTSTRCACHEFREEMERGE::uMarker value indicating that it's part of a larger
217 * chunk of free memory. Must be something that's invalid UTF-8 for both little
218 * and big endian system. */
219# define RTSTRCACHEFREEMERGE_PART UINT32_C(0xfffffff2)
220
221#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
222
223/**
224 * Tracking structure chunk of memory used by the 16 byte or 32 byte
225 * allocations.
226 *
227 * This occupies the first entry in the chunk.
228 */
229typedef struct RTSTRCACHECHUNK
230{
231 /** The size of the chunk. */
232 size_t cb;
233 /** Pointer to the next chunk. */
234 struct RTSTRCACHECHUNK *pNext;
235} RTSTRCACHECHUNK;
236AssertCompile(sizeof(RTSTRCACHECHUNK) <= sizeof(RTSTRCACHEENTRY));
237/** Pointer to the chunk tracking structure. */
238typedef RTSTRCACHECHUNK *PRTSTRCACHECHUNK;
239
240
241/**
242 * Cache instance data.
243 */
244typedef struct RTSTRCACHEINT
245{
246 /** The string cache magic (RTSTRCACHE_MAGIC). */
247 uint32_t u32Magic;
248 /** Ref counter for the cache handle. */
249 uint32_t volatile cRefs;
250 /** The number of strings currently entered in the cache. */
251 uint32_t cStrings;
252 /** The size of the hash table. */
253 uint32_t cHashTab;
254 /** Pointer to the hash table. */
255 PRTSTRCACHEENTRY *papHashTab;
256 /** Free list for allocations of the sizes defined by g_acbFixedLists. */
257 PRTSTRCACHEFREE apFreeLists[RTSTRCACHE_NUM_OF_FIXED_SIZES];
258#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
259 /** Free lists based on */
260 RTLISTANCHOR aMergedFreeLists[RTSTRCACHE_HEAP_THRESHOLD_BIT - RTSTRCACHE_MERGED_THRESHOLD_BIT + 1];
261#endif
262 /** List of allocated memory chunks. */
263 PRTSTRCACHECHUNK pChunkList;
264 /** List of big cache entries. */
265 RTLISTANCHOR BigEntryList;
266 /** Critical section protecting the cache structures. */
267 RTCRITSECT CritSect;
268} RTSTRCACHEINT;
269/** Pointer to a cache instance. */
270typedef RTSTRCACHEINT *PRTSTRCACHEINT;
271
272
273
274/*******************************************************************************
275* Global Variables *
276*******************************************************************************/
277/** The entry sizes of the fixed lists (RTSTRCACHEINT::apFreeLists). */
278static const uint32_t g_acbFixedLists[RTSTRCACHE_NUM_OF_FIXED_SIZES] =
279{
280 16, 32, 64, 128, 192, 256, 320, 384, 448, 512
281};
282
283/** Init once for the default string cache. */
284static RTONCE g_rtStrCacheOnce = RTONCE_INITIALIZER;
285/** The default string cache. */
286static RTSTRCACHE g_hrtStrCacheDefault = NIL_RTSTRCACHE;
287
288
289/** @callback_method_impl{FNRTONCE, Initializes g_hrtStrCacheDefault} */
290static DECLCALLBACK(int) rtStrCacheInitDefault(void *pvUser)
291{
292 NOREF(pvUser);
293 return RTStrCacheCreate(&g_hrtStrCacheDefault, "Default");
294}
295
296
297RTDECL(int) RTStrCacheCreate(PRTSTRCACHE phStrCache, const char *pszName)
298{
299 int rc = VERR_NO_MEMORY;
300 PRTSTRCACHEINT pThis = (PRTSTRCACHEINT)RTMemAllocZ(sizeof(*pThis));
301 if (pThis)
302 {
303 pThis->cHashTab = 512;
304 pThis->papHashTab = (PRTSTRCACHEENTRY*)RTMemAllocZ(sizeof(pThis->papHashTab[0]) * pThis->cHashTab);
305 if (pThis->papHashTab)
306 {
307 rc = RTCritSectInit(&pThis->CritSect);
308 if (RT_SUCCESS(rc))
309 {
310 RTListInit(&pThis->BigEntryList);
311#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
312 for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aMergedFreeLists); i++)
313 RTListInit(&pThis->aMergedFreeLists[i]);
314#endif
315 pThis->cRefs = 1;
316 pThis->u32Magic = RTSTRCACHE_MAGIC;
317
318 *phStrCache = pThis;
319 return VINF_SUCCESS;
320 }
321 RTMemFree(pThis->papHashTab);
322 }
323 RTMemFree(pThis);
324 }
325 return rc;
326}
327RT_EXPORT_SYMBOL(RTStrCacheCreate);
328
329
330RTDECL(int) RTStrCacheDestroy(RTSTRCACHE hStrCache)
331{
332 if ( hStrCache == NIL_RTSTRCACHE
333 || hStrCache == RTSTRCACHE_DEFAULT)
334 return VINF_SUCCESS;
335
336 PRTSTRCACHEINT pThis = hStrCache;
337 RTSTRCACHE_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
338
339 /*
340 * Invalidate it. Enter the crit sect just to be on the safe side.
341 */
342 AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSTRCACHE_MAGIC_DEAD, RTSTRCACHE_MAGIC), VERR_INVALID_HANDLE);
343 RTCritSectEnter(&pThis->CritSect);
344 Assert(pThis->cRefs == 1);
345
346 PRTSTRCACHECHUNK pChunk;
347 while ((pChunk = pThis->pChunkList) != NULL)
348 {
349 pThis->pChunkList = pChunk->pNext;
350 RTMemPageFree(pChunk, pChunk->cb);
351 }
352
353 RTMemFree(pThis->papHashTab);
354 pThis->papHashTab = NULL;
355 pThis->cHashTab = 0;
356
357 PRTSTRCACHEBIGENTRY pCur, pNext;
358 RTListForEachSafe(&pThis->BigEntryList, pCur, pNext, RTSTRCACHEBIGENTRY, ListEntry)
359 {
360 RTMemFree(pCur);
361 }
362
363 RTCritSectLeave(&pThis->CritSect);
364 RTCritSectDelete(&pThis->CritSect);
365
366 RTMemFree(pThis);
367 return VINF_SUCCESS;
368}
369RT_EXPORT_SYMBOL(RTStrCacheDestroy);
370
371
372
373/**
374 * Selects the fixed free list index for a given minimum entry size.
375 *
376 * @returns Free list index.
377 * @param cbMin Minimum entry size.
378 */
379DECLINLINE(uint32_t) rtStrCacheSelectFixedList(uint32_t cbMin)
380{
381 Assert(cbMin <= g_acbFixedLists[RT_ELEMENTS(g_acbFixedLists) - 1]);
382 unsigned i = 0;
383 while (cbMin > g_acbFixedLists[i])
384 i++;
385 return i;
386}
387
388
389#ifdef RT_STRICT
390# define RTSTRCACHE_CHECK(a_pThis) do { rtStrCacheCheck(pThis); } while (0)
391/**
392 * Internal cache check.
393 */
394static void rtStrCacheCheck(PRTSTRCACHEINT pThis)
395{
396# ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
397 for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aMergedFreeLists); i++)
398 {
399 PRTSTRCACHEFREEMERGE pFree;
400 RTListForEach(&pThis->aMergedFreeLists[i], pFree, RTSTRCACHEFREEMERGE, ListEntry)
401 {
402 Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
403 Assert(pFree->cbFree > 0);
404 Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
405 }
406 }
407# endif
408}
409#else
410# define RTSTRCACHE_CHECK(a_pThis) do { } while (0)
411#endif
412
413
414/**
415 * Finds the first empty hash table entry given a hash+length value.
416 *
417 * ASSUMES that the hash table isn't full.
418 *
419 * @returns Hash table index.
420 * @param pThis The string cache instance.
421 * @param uHashLen The hash + length (not RTSTRCACHEENTRY_BIG_LEN).
422 */
423static uint32_t rtStrCacheFindEmptyHashTabEntry(PRTSTRCACHEINT pThis, uint32_t uHashLen)
424{
425 uint32_t iHash = uHashLen % pThis->cHashTab;
426 for (;;)
427 {
428 PRTSTRCACHEENTRY pEntry = pThis->papHashTab[iHash];
429 if (pEntry == NULL || pEntry == PRTSTRCACHEENTRY_NIL)
430 return iHash;
431
432 /* Advance. */
433 iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
434 iHash %= pThis->cHashTab;
435 }
436}
437
438/**
439 * Grows the hash table.
440 *
441 * @returns vINF_SUCCESS or VERR_NO_MEMORY.
442 * @param pThis The string cache instance.
443 */
444static int rtStrCacheGrowHashTab(PRTSTRCACHEINT pThis)
445{
446 /*
447 * Allocate a new hash table two times the size of the old one.
448 */
449 uint32_t cNew = pThis->cHashTab * 2;
450 PRTSTRCACHEENTRY *papNew = (PRTSTRCACHEENTRY *)RTMemAllocZ(sizeof(papNew[0]) * cNew);
451 if (papNew == NULL)
452 return VERR_NO_MEMORY;
453
454 /*
455 * Install the new table and move the items from the old table and into the new one.
456 */
457 PRTSTRCACHEENTRY *papOld = pThis->papHashTab;
458 uint32_t iOld = pThis->cHashTab;
459
460 pThis->papHashTab = papNew;
461 pThis->cHashTab = cNew;
462
463 while (iOld-- > 0)
464 {
465 PRTSTRCACHEENTRY pEntry = papOld[iOld];
466 if (pEntry != NULL && pEntry != PRTSTRCACHEENTRY_NIL)
467 {
468 uint32_t cchString = pEntry->cchString;
469 if (cchString == RTSTRCACHEENTRY_BIG_LEN)
470 cchString = RT_FROM_MEMBER(pEntry, RTSTRCACHEBIGENTRY, Core)->cchString;
471
472 uint32_t iHash = rtStrCacheFindEmptyHashTabEntry(pThis, RT_MAKE_U32(pEntry->uHash, cchString));
473 pThis->papHashTab[iHash] = pEntry;
474 }
475 }
476
477 /*
478 * Free the old hash table.
479 */
480 RTMemFree(papOld);
481 return VINF_SUCCESS;
482}
483
484#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
485
486/**
487 * Link/Relink into the free right list.
488 *
489 * @param pThis The string cache instance.
490 * @param pFree The free string entry.
491 */
492static void rtStrCacheRelinkMerged(PRTSTRCACHEINT pThis, PRTSTRCACHEFREEMERGE pFree)
493{
494 Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
495 Assert(pFree->cbFree > 0);
496 Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
497
498 if (!RTListIsEmpty(&pFree->ListEntry))
499 RTListNodeRemove(&pFree->ListEntry);
500
501 uint32_t iList = (ASMBitLastSetU32(pFree->cbFree) - 1) - RTSTRCACHE_MERGED_THRESHOLD_BIT;
502 if (iList >= RT_ELEMENTS(pThis->aMergedFreeLists))
503 iList = RT_ELEMENTS(pThis->aMergedFreeLists) - 1;
504
505 RTListPrepend(&pThis->aMergedFreeLists[iList], &pFree->ListEntry);
506}
507
508
509/**
510 * Allocate a cache entry from the merged free lists.
511 *
512 * @returns Pointer to the cache entry on success, NULL on allocation error.
513 * @param pThis The string cache instance.
514 * @param uHash The full hash of the string.
515 * @param pchString The string.
516 * @param cchString The string length.
517 * @param cbEntry The required entry size.
518 */
519static PRTSTRCACHEENTRY rtStrCacheAllocMergedEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
520 const char *pchString, uint32_t cchString, uint32_t cbEntry)
521{
522 cbEntry = RT_ALIGN_32(cbEntry, sizeof(RTSTRCACHEFREEMERGE));
523 Assert(cbEntry > cchString);
524
525 /*
526 * Search the list heads first.
527 */
528 PRTSTRCACHEFREEMERGE pFree = NULL;
529
530 uint32_t iList = ASMBitLastSetU32(cbEntry) - 1;
531 if (!RT_IS_POWER_OF_TWO(cbEntry))
532 iList++;
533 iList -= RTSTRCACHE_MERGED_THRESHOLD_BIT;
534
535 while (iList < RT_ELEMENTS(pThis->aMergedFreeLists))
536 {
537 pFree = RTListGetFirst(&pThis->aMergedFreeLists[iList], RTSTRCACHEFREEMERGE, ListEntry);
538 if (pFree)
539 {
540 /*
541 * Found something. Should we we split it? We split from the end
542 * to avoid having to update all the sub entries.
543 */
544 Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
545 Assert(pFree->cbFree >= cbEntry);
546 Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
547
548 if (pFree->cbFree == cbEntry)
549 RTListNodeRemove(&pFree->ListEntry);
550 else
551 {
552 uint32_t cRemainder = (pFree->cbFree - cbEntry) / sizeof(*pFree);
553 PRTSTRCACHEFREEMERGE pRemainder = pFree;
554 pFree += cRemainder;
555
556 Assert((pRemainder->cbFree - cbEntry) == cRemainder * sizeof(*pFree));
557 pRemainder->cbFree = cRemainder * sizeof(*pFree);
558
559 rtStrCacheRelinkMerged(pThis, pRemainder);
560 }
561 break;
562 }
563 iList++;
564 }
565 if (!pFree)
566 {
567 /*
568 * Allocate a new block. (We could search the list below in some
569 * cases, but it's too much effort to write and execute).
570 */
571 size_t const cbChunk = RTSTRCACHE_MERGED_GROW_SIZE; AssertReturn(cbChunk > cbEntry * 2, NULL);
572 PRTSTRCACHECHUNK pChunk = (PRTSTRCACHECHUNK)RTMemPageAlloc(cbChunk);
573 if (!pChunk)
574 return NULL;
575 pChunk->cb = cbChunk;
576 pChunk->pNext = pThis->pChunkList;
577 pThis->pChunkList = pChunk;
578 AssertCompile(sizeof(*pChunk) <= sizeof(*pFree));
579
580 /*
581 * Get one node for the allocation at hand.
582 */
583 pFree = (PRTSTRCACHEFREEMERGE)((uintptr_t)pChunk + sizeof(*pFree));
584
585 /*
586 * Create a free block out of the remainder (always a reminder).
587 */
588 PRTSTRCACHEFREEMERGE pNewFree = (PRTSTRCACHEFREEMERGE)((uintptr_t)pFree + cbEntry);
589 pNewFree->uMarker = RTSTRCACHEFREEMERGE_MAIN;
590 pNewFree->cbFree = cbChunk - sizeof(*pNewFree) - cbEntry; Assert(pNewFree->cbFree < cbChunk && pNewFree->cbFree > 0);
591 pNewFree->pMain = NULL;
592 RTListInit(&pNewFree->ListEntry);
593
594 uint32_t iInternalBlock = pNewFree->cbFree / sizeof(*pNewFree);
595 while (iInternalBlock-- > 1)
596 {
597 pNewFree[iInternalBlock].uMarker = RTSTRCACHEFREEMERGE_PART;
598 pNewFree[iInternalBlock].cbFree = 0;
599 pNewFree[iInternalBlock].pMain = pNewFree;
600 }
601
602 rtStrCacheRelinkMerged(pThis, pNewFree);
603 }
604
605 /*
606 * Initialize the entry. We zero all bytes we don't use so they cannot
607 * accidentally be mistaken for a free entry.
608 */
609 ASMCompilerBarrier();
610 PRTSTRCACHEENTRY pEntry = (PRTSTRCACHEENTRY)pFree;
611 pEntry->cRefs = 1;
612 pEntry->uHash = (uint16_t)uHash;
613 pEntry->cchString = (uint16_t)cchString;
614 memcpy(pEntry->szString, pchString, cchString);
615 RT_BZERO(&pEntry->szString[cchString], cbEntry - RT_UOFFSETOF(RTSTRCACHEENTRY, szString) - cchString);
616
617 RTSTRCACHE_CHECK(pThis);
618
619 return pEntry;
620}
621
622#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
623
624/**
625 * Allocate a cache entry from the heap.
626 *
627 * @returns Pointer to the cache entry on success, NULL on allocation error.
628 * @param pThis The string cache instance.
629 * @param uHash The full hash of the string.
630 * @param pchString The string.
631 * @param cchString The string length.
632 */
633static PRTSTRCACHEENTRY rtStrCacheAllocHeapEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
634 const char *pchString, uint32_t cchString)
635{
636 /*
637 * Allocate a heap block for storing the string. We do some size aligning
638 * here to encourage the heap to give us optimal alignment.
639 */
640 size_t cbEntry = RT_UOFFSETOF(RTSTRCACHEBIGENTRY, Core.szString[cchString + 1]);
641 PRTSTRCACHEBIGENTRY pBigEntry = (PRTSTRCACHEBIGENTRY)RTMemAlloc(RT_ALIGN_Z(cbEntry, 64));
642 if (!pBigEntry)
643 return NULL;
644
645 /*
646 * Initialize the block.
647 */
648 RTListAppend(&pThis->BigEntryList, &pBigEntry->ListEntry);
649 pBigEntry->cchString = cchString;
650 pBigEntry->uHash = uHash;
651 pBigEntry->Core.cRefs = 1;
652 pBigEntry->Core.uHash = (uint16_t)uHash;
653 pBigEntry->Core.cchString = RTSTRCACHEENTRY_BIG_LEN;
654 memcpy(pBigEntry->Core.szString, pchString, cchString);
655 pBigEntry->Core.szString[cchString] = '\0';
656
657 return &pBigEntry->Core;
658}
659
660
661/**
662 * Allocate a cache entry from a fixed size free list.
663 *
664 * @returns Pointer to the cache entry on success, NULL on allocation error.
665 * @param pThis The string cache instance.
666 * @param uHash The full hash of the string.
667 * @param pchString The string.
668 * @param cchString The string length.
669 * @param iFreeList Which free list.
670 */
671static PRTSTRCACHEENTRY rtStrCacheAllocFixedEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
672 const char *pchString, uint32_t cchString, uint32_t iFreeList)
673{
674 /*
675 * Get an entry from the free list. If empty, allocate another chunk of
676 * memory and split it up into free entries of the desired size.
677 */
678 PRTSTRCACHEFREE pFree = pThis->apFreeLists[iFreeList];
679 if (!pFree)
680 {
681 PRTSTRCACHECHUNK pChunk = (PRTSTRCACHECHUNK)RTMemPageAlloc(RTSTRCACHE_FIXED_GROW_SIZE);
682 if (!pChunk)
683 return NULL;
684 pChunk->cb = RTSTRCACHE_FIXED_GROW_SIZE;
685 pChunk->pNext = pThis->pChunkList;
686 pThis->pChunkList = pChunk;
687
688 PRTSTRCACHEFREE pPrev = NULL;
689 uint32_t const cbEntry = g_acbFixedLists[iFreeList];
690 uint32_t cLeft = RTSTRCACHE_FIXED_GROW_SIZE / cbEntry - 1;
691 pFree = (PRTSTRCACHEFREE)((uintptr_t)pChunk + cbEntry);
692
693 Assert(sizeof(*pChunk) <= cbEntry);
694 Assert(sizeof(*pFree) <= cbEntry);
695 Assert(cbEntry < RTSTRCACHE_FIXED_GROW_SIZE / 16);
696
697 while (cLeft-- > 0)
698 {
699 pFree->uZero = 0;
700 pFree->cbFree = cbEntry;
701 pFree->pNext = pPrev;
702 pPrev = pFree;
703 pFree = (PRTSTRCACHEFREE)((uintptr_t)pFree + cbEntry);
704 }
705
706 Assert(pPrev);
707 pThis->apFreeLists[iFreeList] = pFree = pPrev;
708 }
709
710 /*
711 * Unlink it.
712 */
713 pThis->apFreeLists[iFreeList] = pFree->pNext;
714 ASMCompilerBarrier();
715
716 /*
717 * Initialize the entry.
718 */
719 PRTSTRCACHEENTRY pEntry = (PRTSTRCACHEENTRY)pFree;
720 pEntry->cRefs = 1;
721 pEntry->uHash = (uint16_t)uHash;
722 pEntry->cchString = (uint16_t)cchString;
723 memcpy(pEntry->szString, pchString, cchString);
724 pEntry->szString[cchString] = '\0';
725
726 return pEntry;
727}
728
729
730/**
731 * Looks up a string in the hash table.
732 *
733 * @returns Pointer to the string cache entry, NULL + piFreeHashTabEntry if not
734 * found.
735 * @param pThis The string cache instance.
736 * @param uHashLen The hash + length (not RTSTRCACHEENTRY_BIG_LEN).
737 * @param cchString The real length.
738 * @param pchString The string.
739 * @param piFreeHashTabEntry Where to store the index insertion index if NULL
740 * is returned (same as what
741 * rtStrCacheFindEmptyHashTabEntry would return).
742 */
743static PRTSTRCACHEENTRY rtStrCacheLookUp(PRTSTRCACHEINT pThis, uint32_t uHashLen, uint32_t cchString, const char *pchString,
744 uint32_t *piFreeHashTabEntry)
745{
746 *piFreeHashTabEntry = UINT32_MAX;
747
748 uint16_t cchStringFirst = RT_UOFFSETOF(RTSTRCACHEENTRY, szString[cchString + 1]) < RTSTRCACHE_HEAP_THRESHOLD
749 ? (uint16_t)cchString : RTSTRCACHEENTRY_BIG_LEN;
750 uint32_t iHash = uHashLen % pThis->cHashTab;
751 for (;;)
752 {
753 PRTSTRCACHEENTRY pEntry = pThis->papHashTab[iHash];
754
755 /* Give up if NULL, but record the index for insertion. */
756 if (pEntry == NULL)
757 {
758 if (*piFreeHashTabEntry == UINT32_MAX)
759 *piFreeHashTabEntry = iHash;
760 return NULL;
761 }
762
763 if (pEntry != PRTSTRCACHEENTRY_NIL)
764 {
765 /* Compare. */
766 if ( pEntry->uHash == (uint16_t)uHashLen
767 && pEntry->cchString == cchStringFirst)
768 {
769 if (pEntry->cchString != RTSTRCACHEENTRY_BIG_LEN)
770 {
771 if ( !memcmp(pEntry->szString, pchString, cchString)
772 && pEntry->szString[cchString] == '\0')
773 return pEntry;
774 }
775 else
776 {
777 PRTSTRCACHEBIGENTRY pBigEntry = RT_FROM_MEMBER(pEntry, RTSTRCACHEBIGENTRY, Core);
778 if ( pBigEntry->cchString == cchString
779 && !memcmp(pBigEntry->Core.szString, pchString, cchString))
780 return &pBigEntry->Core;
781 }
782 }
783 }
784 /* Record the first NIL index for insertion in case we don't get a hit. */
785 else if (*piFreeHashTabEntry == UINT32_MAX)
786 *piFreeHashTabEntry = iHash;
787
788 /* Advance. */
789 iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
790 iHash %= pThis->cHashTab;
791 }
792}
793
794
795RTDECL(const char *) RTStrCacheEnterN(RTSTRCACHE hStrCache, const char *pchString, size_t cchString)
796{
797 PRTSTRCACHEINT pThis = hStrCache;
798 RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
799
800
801 /*
802 * Calculate the hash and figure the exact string length, then look for an existing entry.
803 */
804 uint32_t const uHash = sdbmN(pchString, cchString, &cchString);
805 uint32_t const uHashLen = RT_MAKE_U32(uHash, cchString);
806 AssertReturn(cchString < _1G, NULL);
807 uint32_t const cchString32 = (uint32_t)cchString;
808
809 RTCritSectEnter(&pThis->CritSect);
810 RTSTRCACHE_CHECK(pThis);
811
812 uint32_t iFreeHashTabEntry;
813 PRTSTRCACHEENTRY pEntry = rtStrCacheLookUp(pThis, uHashLen, cchString32, pchString, &iFreeHashTabEntry);
814 if (pEntry)
815 {
816 uint32_t cRefs = ASMAtomicIncU32(&pEntry->cRefs);
817 Assert(cRefs < UINT32_MAX / 2);
818 }
819 else
820 {
821 /*
822 * Allocate a new entry.
823 */
824 uint32_t cbEntry = cchString32 + 1U + RT_UOFFSETOF(RTSTRCACHEENTRY, szString);
825 if (cbEntry >= RTSTRCACHE_HEAP_THRESHOLD)
826 pEntry = rtStrCacheAllocHeapEntry(pThis, uHash, pchString, cchString32);
827#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
828 else if (cbEntry >= RTSTRCACHE_MERGED_THRESHOLD_BIT)
829 pEntry = rtStrCacheAllocMergedEntry(pThis, uHash, pchString, cchString32, cbEntry);
830#endif
831 else
832 pEntry = rtStrCacheAllocFixedEntry(pThis, uHash, pchString, cchString32,
833 rtStrCacheSelectFixedList(cbEntry));
834 if (!pEntry)
835 {
836 RTSTRCACHE_CHECK(pThis);
837 RTCritSectLeave(&pThis->CritSect);
838 return NULL;
839 }
840
841 /*
842 * Insert it into the hash table.
843 */
844 if (pThis->cHashTab - pThis->cStrings < pThis->cHashTab / 2)
845 {
846 int rc = rtStrCacheGrowHashTab(pThis);
847 if (RT_SUCCESS(rc))
848 iFreeHashTabEntry = rtStrCacheFindEmptyHashTabEntry(pThis, uHashLen);
849 else if (pThis->cHashTab - pThis->cStrings <= pThis->cHashTab / 8) /* 12.5% full => error */
850 {
851 pThis->papHashTab[iFreeHashTabEntry] = pEntry;
852 pThis->cStrings++;
853 RTStrCacheRelease(hStrCache, pEntry->szString);
854
855 RTSTRCACHE_CHECK(pThis);
856 RTCritSectLeave(&pThis->CritSect);
857 return NULL;
858 }
859 }
860
861 pThis->papHashTab[iFreeHashTabEntry] = pEntry;
862 pThis->cStrings++;
863 Assert(pThis->cStrings < pThis->cHashTab && pThis->cStrings > 0);
864 }
865
866 RTSTRCACHE_CHECK(pThis);
867 RTCritSectLeave(&pThis->CritSect);
868 return pEntry->szString;
869}
870RT_EXPORT_SYMBOL(RTStrCacheEnterN);
871
872
873RTDECL(const char *) RTStrCacheEnter(RTSTRCACHE hStrCache, const char *psz)
874{
875 return RTStrCacheEnterN(hStrCache, psz, strlen(psz));
876}
877RT_EXPORT_SYMBOL(RTStrCacheEnter);
878
879
880static const char *rtStrCacheEnterLowerWorker(PRTSTRCACHEINT pThis, const char *pchString, size_t cchString)
881{
882 /*
883 * Try use a dynamic heap buffer first.
884 */
885 if (cchString < 512)
886 {
887 char *pszStackBuf = (char *)alloca(cchString + 1);
888 if (pszStackBuf)
889 {
890 memcpy(pszStackBuf, pchString, cchString);
891 pszStackBuf[cchString] = '\0';
892 RTStrToLower(pszStackBuf);
893 return RTStrCacheEnterN(pThis, pszStackBuf, cchString);
894 }
895 }
896
897 /*
898 * Fall back on heap.
899 */
900 char *pszHeapBuf = (char *)RTMemTmpAlloc(cchString + 1);
901 if (!pszHeapBuf)
902 return NULL;
903 memcpy(pszHeapBuf, pchString, cchString);
904 pszHeapBuf[cchString] = '\0';
905 RTStrToLower(pszHeapBuf);
906 const char *pszRet = RTStrCacheEnterN(pThis, pszHeapBuf, cchString);
907 RTMemTmpFree(pszHeapBuf);
908 return pszRet;
909}
910
911RTDECL(const char *) RTStrCacheEnterLowerN(RTSTRCACHE hStrCache, const char *pchString, size_t cchString)
912{
913 PRTSTRCACHEINT pThis = hStrCache;
914 RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
915 return rtStrCacheEnterLowerWorker(pThis, pchString, RTStrNLen(pchString, cchString));
916}
917RT_EXPORT_SYMBOL(RTStrCacheEnterLowerN);
918
919
920RTDECL(const char *) RTStrCacheEnterLower(RTSTRCACHE hStrCache, const char *psz)
921{
922 PRTSTRCACHEINT pThis = hStrCache;
923 RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
924 return rtStrCacheEnterLowerWorker(pThis, psz, strlen(psz));
925}
926RT_EXPORT_SYMBOL(RTStrCacheEnterLower);
927
928
929RTDECL(uint32_t) RTStrCacheRetain(const char *psz)
930{
931 AssertPtr(psz);
932
933 PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
934 Assert(!((uintptr_t)pStr & 15) || pStr->cchString == RTSTRCACHEENTRY_BIG_LEN);
935
936 uint32_t cRefs = ASMAtomicIncU32(&pStr->cRefs);
937 Assert(cRefs > 1);
938 Assert(cRefs < UINT32_MAX / 2);
939
940 return cRefs;
941}
942RT_EXPORT_SYMBOL(RTStrCacheRetain);
943
944
945static uint32_t rtStrCacheFreeEntry(PRTSTRCACHEINT pThis, PRTSTRCACHEENTRY pStr)
946{
947 RTCritSectEnter(&pThis->CritSect);
948 RTSTRCACHE_CHECK(pThis);
949
950 /* Remove it from the hash table. */
951 uint32_t uHashLen = RT_MAKE_U32(pStr->uHash,
952 pStr->cchString == RTSTRCACHEENTRY_BIG_LEN
953 ? RT_FROM_MEMBER(pStr, RTSTRCACHEBIGENTRY, Core)->cchString
954 : pStr->cchString);
955 uint32_t iHash = uHashLen % pThis->cHashTab;
956 if (pThis->papHashTab[iHash] == pStr)
957 pThis->papHashTab[iHash] = PRTSTRCACHEENTRY_NIL;
958 else
959 {
960 do
961 {
962 AssertBreak(pThis->papHashTab[iHash] != NULL);
963 iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
964 iHash %= pThis->cHashTab;
965 } while (pThis->papHashTab[iHash] != pStr);
966 if (RT_LIKELY(pThis->papHashTab[iHash] == pStr))
967 pThis->papHashTab[iHash] = PRTSTRCACHEENTRY_NIL;
968 else
969 {
970 AssertFailed();
971 iHash = pThis->cHashTab;
972 while (iHash-- > 0)
973 if (pThis->papHashTab[iHash] == pStr)
974 break;
975 AssertMsgFailed(("iHash=%u cHashTab=%u\n", iHash, pThis->cHashTab));
976 }
977 }
978
979 pThis->cStrings--;
980 Assert(pThis->cStrings < pThis->cHashTab);
981
982 /* Free it. */
983 if (pStr->cchString != RTSTRCACHEENTRY_BIG_LEN)
984 {
985 uint32_t const cbMin = pStr->cchString + 1U + RT_UOFFSETOF(RTSTRCACHEENTRY, szString);
986#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
987 if (cbMin <= RTSTRCACHE_MAX_FIXED)
988#endif
989 {
990 /*
991 * No merging, just add it to the list.
992 */
993 uint32_t const iFreeList = rtStrCacheSelectFixedList(cbMin);
994 ASMCompilerBarrier();
995 PRTSTRCACHEFREE pFreeStr = (PRTSTRCACHEFREE)pStr;
996 pFreeStr->cbFree = cbMin;
997 pFreeStr->uZero = 0;
998 pFreeStr->pNext = pThis->apFreeLists[iFreeList];
999 pThis->apFreeLists[iFreeList] = pFreeStr;
1000 }
1001#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
1002 else
1003 {
1004 /*
1005 * Complicated mode, we merge with adjecent nodes.
1006 */
1007 ASMCompilerBarrier();
1008 PRTSTRCACHEFREEMERGE pFreeStr = (PRTSTRCACHEFREEMERGE)pStr;
1009 pFreeStr->cbFree = RT_ALIGN_32(cbMin, sizeof(*pFreeStr));
1010 pFreeStr->uMarker = RTSTRCACHEFREEMERGE_MAIN;
1011 pFreeStr->pMain = NULL;
1012 RTListInit(&pFreeStr->ListEntry);
1013
1014 /*
1015 * Merge with previous?
1016 * (Reading one block back is safe because there is always the
1017 * RTSTRCACHECHUNK structure at the head of each memory chunk.)
1018 */
1019 uint32_t cInternalBlocks = pFreeStr->cbFree / sizeof(*pFreeStr);
1020 PRTSTRCACHEFREEMERGE pMain = pFreeStr - 1;
1021 if ( pMain->uMarker == RTSTRCACHEFREEMERGE_MAIN
1022 || pMain->uMarker == RTSTRCACHEFREEMERGE_PART)
1023 {
1024 while (pMain->uMarker != RTSTRCACHEFREEMERGE_MAIN)
1025 pMain--;
1026 pMain->cbFree += pFreeStr->cbFree;
1027 }
1028 else
1029 {
1030 pMain = pFreeStr;
1031 pFreeStr++;
1032 cInternalBlocks--;
1033 }
1034
1035 /*
1036 * Mark internal blocks in the string we're freeing.
1037 */
1038 while (cInternalBlocks-- > 0)
1039 {
1040 pFreeStr->uMarker = RTSTRCACHEFREEMERGE_PART;
1041 pFreeStr->cbFree = 0;
1042 pFreeStr->pMain = pMain;
1043 RTListInit(&pFreeStr->ListEntry);
1044 pFreeStr++;
1045 }
1046
1047 /*
1048 * Merge with next? Limitation: We won't try cross page boundraries.
1049 * (pFreeStr points to the next first free enter after the string now.)
1050 */
1051 if ( PAGE_ADDRESS(pFreeStr) == PAGE_ADDRESS(&pFreeStr[-1])
1052 && pFreeStr->uMarker == RTSTRCACHEFREEMERGE_MAIN)
1053 {
1054 pMain->cbFree += pFreeStr->cbFree;
1055 cInternalBlocks = pFreeStr->cbFree / sizeof(*pFreeStr);
1056 Assert(cInternalBlocks > 0);
1057
1058 /* Update the main block we merge with. */
1059 pFreeStr->cbFree = 0;
1060 pFreeStr->uMarker = RTSTRCACHEFREEMERGE_PART;
1061 RTListNodeRemove(&pFreeStr->ListEntry);
1062 RTListInit(&pFreeStr->ListEntry);
1063
1064 /* Change the internal blocks we merged in. */
1065 cInternalBlocks--;
1066 while (cInternalBlocks-- > 0)
1067 {
1068 pFreeStr++;
1069 pFreeStr->pMain = pMain;
1070 Assert(pFreeStr->uMarker == RTSTRCACHEFREEMERGE_PART);
1071 Assert(!pFreeStr->cbFree);
1072 }
1073 }
1074
1075 /*
1076 * Add/relink into the appropriate free list.
1077 */
1078 rtStrCacheRelinkMerged(pThis, pMain);
1079 }
1080#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
1081 RTSTRCACHE_CHECK(pThis);
1082 RTCritSectLeave(&pThis->CritSect);
1083 }
1084 else
1085 {
1086 /* Big string. */
1087 PRTSTRCACHEBIGENTRY pBigStr = RT_FROM_MEMBER(pStr, RTSTRCACHEBIGENTRY, Core);
1088 RTListNodeRemove(&pBigStr->ListEntry);
1089
1090 RTSTRCACHE_CHECK(pThis);
1091 RTCritSectLeave(&pThis->CritSect);
1092
1093 RTMemFree(pBigStr);
1094 }
1095
1096 return 0;
1097}
1098
1099RTDECL(uint32_t) RTStrCacheRelease(RTSTRCACHE hStrCache, const char *psz)
1100{
1101 if (!psz)
1102 return 0;
1103
1104 PRTSTRCACHEINT pThis = hStrCache;
1105 RTSTRCACHE_VALID_RETURN_RC(pThis, UINT32_MAX);
1106
1107 AssertPtr(psz);
1108 PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
1109 Assert(!((uintptr_t)pStr & 15) || pStr->cchString == RTSTRCACHEENTRY_BIG_LEN);
1110
1111 /*
1112 * Drop a reference and maybe free the entry.
1113 */
1114 uint32_t cRefs = ASMAtomicDecU32(&pStr->cRefs);
1115 Assert(cRefs < UINT32_MAX / 2);
1116 if (!cRefs)
1117 return rtStrCacheFreeEntry(pThis, pStr);
1118
1119 return cRefs;
1120}
1121RT_EXPORT_SYMBOL(RTStrCacheRelease);
1122
1123
1124RTDECL(size_t) RTStrCacheLength(const char *psz)
1125{
1126 if (!psz)
1127 return 0;
1128
1129 AssertPtr(psz);
1130 PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
1131 if (pStr->cchString == RTSTRCACHEENTRY_BIG_LEN)
1132 {
1133 PRTSTRCACHEBIGENTRY pBigStr = RT_FROM_MEMBER(psz, RTSTRCACHEBIGENTRY, Core.szString);
1134 return pBigStr->cchString;
1135 }
1136 Assert(!((uintptr_t)pStr & 15));
1137 return pStr->cchString;
1138}
1139RT_EXPORT_SYMBOL(RTStrCacheLength);
1140
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette