2 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/common/const-string.h>
29 #include <dali/public-api/common/dali-common.h>
45 for(auto& page : mPages)
50 char* Allocate(uint32_t size, uint32_t alignment)
52 uintptr_t mask = alignment - 1;
53 uintptr_t alignedOffset = (~reinterpret_cast<uintptr_t>(mCursor) + 1) & mask;
54 uintptr_t totalSize = size + alignedOffset;
56 if(totalSize > static_cast<uintptr_t>(mEnd - mCursor))
59 alignedOffset = (~reinterpret_cast<uintptr_t>(mCursor) + 1) & mask;
61 char* ptr = mCursor + alignedOffset;
69 constexpr size_t pageSize = 4096;
70 char* page = new char[pageSize];
71 mPages.push_back(page);
73 mEnd = mCursor + pageSize;
76 char* mCursor{nullptr};
78 std::vector<char*> mPages;
84 explicit StringEntry(uint16_t stringLength)
85 : mStringLength(stringLength)
88 size_t getStringLength() const
92 const char* GetStringData() const
94 return reinterpret_cast<const char*>(this + 1);
97 std::string_view GetString() const
99 return std::string_view(GetStringData(), getStringLength());
102 * GetStringEntryFromData - Given key data that is known to be embedded
103 * into a StringEntry, return the StringEntry itself.
105 static StringEntry& GetStringEntryFromData(const char* stringData)
107 char* ptr = const_cast<char*>(stringData) - sizeof(StringEntry);
108 return *reinterpret_cast<StringEntry*>(ptr);
111 * Create a StringEntry from string_view
114 static StringEntry* Create(std::string_view str, ArenaAllocator& allocator)
116 size_t length = str.size();
118 // Allocate a new item with space for the string at the end and a null
120 size_t allocSize = sizeof(StringEntry) + length + 1;
121 size_t alignment = alignof(StringEntry);
123 // Construct the value.
124 StringEntry* newItem = new(allocator.Allocate(allocSize, alignment)) StringEntry(length);
126 // Copy the string information.
127 char* strBuffer = const_cast<char*>(newItem->GetStringData());
129 memcpy(strBuffer, str.data(), length);
130 strBuffer[length] = 0; // Null terminate.
135 uint16_t mStringLength;
141 unsigned Size() const
150 static StringPool& Instance()
152 static StringPool object;
156 const char* Intern(std::string_view str)
158 const std::lock_guard<std::mutex> lock(mMutex);
160 auto bucketNumber = FindBucket(str);
161 auto& bucket = mTable[bucketNumber];
165 // string already exists
166 return bucket->GetStringData();
169 // assign the newly created StringEntry to the designated bucket.
170 bucket = StringEntry::Create(str, mAllocator);
174 bucketNumber = RehashTable(bucketNumber);
176 return mTable[bucketNumber]->GetStringData();
180 unsigned NextBucket(unsigned currentBucket, unsigned totalBucket, unsigned ProbeAmount)
182 return (currentBucket + ProbeAmount) & (totalBucket - 1);
187 unsigned newBuckets = 512;
189 * for memory efficiency and cache locality we store the StringEntry and corresponding HashValue in separate
190 * segments in this way each entry in the bucket will take 12byte instead of 16 if we keep them together.
191 * So we allocate one memory and treat 1st segment as a array of StringEntry* and the second segment as array of
194 mTable = static_cast<StringEntry**>(calloc(newBuckets + 1, sizeof(StringEntry**) + sizeof(unsigned)));
195 DALI_ASSERT_ALWAYS(mTable && "calloc returned nullptr");
197 mBuckets = newBuckets;
200 unsigned FindBucket(std::string_view name)
202 unsigned bucketSize = mBuckets;
203 unsigned fullHashValue = std::hash<std::string_view>{}(name);
204 unsigned bucketNumber = fullHashValue & (bucketSize - 1);
205 // point to the start of the hashvalue segment.
206 unsigned* hashTable = reinterpret_cast<unsigned*>(mTable + mBuckets + 1);
208 unsigned probeAmt = 1;
211 StringEntry* bucketItem = mTable[bucketNumber];
212 // If we found an empty bucket, this key isn't in the table yet, return it.
215 hashTable[bucketNumber] = fullHashValue;
219 if(hashTable[bucketNumber] == fullHashValue)
221 // If the full hash value matches, check deeply for a match.
222 if(name == bucketItem->GetString())
229 // Okay, we didn't find the item. Probe to the next bucket.
230 bucketNumber = NextBucket(bucketNumber, bucketSize, probeAmt);
232 // Use quadratic probing, it has fewer clumping artifacts than linear
236 unsigned RehashTable(unsigned bucketNumber)
239 unsigned* hashTable = reinterpret_cast<unsigned*>(mTable + mBuckets + 1);
241 // If the hash table is now more than 3/4 full.
242 // grow/rehash the table.
243 if((mItems * 4) > (mBuckets * 3))
245 newSize = mBuckets * 2;
251 unsigned newBucketNumber = bucketNumber;
253 // Allocate one extra bucket which will always be non-empty.
254 auto newTable = static_cast<StringEntry**>(calloc(newSize + 1, sizeof(StringEntry*) + sizeof(unsigned)));
255 DALI_ASSERT_ALWAYS(newTable && "calloc returned nullptr");
257 // point to the start of the hashvalue segment. as the pointer is of type StringEntry* , but the
258 // second segment keeps only unsigned data hence the reinterpret_cast.
259 unsigned* newHashTable = reinterpret_cast<unsigned*>(newTable + newSize + 1);
261 // Rehash all the items into their new buckets.
262 for(unsigned i = 0, j = mBuckets; i != j; ++i)
264 StringEntry* bucket = mTable[i];
267 // Fast case, bucket available.
268 unsigned fullHash = hashTable[i];
269 unsigned newBucket = fullHash & (newSize - 1);
270 if(!newTable[newBucket])
272 newTable[fullHash & (newSize - 1)] = bucket;
273 newHashTable[fullHash & (newSize - 1)] = fullHash;
274 if(i == bucketNumber)
275 newBucketNumber = newBucket;
279 // Otherwise probe for a spot.
280 unsigned ProbeSize = 1;
283 newBucket = NextBucket(newBucket, newSize, ProbeSize++);
284 } while(newTable[newBucket]);
286 // Finally found a slot. Fill it in.
287 newTable[newBucket] = bucket;
288 newHashTable[newBucket] = fullHash;
289 if(i == bucketNumber)
290 newBucketNumber = newBucket;
298 return newBucketNumber;
303 ArenaAllocator mAllocator;
304 StringEntry** mTable{nullptr};
305 unsigned mBuckets{0};
311 Dali::Internal::ConstString::ConstString(std::string_view str)
313 mString = StringPool::Instance().Intern(str);
316 size_t Dali::Internal::ConstString::GetLength() const
318 return mString ? StringEntry::GetStringEntryFromData(mString).getStringLength() : 0;
321 void Dali::Internal::ConstString::SetString(std::string_view str)
329 mString = StringPool::Instance().Intern(str);