1*51c0b2f7Stbbdev /* 2*51c0b2f7Stbbdev Copyright (c) 2005-2020 Intel Corporation 3*51c0b2f7Stbbdev 4*51c0b2f7Stbbdev Licensed under the Apache License, Version 2.0 (the "License"); 5*51c0b2f7Stbbdev you may not use this file except in compliance with the License. 6*51c0b2f7Stbbdev You may obtain a copy of the License at 7*51c0b2f7Stbbdev 8*51c0b2f7Stbbdev http://www.apache.org/licenses/LICENSE-2.0 9*51c0b2f7Stbbdev 10*51c0b2f7Stbbdev Unless required by applicable law or agreed to in writing, software 11*51c0b2f7Stbbdev distributed under the License is distributed on an "AS IS" BASIS, 12*51c0b2f7Stbbdev WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*51c0b2f7Stbbdev See the License for the specific language governing permissions and 14*51c0b2f7Stbbdev limitations under the License. 15*51c0b2f7Stbbdev */ 16*51c0b2f7Stbbdev 17*51c0b2f7Stbbdev #include "tbbmalloc_internal.h" 18*51c0b2f7Stbbdev #include "../tbb/environment.h" 19*51c0b2f7Stbbdev 20*51c0b2f7Stbbdev #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) 21*51c0b2f7Stbbdev // Suppress warning: unary minus operator applied to unsigned type, result still unsigned 22*51c0b2f7Stbbdev // TBB_REVAMP_TODO: review this warning 23*51c0b2f7Stbbdev #pragma warning(push) 24*51c0b2f7Stbbdev #pragma warning(disable:4146) 25*51c0b2f7Stbbdev #endif 26*51c0b2f7Stbbdev 27*51c0b2f7Stbbdev /******************************* Allocation of large objects *********************************************/ 28*51c0b2f7Stbbdev 29*51c0b2f7Stbbdev namespace rml { 30*51c0b2f7Stbbdev namespace internal { 31*51c0b2f7Stbbdev 32*51c0b2f7Stbbdev /* ---------------------------- Large Object cache init section ---------------------------------------- */ 33*51c0b2f7Stbbdev 34*51c0b2f7Stbbdev void LargeObjectCache::init(ExtMemoryPool *memPool) 35*51c0b2f7Stbbdev { 36*51c0b2f7Stbbdev extMemPool = memPool; 37*51c0b2f7Stbbdev // scalable_allocation_mode can be called before allocator initialization, respect this manual request 38*51c0b2f7Stbbdev if (hugeSizeThreshold == 0) { 39*51c0b2f7Stbbdev // Huge size threshold initialization if environment variable was set 40*51c0b2f7Stbbdev long requestedThreshold = tbb::detail::r1::GetIntegralEnvironmentVariable("TBB_MALLOC_SET_HUGE_SIZE_THRESHOLD"); 41*51c0b2f7Stbbdev // Read valid env or initialize by default with max possible values 42*51c0b2f7Stbbdev if (requestedThreshold != -1) { 43*51c0b2f7Stbbdev setHugeSizeThreshold(requestedThreshold); 44*51c0b2f7Stbbdev } else { 45*51c0b2f7Stbbdev setHugeSizeThreshold(maxHugeSize); 46*51c0b2f7Stbbdev } 47*51c0b2f7Stbbdev } 48*51c0b2f7Stbbdev } 49*51c0b2f7Stbbdev 50*51c0b2f7Stbbdev /* ----------------------------- Huge size threshold settings ----------------------------------------- */ 51*51c0b2f7Stbbdev 52*51c0b2f7Stbbdev void LargeObjectCache::setHugeSizeThreshold(size_t value) 53*51c0b2f7Stbbdev { 54*51c0b2f7Stbbdev // Valid in the huge cache range: [MaxLargeSize, MaxHugeSize]. 55*51c0b2f7Stbbdev if (value <= maxHugeSize) { 56*51c0b2f7Stbbdev hugeSizeThreshold = value >= maxLargeSize ? alignToBin(value) : maxLargeSize; 57*51c0b2f7Stbbdev 58*51c0b2f7Stbbdev // Calculate local indexes for the global threshold size (for fast search inside a regular cleanup) 59*51c0b2f7Stbbdev largeCache.hugeSizeThresholdIdx = LargeCacheType::numBins; 60*51c0b2f7Stbbdev hugeCache.hugeSizeThresholdIdx = HugeCacheType::sizeToIdx(hugeSizeThreshold); 61*51c0b2f7Stbbdev } 62*51c0b2f7Stbbdev } 63*51c0b2f7Stbbdev 64*51c0b2f7Stbbdev bool LargeObjectCache::sizeInCacheRange(size_t size) 65*51c0b2f7Stbbdev { 66*51c0b2f7Stbbdev return size <= maxHugeSize && (size <= defaultMaxHugeSize || size >= hugeSizeThreshold); 67*51c0b2f7Stbbdev } 68*51c0b2f7Stbbdev 69*51c0b2f7Stbbdev /* ----------------------------------------------------------------------------------------------------- */ 70*51c0b2f7Stbbdev 71*51c0b2f7Stbbdev /* The functor called by the aggregator for the operation list */ 72*51c0b2f7Stbbdev template<typename Props> 73*51c0b2f7Stbbdev class CacheBinFunctor { 74*51c0b2f7Stbbdev typename LargeObjectCacheImpl<Props>::CacheBin *const bin; 75*51c0b2f7Stbbdev ExtMemoryPool *const extMemPool; 76*51c0b2f7Stbbdev typename LargeObjectCacheImpl<Props>::BinBitMask *const bitMask; 77*51c0b2f7Stbbdev const int idx; 78*51c0b2f7Stbbdev 79*51c0b2f7Stbbdev LargeMemoryBlock *toRelease; 80*51c0b2f7Stbbdev bool needCleanup; 81*51c0b2f7Stbbdev uintptr_t currTime; 82*51c0b2f7Stbbdev 83*51c0b2f7Stbbdev /* Do preprocessing under the operation list. */ 84*51c0b2f7Stbbdev /* All the OP_PUT_LIST operations are merged in the one operation. 85*51c0b2f7Stbbdev All OP_GET operations are merged with the OP_PUT_LIST operations but 86*51c0b2f7Stbbdev it demands the update of the moving average value in the bin. 87*51c0b2f7Stbbdev Only the last OP_CLEAN_TO_THRESHOLD operation has sense. 88*51c0b2f7Stbbdev The OP_CLEAN_ALL operation also should be performed only once. 89*51c0b2f7Stbbdev Moreover it cancels the OP_CLEAN_TO_THRESHOLD operation. */ 90*51c0b2f7Stbbdev class OperationPreprocessor { 91*51c0b2f7Stbbdev // TODO: remove the dependency on CacheBin. 92*51c0b2f7Stbbdev typename LargeObjectCacheImpl<Props>::CacheBin *const bin; 93*51c0b2f7Stbbdev 94*51c0b2f7Stbbdev /* Contains the relative time in the operation list. 95*51c0b2f7Stbbdev It counts in the reverse order since the aggregator also 96*51c0b2f7Stbbdev provides operations in the reverse order. */ 97*51c0b2f7Stbbdev uintptr_t lclTime; 98*51c0b2f7Stbbdev 99*51c0b2f7Stbbdev /* opGet contains only OP_GET operations which cannot be merge with OP_PUT operations 100*51c0b2f7Stbbdev opClean contains all OP_CLEAN_TO_THRESHOLD and OP_CLEAN_ALL operations. */ 101*51c0b2f7Stbbdev CacheBinOperation *opGet, *opClean; 102*51c0b2f7Stbbdev /* The time of the last OP_CLEAN_TO_THRESHOLD operations */ 103*51c0b2f7Stbbdev uintptr_t cleanTime; 104*51c0b2f7Stbbdev 105*51c0b2f7Stbbdev /* lastGetOpTime - the time of the last OP_GET operation. 106*51c0b2f7Stbbdev lastGet - the same meaning as CacheBin::lastGet */ 107*51c0b2f7Stbbdev uintptr_t lastGetOpTime, lastGet; 108*51c0b2f7Stbbdev 109*51c0b2f7Stbbdev /* The total sum of all usedSize changes requested with CBOP_UPDATE_USED_SIZE operations. */ 110*51c0b2f7Stbbdev size_t updateUsedSize; 111*51c0b2f7Stbbdev 112*51c0b2f7Stbbdev /* The list of blocks for the OP_PUT_LIST operation. */ 113*51c0b2f7Stbbdev LargeMemoryBlock *head, *tail; 114*51c0b2f7Stbbdev int putListNum; 115*51c0b2f7Stbbdev 116*51c0b2f7Stbbdev /* if the OP_CLEAN_ALL is requested. */ 117*51c0b2f7Stbbdev bool isCleanAll; 118*51c0b2f7Stbbdev 119*51c0b2f7Stbbdev inline void commitOperation(CacheBinOperation *op) const; 120*51c0b2f7Stbbdev inline void addOpToOpList(CacheBinOperation *op, CacheBinOperation **opList) const; 121*51c0b2f7Stbbdev bool getFromPutList(CacheBinOperation* opGet, uintptr_t currTime); 122*51c0b2f7Stbbdev void addToPutList( LargeMemoryBlock *head, LargeMemoryBlock *tail, int num ); 123*51c0b2f7Stbbdev 124*51c0b2f7Stbbdev public: 125*51c0b2f7Stbbdev OperationPreprocessor(typename LargeObjectCacheImpl<Props>::CacheBin *bin) : 126*51c0b2f7Stbbdev bin(bin), lclTime(0), opGet(NULL), opClean(NULL), cleanTime(0), 127*51c0b2f7Stbbdev lastGetOpTime(0), updateUsedSize(0), head(NULL), isCleanAll(false) {} 128*51c0b2f7Stbbdev void operator()(CacheBinOperation* opList); 129*51c0b2f7Stbbdev uintptr_t getTimeRange() const { return -lclTime; } 130*51c0b2f7Stbbdev 131*51c0b2f7Stbbdev friend class CacheBinFunctor; 132*51c0b2f7Stbbdev }; 133*51c0b2f7Stbbdev 134*51c0b2f7Stbbdev public: 135*51c0b2f7Stbbdev CacheBinFunctor(typename LargeObjectCacheImpl<Props>::CacheBin *bin, ExtMemoryPool *extMemPool, 136*51c0b2f7Stbbdev typename LargeObjectCacheImpl<Props>::BinBitMask *bitMask, int idx) : 137*51c0b2f7Stbbdev bin(bin), extMemPool(extMemPool), bitMask(bitMask), idx(idx), toRelease(NULL), needCleanup(false) {} 138*51c0b2f7Stbbdev void operator()(CacheBinOperation* opList); 139*51c0b2f7Stbbdev 140*51c0b2f7Stbbdev bool isCleanupNeeded() const { return needCleanup; } 141*51c0b2f7Stbbdev LargeMemoryBlock *getToRelease() const { return toRelease; } 142*51c0b2f7Stbbdev uintptr_t getCurrTime() const { return currTime; } 143*51c0b2f7Stbbdev }; 144*51c0b2f7Stbbdev 145*51c0b2f7Stbbdev /* ---------------- Cache Bin Aggregator Operation Helpers ---------------- */ 146*51c0b2f7Stbbdev 147*51c0b2f7Stbbdev // The list of structures which describe the operation data 148*51c0b2f7Stbbdev struct OpGet { 149*51c0b2f7Stbbdev static const CacheBinOperationType type = CBOP_GET; 150*51c0b2f7Stbbdev LargeMemoryBlock **res; 151*51c0b2f7Stbbdev size_t size; 152*51c0b2f7Stbbdev uintptr_t currTime; 153*51c0b2f7Stbbdev }; 154*51c0b2f7Stbbdev 155*51c0b2f7Stbbdev struct OpPutList { 156*51c0b2f7Stbbdev static const CacheBinOperationType type = CBOP_PUT_LIST; 157*51c0b2f7Stbbdev LargeMemoryBlock *head; 158*51c0b2f7Stbbdev }; 159*51c0b2f7Stbbdev 160*51c0b2f7Stbbdev struct OpCleanToThreshold { 161*51c0b2f7Stbbdev static const CacheBinOperationType type = CBOP_CLEAN_TO_THRESHOLD; 162*51c0b2f7Stbbdev LargeMemoryBlock **res; 163*51c0b2f7Stbbdev uintptr_t currTime; 164*51c0b2f7Stbbdev }; 165*51c0b2f7Stbbdev 166*51c0b2f7Stbbdev struct OpCleanAll { 167*51c0b2f7Stbbdev static const CacheBinOperationType type = CBOP_CLEAN_ALL; 168*51c0b2f7Stbbdev LargeMemoryBlock **res; 169*51c0b2f7Stbbdev }; 170*51c0b2f7Stbbdev 171*51c0b2f7Stbbdev struct OpUpdateUsedSize { 172*51c0b2f7Stbbdev static const CacheBinOperationType type = CBOP_UPDATE_USED_SIZE; 173*51c0b2f7Stbbdev size_t size; 174*51c0b2f7Stbbdev }; 175*51c0b2f7Stbbdev 176*51c0b2f7Stbbdev union CacheBinOperationData { 177*51c0b2f7Stbbdev private: 178*51c0b2f7Stbbdev OpGet opGet; 179*51c0b2f7Stbbdev OpPutList opPutList; 180*51c0b2f7Stbbdev OpCleanToThreshold opCleanToThreshold; 181*51c0b2f7Stbbdev OpCleanAll opCleanAll; 182*51c0b2f7Stbbdev OpUpdateUsedSize opUpdateUsedSize; 183*51c0b2f7Stbbdev }; 184*51c0b2f7Stbbdev 185*51c0b2f7Stbbdev // Forward declarations 186*51c0b2f7Stbbdev template <typename OpTypeData> OpTypeData& opCast(CacheBinOperation &op); 187*51c0b2f7Stbbdev 188*51c0b2f7Stbbdev // Describes the aggregator operation 189*51c0b2f7Stbbdev struct CacheBinOperation : public MallocAggregatedOperation<CacheBinOperation>::type { 190*51c0b2f7Stbbdev CacheBinOperationType type; 191*51c0b2f7Stbbdev 192*51c0b2f7Stbbdev template <typename OpTypeData> 193*51c0b2f7Stbbdev CacheBinOperation(OpTypeData &d, CacheBinOperationStatus st = CBST_WAIT) { 194*51c0b2f7Stbbdev opCast<OpTypeData>(*this) = d; 195*51c0b2f7Stbbdev type = OpTypeData::type; 196*51c0b2f7Stbbdev MallocAggregatedOperation<CacheBinOperation>::type::status = st; 197*51c0b2f7Stbbdev } 198*51c0b2f7Stbbdev private: 199*51c0b2f7Stbbdev CacheBinOperationData data; 200*51c0b2f7Stbbdev 201*51c0b2f7Stbbdev template <typename OpTypeData> 202*51c0b2f7Stbbdev friend OpTypeData& opCast(CacheBinOperation &op); 203*51c0b2f7Stbbdev }; 204*51c0b2f7Stbbdev 205*51c0b2f7Stbbdev // The opCast function can be the member of CacheBinOperation but it will have 206*51c0b2f7Stbbdev // small stylistic ambiguity: it will look like a getter (with a cast) for the 207*51c0b2f7Stbbdev // CacheBinOperation::data data member but it should return a reference to 208*51c0b2f7Stbbdev // simplify the code from a lot of getter/setter calls. So the global cast in 209*51c0b2f7Stbbdev // the style of static_cast (or reinterpret_cast) seems to be more readable and 210*51c0b2f7Stbbdev // have more explicit semantic. 211*51c0b2f7Stbbdev template <typename OpTypeData> 212*51c0b2f7Stbbdev OpTypeData& opCast(CacheBinOperation &op) { 213*51c0b2f7Stbbdev return *reinterpret_cast<OpTypeData*>(&op.data); 214*51c0b2f7Stbbdev } 215*51c0b2f7Stbbdev 216*51c0b2f7Stbbdev /* ------------------------------------------------------------------------ */ 217*51c0b2f7Stbbdev 218*51c0b2f7Stbbdev #if __TBB_MALLOC_LOCACHE_STAT 219*51c0b2f7Stbbdev //intptr_t mallocCalls, cacheHits; 220*51c0b2f7Stbbdev std::atomic<intptr_t> mallocCalls, cacheHits; 221*51c0b2f7Stbbdev //intptr_t memAllocKB, memHitKB; 222*51c0b2f7Stbbdev std::atomic<intptr_t> memAllocKB, memHitKB; 223*51c0b2f7Stbbdev #endif 224*51c0b2f7Stbbdev 225*51c0b2f7Stbbdev inline bool lessThanWithOverflow(intptr_t a, intptr_t b) 226*51c0b2f7Stbbdev { 227*51c0b2f7Stbbdev return (a < b && (b - a < UINTPTR_MAX/2)) || 228*51c0b2f7Stbbdev (a > b && (a - b > UINTPTR_MAX/2)); 229*51c0b2f7Stbbdev } 230*51c0b2f7Stbbdev 231*51c0b2f7Stbbdev /* ----------------------------------- Operation processing methods ------------------------------------ */ 232*51c0b2f7Stbbdev 233*51c0b2f7Stbbdev template<typename Props> void CacheBinFunctor<Props>:: 234*51c0b2f7Stbbdev OperationPreprocessor::commitOperation(CacheBinOperation *op) const 235*51c0b2f7Stbbdev { 236*51c0b2f7Stbbdev // FencedStore( (intptr_t&)(op->status), CBST_DONE ); 237*51c0b2f7Stbbdev op->status.store(CBST_DONE, std::memory_order_release); 238*51c0b2f7Stbbdev } 239*51c0b2f7Stbbdev 240*51c0b2f7Stbbdev template<typename Props> void CacheBinFunctor<Props>:: 241*51c0b2f7Stbbdev OperationPreprocessor::addOpToOpList(CacheBinOperation *op, CacheBinOperation **opList) const 242*51c0b2f7Stbbdev { 243*51c0b2f7Stbbdev op->next = *opList; 244*51c0b2f7Stbbdev *opList = op; 245*51c0b2f7Stbbdev } 246*51c0b2f7Stbbdev 247*51c0b2f7Stbbdev template<typename Props> bool CacheBinFunctor<Props>:: 248*51c0b2f7Stbbdev OperationPreprocessor::getFromPutList(CacheBinOperation *opGet, uintptr_t currTime) 249*51c0b2f7Stbbdev { 250*51c0b2f7Stbbdev if ( head ) { 251*51c0b2f7Stbbdev uintptr_t age = head->age; 252*51c0b2f7Stbbdev LargeMemoryBlock *next = head->next; 253*51c0b2f7Stbbdev *opCast<OpGet>(*opGet).res = head; 254*51c0b2f7Stbbdev commitOperation( opGet ); 255*51c0b2f7Stbbdev head = next; 256*51c0b2f7Stbbdev putListNum--; 257*51c0b2f7Stbbdev MALLOC_ASSERT( putListNum>=0, ASSERT_TEXT ); 258*51c0b2f7Stbbdev 259*51c0b2f7Stbbdev // use moving average with current hit interval 260*51c0b2f7Stbbdev bin->updateMeanHitRange( currTime - age ); 261*51c0b2f7Stbbdev return true; 262*51c0b2f7Stbbdev } 263*51c0b2f7Stbbdev return false; 264*51c0b2f7Stbbdev } 265*51c0b2f7Stbbdev 266*51c0b2f7Stbbdev template<typename Props> void CacheBinFunctor<Props>:: 267*51c0b2f7Stbbdev OperationPreprocessor::addToPutList(LargeMemoryBlock *h, LargeMemoryBlock *t, int num) 268*51c0b2f7Stbbdev { 269*51c0b2f7Stbbdev if ( head ) { 270*51c0b2f7Stbbdev MALLOC_ASSERT( tail, ASSERT_TEXT ); 271*51c0b2f7Stbbdev tail->next = h; 272*51c0b2f7Stbbdev h->prev = tail; 273*51c0b2f7Stbbdev tail = t; 274*51c0b2f7Stbbdev putListNum += num; 275*51c0b2f7Stbbdev } else { 276*51c0b2f7Stbbdev head = h; 277*51c0b2f7Stbbdev tail = t; 278*51c0b2f7Stbbdev putListNum = num; 279*51c0b2f7Stbbdev } 280*51c0b2f7Stbbdev } 281*51c0b2f7Stbbdev 282*51c0b2f7Stbbdev template<typename Props> void CacheBinFunctor<Props>:: 283*51c0b2f7Stbbdev OperationPreprocessor::operator()(CacheBinOperation* opList) 284*51c0b2f7Stbbdev { 285*51c0b2f7Stbbdev for ( CacheBinOperation *op = opList, *opNext; op; op = opNext ) { 286*51c0b2f7Stbbdev opNext = op->next; 287*51c0b2f7Stbbdev switch ( op->type ) { 288*51c0b2f7Stbbdev case CBOP_GET: 289*51c0b2f7Stbbdev { 290*51c0b2f7Stbbdev lclTime--; 291*51c0b2f7Stbbdev if ( !lastGetOpTime ) { 292*51c0b2f7Stbbdev lastGetOpTime = lclTime; 293*51c0b2f7Stbbdev lastGet = 0; 294*51c0b2f7Stbbdev } else if ( !lastGet ) lastGet = lclTime; 295*51c0b2f7Stbbdev 296*51c0b2f7Stbbdev if ( !getFromPutList(op,lclTime) ) { 297*51c0b2f7Stbbdev opCast<OpGet>(*op).currTime = lclTime; 298*51c0b2f7Stbbdev addOpToOpList( op, &opGet ); 299*51c0b2f7Stbbdev } 300*51c0b2f7Stbbdev } 301*51c0b2f7Stbbdev break; 302*51c0b2f7Stbbdev 303*51c0b2f7Stbbdev case CBOP_PUT_LIST: 304*51c0b2f7Stbbdev { 305*51c0b2f7Stbbdev LargeMemoryBlock *head = opCast<OpPutList>(*op).head; 306*51c0b2f7Stbbdev LargeMemoryBlock *curr = head, *prev = NULL; 307*51c0b2f7Stbbdev 308*51c0b2f7Stbbdev int num = 0; 309*51c0b2f7Stbbdev do { 310*51c0b2f7Stbbdev // we do not kept prev pointers during assigning blocks to bins, set them now 311*51c0b2f7Stbbdev curr->prev = prev; 312*51c0b2f7Stbbdev 313*51c0b2f7Stbbdev // Save the local times to the memory blocks. Local times are necessary 314*51c0b2f7Stbbdev // for the getFromPutList function which updates the hit range value in 315*51c0b2f7Stbbdev // CacheBin when OP_GET and OP_PUT_LIST operations are merged successfully. 316*51c0b2f7Stbbdev // The age will be updated to the correct global time after preprocessing 317*51c0b2f7Stbbdev // when global cache time is updated. 318*51c0b2f7Stbbdev curr->age = --lclTime; 319*51c0b2f7Stbbdev 320*51c0b2f7Stbbdev prev = curr; 321*51c0b2f7Stbbdev num += 1; 322*51c0b2f7Stbbdev 323*51c0b2f7Stbbdev STAT_increment(getThreadId(), ThreadCommonCounters, cacheLargeObj); 324*51c0b2f7Stbbdev } while ((curr = curr->next) != NULL); 325*51c0b2f7Stbbdev 326*51c0b2f7Stbbdev LargeMemoryBlock *tail = prev; 327*51c0b2f7Stbbdev addToPutList(head, tail, num); 328*51c0b2f7Stbbdev 329*51c0b2f7Stbbdev while ( opGet ) { 330*51c0b2f7Stbbdev CacheBinOperation *next = opGet->next; 331*51c0b2f7Stbbdev if ( !getFromPutList(opGet, opCast<OpGet>(*opGet).currTime) ) 332*51c0b2f7Stbbdev break; 333*51c0b2f7Stbbdev opGet = next; 334*51c0b2f7Stbbdev } 335*51c0b2f7Stbbdev } 336*51c0b2f7Stbbdev break; 337*51c0b2f7Stbbdev 338*51c0b2f7Stbbdev case CBOP_UPDATE_USED_SIZE: 339*51c0b2f7Stbbdev updateUsedSize += opCast<OpUpdateUsedSize>(*op).size; 340*51c0b2f7Stbbdev commitOperation( op ); 341*51c0b2f7Stbbdev break; 342*51c0b2f7Stbbdev 343*51c0b2f7Stbbdev case CBOP_CLEAN_ALL: 344*51c0b2f7Stbbdev isCleanAll = true; 345*51c0b2f7Stbbdev addOpToOpList( op, &opClean ); 346*51c0b2f7Stbbdev break; 347*51c0b2f7Stbbdev 348*51c0b2f7Stbbdev case CBOP_CLEAN_TO_THRESHOLD: 349*51c0b2f7Stbbdev { 350*51c0b2f7Stbbdev uintptr_t currTime = opCast<OpCleanToThreshold>(*op).currTime; 351*51c0b2f7Stbbdev // We don't worry about currTime overflow since it is a rare 352*51c0b2f7Stbbdev // occurrence and doesn't affect correctness 353*51c0b2f7Stbbdev cleanTime = cleanTime < currTime ? currTime : cleanTime; 354*51c0b2f7Stbbdev addOpToOpList( op, &opClean ); 355*51c0b2f7Stbbdev } 356*51c0b2f7Stbbdev break; 357*51c0b2f7Stbbdev 358*51c0b2f7Stbbdev default: 359*51c0b2f7Stbbdev MALLOC_ASSERT( false, "Unknown operation." ); 360*51c0b2f7Stbbdev } 361*51c0b2f7Stbbdev } 362*51c0b2f7Stbbdev MALLOC_ASSERT( !( opGet && head ), "Not all put/get pairs are processed!" ); 363*51c0b2f7Stbbdev } 364*51c0b2f7Stbbdev 365*51c0b2f7Stbbdev template<typename Props> void CacheBinFunctor<Props>::operator()(CacheBinOperation* opList) 366*51c0b2f7Stbbdev { 367*51c0b2f7Stbbdev MALLOC_ASSERT( opList, "Empty operation list is passed into operation handler." ); 368*51c0b2f7Stbbdev 369*51c0b2f7Stbbdev OperationPreprocessor prep(bin); 370*51c0b2f7Stbbdev prep(opList); 371*51c0b2f7Stbbdev 372*51c0b2f7Stbbdev if ( uintptr_t timeRange = prep.getTimeRange() ) { 373*51c0b2f7Stbbdev uintptr_t startTime = extMemPool->loc.getCurrTimeRange(timeRange); 374*51c0b2f7Stbbdev // endTime is used as the current (base) time since the local time is negative. 375*51c0b2f7Stbbdev uintptr_t endTime = startTime + timeRange; 376*51c0b2f7Stbbdev 377*51c0b2f7Stbbdev if ( prep.lastGetOpTime && prep.lastGet ) bin->setLastGet(prep.lastGet+endTime); 378*51c0b2f7Stbbdev 379*51c0b2f7Stbbdev if ( CacheBinOperation *opGet = prep.opGet ) { 380*51c0b2f7Stbbdev bool isEmpty = false; 381*51c0b2f7Stbbdev do { 382*51c0b2f7Stbbdev #if __TBB_MALLOC_WHITEBOX_TEST 383*51c0b2f7Stbbdev tbbmalloc_whitebox::locGetProcessed++; 384*51c0b2f7Stbbdev #endif 385*51c0b2f7Stbbdev const OpGet &opGetData = opCast<OpGet>(*opGet); 386*51c0b2f7Stbbdev if ( !isEmpty ) { 387*51c0b2f7Stbbdev if ( LargeMemoryBlock *res = bin->get() ) { 388*51c0b2f7Stbbdev uintptr_t getTime = opGetData.currTime + endTime; 389*51c0b2f7Stbbdev // use moving average with current hit interval 390*51c0b2f7Stbbdev bin->updateMeanHitRange( getTime - res->age); 391*51c0b2f7Stbbdev bin->updateCachedSize( -opGetData.size ); 392*51c0b2f7Stbbdev *opGetData.res = res; 393*51c0b2f7Stbbdev } else { 394*51c0b2f7Stbbdev isEmpty = true; 395*51c0b2f7Stbbdev uintptr_t lastGetOpTime = prep.lastGetOpTime+endTime; 396*51c0b2f7Stbbdev bin->forgetOutdatedState(lastGetOpTime); 397*51c0b2f7Stbbdev bin->updateAgeThreshold(lastGetOpTime); 398*51c0b2f7Stbbdev } 399*51c0b2f7Stbbdev } 400*51c0b2f7Stbbdev 401*51c0b2f7Stbbdev CacheBinOperation *opNext = opGet->next; 402*51c0b2f7Stbbdev bin->updateUsedSize( opGetData.size, bitMask, idx ); 403*51c0b2f7Stbbdev prep.commitOperation( opGet ); 404*51c0b2f7Stbbdev opGet = opNext; 405*51c0b2f7Stbbdev } while ( opGet ); 406*51c0b2f7Stbbdev if ( prep.lastGetOpTime ) 407*51c0b2f7Stbbdev bin->setLastGet( prep.lastGetOpTime + endTime ); 408*51c0b2f7Stbbdev } else if ( LargeMemoryBlock *curr = prep.head ) { 409*51c0b2f7Stbbdev curr->prev = NULL; 410*51c0b2f7Stbbdev while ( curr ) { 411*51c0b2f7Stbbdev // Update local times to global times 412*51c0b2f7Stbbdev curr->age += endTime; 413*51c0b2f7Stbbdev curr=curr->next; 414*51c0b2f7Stbbdev } 415*51c0b2f7Stbbdev #if __TBB_MALLOC_WHITEBOX_TEST 416*51c0b2f7Stbbdev tbbmalloc_whitebox::locPutProcessed+=prep.putListNum; 417*51c0b2f7Stbbdev #endif 418*51c0b2f7Stbbdev toRelease = bin->putList(prep.head, prep.tail, bitMask, idx, prep.putListNum, extMemPool->loc.hugeSizeThreshold); 419*51c0b2f7Stbbdev } 420*51c0b2f7Stbbdev needCleanup = extMemPool->loc.isCleanupNeededOnRange(timeRange, startTime); 421*51c0b2f7Stbbdev currTime = endTime - 1; 422*51c0b2f7Stbbdev } 423*51c0b2f7Stbbdev 424*51c0b2f7Stbbdev if ( CacheBinOperation *opClean = prep.opClean ) { 425*51c0b2f7Stbbdev if ( prep.isCleanAll ) 426*51c0b2f7Stbbdev *opCast<OpCleanAll>(*opClean).res = bin->cleanAll(bitMask, idx); 427*51c0b2f7Stbbdev else 428*51c0b2f7Stbbdev *opCast<OpCleanToThreshold>(*opClean).res = bin->cleanToThreshold(prep.cleanTime, bitMask, idx); 429*51c0b2f7Stbbdev 430*51c0b2f7Stbbdev CacheBinOperation *opNext = opClean->next; 431*51c0b2f7Stbbdev prep.commitOperation( opClean ); 432*51c0b2f7Stbbdev 433*51c0b2f7Stbbdev while ((opClean = opNext) != NULL) { 434*51c0b2f7Stbbdev opNext = opClean->next; 435*51c0b2f7Stbbdev prep.commitOperation(opClean); 436*51c0b2f7Stbbdev } 437*51c0b2f7Stbbdev } 438*51c0b2f7Stbbdev 439*51c0b2f7Stbbdev if ( size_t size = prep.updateUsedSize ) 440*51c0b2f7Stbbdev bin->updateUsedSize(size, bitMask, idx); 441*51c0b2f7Stbbdev } 442*51c0b2f7Stbbdev /* ----------------------------------------------------------------------------------------------------- */ 443*51c0b2f7Stbbdev /* --------------------------- Methods for creating and executing operations --------------------------- */ 444*51c0b2f7Stbbdev template<typename Props> void LargeObjectCacheImpl<Props>:: 445*51c0b2f7Stbbdev CacheBin::ExecuteOperation(CacheBinOperation *op, ExtMemoryPool *extMemPool, BinBitMask *bitMask, int idx, bool longLifeTime) 446*51c0b2f7Stbbdev { 447*51c0b2f7Stbbdev CacheBinFunctor<Props> func( this, extMemPool, bitMask, idx ); 448*51c0b2f7Stbbdev aggregator.execute( op, func, longLifeTime ); 449*51c0b2f7Stbbdev 450*51c0b2f7Stbbdev if ( LargeMemoryBlock *toRelease = func.getToRelease()) { 451*51c0b2f7Stbbdev extMemPool->backend.returnLargeObject(toRelease); 452*51c0b2f7Stbbdev } 453*51c0b2f7Stbbdev 454*51c0b2f7Stbbdev if ( func.isCleanupNeeded() ) { 455*51c0b2f7Stbbdev extMemPool->loc.doCleanup( func.getCurrTime(), /*doThreshDecr=*/false); 456*51c0b2f7Stbbdev } 457*51c0b2f7Stbbdev } 458*51c0b2f7Stbbdev 459*51c0b2f7Stbbdev template<typename Props> LargeMemoryBlock *LargeObjectCacheImpl<Props>:: 460*51c0b2f7Stbbdev CacheBin::get(ExtMemoryPool *extMemPool, size_t size, BinBitMask *bitMask, int idx) 461*51c0b2f7Stbbdev { 462*51c0b2f7Stbbdev LargeMemoryBlock *lmb=NULL; 463*51c0b2f7Stbbdev OpGet data = {&lmb, size}; 464*51c0b2f7Stbbdev CacheBinOperation op(data); 465*51c0b2f7Stbbdev ExecuteOperation( &op, extMemPool, bitMask, idx ); 466*51c0b2f7Stbbdev return lmb; 467*51c0b2f7Stbbdev } 468*51c0b2f7Stbbdev 469*51c0b2f7Stbbdev template<typename Props> void LargeObjectCacheImpl<Props>:: 470*51c0b2f7Stbbdev CacheBin::putList(ExtMemoryPool *extMemPool, LargeMemoryBlock *head, BinBitMask *bitMask, int idx) 471*51c0b2f7Stbbdev { 472*51c0b2f7Stbbdev MALLOC_ASSERT(sizeof(LargeMemoryBlock)+sizeof(CacheBinOperation)<=head->unalignedSize, "CacheBinOperation is too large to be placed in LargeMemoryBlock!"); 473*51c0b2f7Stbbdev 474*51c0b2f7Stbbdev OpPutList data = {head}; 475*51c0b2f7Stbbdev CacheBinOperation *op = new (head+1) CacheBinOperation(data, CBST_NOWAIT); 476*51c0b2f7Stbbdev ExecuteOperation( op, extMemPool, bitMask, idx, false ); 477*51c0b2f7Stbbdev } 478*51c0b2f7Stbbdev 479*51c0b2f7Stbbdev template<typename Props> bool LargeObjectCacheImpl<Props>:: 480*51c0b2f7Stbbdev CacheBin::cleanToThreshold(ExtMemoryPool *extMemPool, BinBitMask *bitMask, uintptr_t currTime, int idx) 481*51c0b2f7Stbbdev { 482*51c0b2f7Stbbdev LargeMemoryBlock *toRelease = NULL; 483*51c0b2f7Stbbdev 484*51c0b2f7Stbbdev /* oldest may be more recent then age, that's why cast to signed type 485*51c0b2f7Stbbdev was used. age overflow is also processed correctly. */ 486*51c0b2f7Stbbdev if (last && (intptr_t)(currTime - oldest) > ageThreshold) { 487*51c0b2f7Stbbdev OpCleanToThreshold data = {&toRelease, currTime}; 488*51c0b2f7Stbbdev CacheBinOperation op(data); 489*51c0b2f7Stbbdev ExecuteOperation( &op, extMemPool, bitMask, idx ); 490*51c0b2f7Stbbdev } 491*51c0b2f7Stbbdev bool released = toRelease; 492*51c0b2f7Stbbdev 493*51c0b2f7Stbbdev Backend *backend = &extMemPool->backend; 494*51c0b2f7Stbbdev while ( toRelease ) { 495*51c0b2f7Stbbdev LargeMemoryBlock *helper = toRelease->next; 496*51c0b2f7Stbbdev backend->returnLargeObject(toRelease); 497*51c0b2f7Stbbdev toRelease = helper; 498*51c0b2f7Stbbdev } 499*51c0b2f7Stbbdev return released; 500*51c0b2f7Stbbdev } 501*51c0b2f7Stbbdev 502*51c0b2f7Stbbdev template<typename Props> bool LargeObjectCacheImpl<Props>:: 503*51c0b2f7Stbbdev CacheBin::releaseAllToBackend(ExtMemoryPool *extMemPool, BinBitMask *bitMask, int idx) 504*51c0b2f7Stbbdev { 505*51c0b2f7Stbbdev LargeMemoryBlock *toRelease = NULL; 506*51c0b2f7Stbbdev 507*51c0b2f7Stbbdev if (last) { 508*51c0b2f7Stbbdev OpCleanAll data = {&toRelease}; 509*51c0b2f7Stbbdev CacheBinOperation op(data); 510*51c0b2f7Stbbdev ExecuteOperation(&op, extMemPool, bitMask, idx); 511*51c0b2f7Stbbdev } 512*51c0b2f7Stbbdev bool released = toRelease; 513*51c0b2f7Stbbdev 514*51c0b2f7Stbbdev Backend *backend = &extMemPool->backend; 515*51c0b2f7Stbbdev while ( toRelease ) { 516*51c0b2f7Stbbdev LargeMemoryBlock *helper = toRelease->next; 517*51c0b2f7Stbbdev MALLOC_ASSERT(!helper || lessThanWithOverflow(helper->age, toRelease->age), 518*51c0b2f7Stbbdev ASSERT_TEXT); 519*51c0b2f7Stbbdev backend->returnLargeObject(toRelease); 520*51c0b2f7Stbbdev toRelease = helper; 521*51c0b2f7Stbbdev } 522*51c0b2f7Stbbdev return released; 523*51c0b2f7Stbbdev } 524*51c0b2f7Stbbdev 525*51c0b2f7Stbbdev template<typename Props> void LargeObjectCacheImpl<Props>:: 526*51c0b2f7Stbbdev CacheBin::updateUsedSize(ExtMemoryPool *extMemPool, size_t size, BinBitMask *bitMask, int idx) 527*51c0b2f7Stbbdev { 528*51c0b2f7Stbbdev OpUpdateUsedSize data = {size}; 529*51c0b2f7Stbbdev CacheBinOperation op(data); 530*51c0b2f7Stbbdev ExecuteOperation( &op, extMemPool, bitMask, idx ); 531*51c0b2f7Stbbdev } 532*51c0b2f7Stbbdev 533*51c0b2f7Stbbdev /* ------------------------------ Unsafe methods used with the aggregator ------------------------------ */ 534*51c0b2f7Stbbdev 535*51c0b2f7Stbbdev template<typename Props> LargeMemoryBlock *LargeObjectCacheImpl<Props>:: 536*51c0b2f7Stbbdev CacheBin::putList(LargeMemoryBlock *head, LargeMemoryBlock *tail, BinBitMask *bitMask, int idx, int num, size_t hugeSizeThreshold) 537*51c0b2f7Stbbdev { 538*51c0b2f7Stbbdev size_t size = head->unalignedSize; 539*51c0b2f7Stbbdev usedSize -= num*size; 540*51c0b2f7Stbbdev MALLOC_ASSERT( !last || (last->age != 0 && last->age != -1U), ASSERT_TEXT ); 541*51c0b2f7Stbbdev MALLOC_ASSERT( (tail==head && num==1) || (tail!=head && num>1), ASSERT_TEXT ); 542*51c0b2f7Stbbdev LargeMemoryBlock *toRelease = NULL; 543*51c0b2f7Stbbdev if (size < hugeSizeThreshold && !lastCleanedAge) { 544*51c0b2f7Stbbdev // 1st object of such size was released. 545*51c0b2f7Stbbdev // Not cache it, and remember when this occurs 546*51c0b2f7Stbbdev // to take into account during cache miss. 547*51c0b2f7Stbbdev lastCleanedAge = tail->age; 548*51c0b2f7Stbbdev toRelease = tail; 549*51c0b2f7Stbbdev tail = tail->prev; 550*51c0b2f7Stbbdev if (tail) 551*51c0b2f7Stbbdev tail->next = NULL; 552*51c0b2f7Stbbdev else 553*51c0b2f7Stbbdev head = NULL; 554*51c0b2f7Stbbdev num--; 555*51c0b2f7Stbbdev } 556*51c0b2f7Stbbdev if (num) { 557*51c0b2f7Stbbdev // add [head;tail] list to cache 558*51c0b2f7Stbbdev MALLOC_ASSERT( tail, ASSERT_TEXT ); 559*51c0b2f7Stbbdev tail->next = first; 560*51c0b2f7Stbbdev if (first) 561*51c0b2f7Stbbdev first->prev = tail; 562*51c0b2f7Stbbdev first = head; 563*51c0b2f7Stbbdev if (!last) { 564*51c0b2f7Stbbdev MALLOC_ASSERT(0 == oldest, ASSERT_TEXT); 565*51c0b2f7Stbbdev oldest = tail->age; 566*51c0b2f7Stbbdev last = tail; 567*51c0b2f7Stbbdev } 568*51c0b2f7Stbbdev 569*51c0b2f7Stbbdev cachedSize += num*size; 570*51c0b2f7Stbbdev } 571*51c0b2f7Stbbdev 572*51c0b2f7Stbbdev // No used object, and nothing in the bin, mark the bin as empty 573*51c0b2f7Stbbdev if (!usedSize && !first) 574*51c0b2f7Stbbdev bitMask->set(idx, false); 575*51c0b2f7Stbbdev 576*51c0b2f7Stbbdev return toRelease; 577*51c0b2f7Stbbdev } 578*51c0b2f7Stbbdev 579*51c0b2f7Stbbdev template<typename Props> LargeMemoryBlock *LargeObjectCacheImpl<Props>:: 580*51c0b2f7Stbbdev CacheBin::get() 581*51c0b2f7Stbbdev { 582*51c0b2f7Stbbdev LargeMemoryBlock *result=first; 583*51c0b2f7Stbbdev if (result) { 584*51c0b2f7Stbbdev first = result->next; 585*51c0b2f7Stbbdev if (first) 586*51c0b2f7Stbbdev first->prev = NULL; 587*51c0b2f7Stbbdev else { 588*51c0b2f7Stbbdev last = NULL; 589*51c0b2f7Stbbdev oldest = 0; 590*51c0b2f7Stbbdev } 591*51c0b2f7Stbbdev } 592*51c0b2f7Stbbdev 593*51c0b2f7Stbbdev return result; 594*51c0b2f7Stbbdev } 595*51c0b2f7Stbbdev 596*51c0b2f7Stbbdev template<typename Props> void LargeObjectCacheImpl<Props>:: 597*51c0b2f7Stbbdev CacheBin::forgetOutdatedState(uintptr_t currTime) 598*51c0b2f7Stbbdev { 599*51c0b2f7Stbbdev // If the time since the last get is LongWaitFactor times more than ageThreshold 600*51c0b2f7Stbbdev // for the bin, treat the bin as rarely-used and forget everything we know 601*51c0b2f7Stbbdev // about it. 602*51c0b2f7Stbbdev // If LongWaitFactor is too small, we forget too early and 603*51c0b2f7Stbbdev // so prevents good caching, while if too high, caching blocks 604*51c0b2f7Stbbdev // with unrelated usage pattern occurs. 605*51c0b2f7Stbbdev const uintptr_t sinceLastGet = currTime - lastGet; 606*51c0b2f7Stbbdev bool doCleanup = false; 607*51c0b2f7Stbbdev 608*51c0b2f7Stbbdev if (ageThreshold) 609*51c0b2f7Stbbdev doCleanup = sinceLastGet > Props::LongWaitFactor * ageThreshold; 610*51c0b2f7Stbbdev else if (lastCleanedAge) 611*51c0b2f7Stbbdev doCleanup = sinceLastGet > Props::LongWaitFactor * (lastCleanedAge - lastGet); 612*51c0b2f7Stbbdev 613*51c0b2f7Stbbdev if (doCleanup) { 614*51c0b2f7Stbbdev lastCleanedAge = 0; 615*51c0b2f7Stbbdev ageThreshold = 0; 616*51c0b2f7Stbbdev } 617*51c0b2f7Stbbdev 618*51c0b2f7Stbbdev } 619*51c0b2f7Stbbdev 620*51c0b2f7Stbbdev template<typename Props> LargeMemoryBlock *LargeObjectCacheImpl<Props>:: 621*51c0b2f7Stbbdev CacheBin::cleanToThreshold(uintptr_t currTime, BinBitMask *bitMask, int idx) 622*51c0b2f7Stbbdev { 623*51c0b2f7Stbbdev /* oldest may be more recent then age, that's why cast to signed type 624*51c0b2f7Stbbdev was used. age overflow is also processed correctly. */ 625*51c0b2f7Stbbdev if ( !last || (intptr_t)(currTime - last->age) < ageThreshold ) return NULL; 626*51c0b2f7Stbbdev 627*51c0b2f7Stbbdev #if MALLOC_DEBUG 628*51c0b2f7Stbbdev uintptr_t nextAge = 0; 629*51c0b2f7Stbbdev #endif 630*51c0b2f7Stbbdev do { 631*51c0b2f7Stbbdev #if MALLOC_DEBUG 632*51c0b2f7Stbbdev // check that list ordered 633*51c0b2f7Stbbdev MALLOC_ASSERT(!nextAge || lessThanWithOverflow(nextAge, last->age), 634*51c0b2f7Stbbdev ASSERT_TEXT); 635*51c0b2f7Stbbdev nextAge = last->age; 636*51c0b2f7Stbbdev #endif 637*51c0b2f7Stbbdev cachedSize -= last->unalignedSize; 638*51c0b2f7Stbbdev last = last->prev; 639*51c0b2f7Stbbdev } while (last && (intptr_t)(currTime - last->age) > ageThreshold); 640*51c0b2f7Stbbdev 641*51c0b2f7Stbbdev LargeMemoryBlock *toRelease = NULL; 642*51c0b2f7Stbbdev if (last) { 643*51c0b2f7Stbbdev toRelease = last->next; 644*51c0b2f7Stbbdev oldest = last->age; 645*51c0b2f7Stbbdev last->next = NULL; 646*51c0b2f7Stbbdev } else { 647*51c0b2f7Stbbdev toRelease = first; 648*51c0b2f7Stbbdev first = NULL; 649*51c0b2f7Stbbdev oldest = 0; 650*51c0b2f7Stbbdev if (!usedSize) 651*51c0b2f7Stbbdev bitMask->set(idx, false); 652*51c0b2f7Stbbdev } 653*51c0b2f7Stbbdev MALLOC_ASSERT( toRelease, ASSERT_TEXT ); 654*51c0b2f7Stbbdev lastCleanedAge = toRelease->age; 655*51c0b2f7Stbbdev 656*51c0b2f7Stbbdev return toRelease; 657*51c0b2f7Stbbdev } 658*51c0b2f7Stbbdev 659*51c0b2f7Stbbdev template<typename Props> LargeMemoryBlock *LargeObjectCacheImpl<Props>:: 660*51c0b2f7Stbbdev CacheBin::cleanAll(BinBitMask *bitMask, int idx) 661*51c0b2f7Stbbdev { 662*51c0b2f7Stbbdev if (!last) return NULL; 663*51c0b2f7Stbbdev 664*51c0b2f7Stbbdev LargeMemoryBlock *toRelease = first; 665*51c0b2f7Stbbdev last = NULL; 666*51c0b2f7Stbbdev first = NULL; 667*51c0b2f7Stbbdev oldest = 0; 668*51c0b2f7Stbbdev cachedSize = 0; 669*51c0b2f7Stbbdev if (!usedSize) 670*51c0b2f7Stbbdev bitMask->set(idx, false); 671*51c0b2f7Stbbdev 672*51c0b2f7Stbbdev return toRelease; 673*51c0b2f7Stbbdev } 674*51c0b2f7Stbbdev 675*51c0b2f7Stbbdev /* ----------------------------------------------------------------------------------------------------- */ 676*51c0b2f7Stbbdev 677*51c0b2f7Stbbdev template<typename Props> size_t LargeObjectCacheImpl<Props>:: 678*51c0b2f7Stbbdev CacheBin::reportStat(int num, FILE *f) 679*51c0b2f7Stbbdev { 680*51c0b2f7Stbbdev #if __TBB_MALLOC_LOCACHE_STAT 681*51c0b2f7Stbbdev if (first) 682*51c0b2f7Stbbdev printf("%d(%lu): total %lu KB thr %ld lastCln %lu oldest %lu\n", 683*51c0b2f7Stbbdev num, num*Props::CacheStep+Props::MinSize, 684*51c0b2f7Stbbdev cachedSize/1024, ageThreshold, lastCleanedAge, oldest); 685*51c0b2f7Stbbdev #else 686*51c0b2f7Stbbdev suppress_unused_warning(num); 687*51c0b2f7Stbbdev suppress_unused_warning(f); 688*51c0b2f7Stbbdev #endif 689*51c0b2f7Stbbdev return cachedSize; 690*51c0b2f7Stbbdev } 691*51c0b2f7Stbbdev 692*51c0b2f7Stbbdev // Release objects from cache blocks that are older than ageThreshold 693*51c0b2f7Stbbdev template<typename Props> 694*51c0b2f7Stbbdev bool LargeObjectCacheImpl<Props>::regularCleanup(ExtMemoryPool *extMemPool, uintptr_t currTime, bool doThreshDecr) 695*51c0b2f7Stbbdev { 696*51c0b2f7Stbbdev bool released = false; 697*51c0b2f7Stbbdev BinsSummary binsSummary; 698*51c0b2f7Stbbdev 699*51c0b2f7Stbbdev // Threshold settings is below this cache or starts from zero index 700*51c0b2f7Stbbdev if (hugeSizeThresholdIdx == 0) return false; 701*51c0b2f7Stbbdev 702*51c0b2f7Stbbdev // Starting searching for bin that is less than huge size threshold (can be cleaned-up) 703*51c0b2f7Stbbdev int startSearchIdx = hugeSizeThresholdIdx - 1; 704*51c0b2f7Stbbdev 705*51c0b2f7Stbbdev for (int i = bitMask.getMaxTrue(startSearchIdx); i >= 0; i = bitMask.getMaxTrue(i-1)) { 706*51c0b2f7Stbbdev bin[i].updateBinsSummary(&binsSummary); 707*51c0b2f7Stbbdev if (!doThreshDecr && tooLargeLOC.load(std::memory_order_relaxed) > 2 && binsSummary.isLOCTooLarge()) { 708*51c0b2f7Stbbdev // if LOC is too large for quite long time, decrease the threshold 709*51c0b2f7Stbbdev // based on bin hit statistics. 710*51c0b2f7Stbbdev // For this, redo cleanup from the beginning. 711*51c0b2f7Stbbdev // Note: on this iteration total usedSz can be not too large 712*51c0b2f7Stbbdev // in comparison to total cachedSz, as we calculated it only 713*51c0b2f7Stbbdev // partially. We are ok with it. 714*51c0b2f7Stbbdev i = bitMask.getMaxTrue(startSearchIdx)+1; 715*51c0b2f7Stbbdev doThreshDecr = true; 716*51c0b2f7Stbbdev binsSummary.reset(); 717*51c0b2f7Stbbdev continue; 718*51c0b2f7Stbbdev } 719*51c0b2f7Stbbdev if (doThreshDecr) 720*51c0b2f7Stbbdev bin[i].decreaseThreshold(); 721*51c0b2f7Stbbdev 722*51c0b2f7Stbbdev if (bin[i].cleanToThreshold(extMemPool, &bitMask, currTime, i)) { 723*51c0b2f7Stbbdev released = true; 724*51c0b2f7Stbbdev } 725*51c0b2f7Stbbdev } 726*51c0b2f7Stbbdev // We want to find if LOC was too large for some time continuously, 727*51c0b2f7Stbbdev // so OK with races between incrementing and zeroing, but incrementing 728*51c0b2f7Stbbdev // must be atomic. 729*51c0b2f7Stbbdev if (binsSummary.isLOCTooLarge()) { 730*51c0b2f7Stbbdev tooLargeLOC++; 731*51c0b2f7Stbbdev } else { 732*51c0b2f7Stbbdev tooLargeLOC.store(0, std::memory_order_relaxed); 733*51c0b2f7Stbbdev } 734*51c0b2f7Stbbdev return released; 735*51c0b2f7Stbbdev } 736*51c0b2f7Stbbdev 737*51c0b2f7Stbbdev template<typename Props> 738*51c0b2f7Stbbdev bool LargeObjectCacheImpl<Props>::cleanAll(ExtMemoryPool *extMemPool) 739*51c0b2f7Stbbdev { 740*51c0b2f7Stbbdev bool released = false; 741*51c0b2f7Stbbdev for (int i = numBins-1; i >= 0; i--) { 742*51c0b2f7Stbbdev released |= bin[i].releaseAllToBackend(extMemPool, &bitMask, i); 743*51c0b2f7Stbbdev } 744*51c0b2f7Stbbdev return released; 745*51c0b2f7Stbbdev } 746*51c0b2f7Stbbdev 747*51c0b2f7Stbbdev template<typename Props> 748*51c0b2f7Stbbdev void LargeObjectCacheImpl<Props>::reset() { 749*51c0b2f7Stbbdev tooLargeLOC.store(0, std::memory_order_relaxed); 750*51c0b2f7Stbbdev for (int i = numBins-1; i >= 0; i--) 751*51c0b2f7Stbbdev bin[i].init(); 752*51c0b2f7Stbbdev bitMask.reset(); 753*51c0b2f7Stbbdev } 754*51c0b2f7Stbbdev 755*51c0b2f7Stbbdev #if __TBB_MALLOC_WHITEBOX_TEST 756*51c0b2f7Stbbdev template<typename Props> 757*51c0b2f7Stbbdev size_t LargeObjectCacheImpl<Props>::getLOCSize() const 758*51c0b2f7Stbbdev { 759*51c0b2f7Stbbdev size_t size = 0; 760*51c0b2f7Stbbdev for (int i = numBins-1; i >= 0; i--) 761*51c0b2f7Stbbdev size += bin[i].getSize(); 762*51c0b2f7Stbbdev return size; 763*51c0b2f7Stbbdev } 764*51c0b2f7Stbbdev 765*51c0b2f7Stbbdev size_t LargeObjectCache::getLOCSize() const 766*51c0b2f7Stbbdev { 767*51c0b2f7Stbbdev return largeCache.getLOCSize() + hugeCache.getLOCSize(); 768*51c0b2f7Stbbdev } 769*51c0b2f7Stbbdev 770*51c0b2f7Stbbdev template<typename Props> 771*51c0b2f7Stbbdev size_t LargeObjectCacheImpl<Props>::getUsedSize() const 772*51c0b2f7Stbbdev { 773*51c0b2f7Stbbdev size_t size = 0; 774*51c0b2f7Stbbdev for (int i = numBins-1; i >= 0; i--) 775*51c0b2f7Stbbdev size += bin[i].getUsedSize(); 776*51c0b2f7Stbbdev return size; 777*51c0b2f7Stbbdev } 778*51c0b2f7Stbbdev 779*51c0b2f7Stbbdev size_t LargeObjectCache::getUsedSize() const 780*51c0b2f7Stbbdev { 781*51c0b2f7Stbbdev return largeCache.getUsedSize() + hugeCache.getUsedSize(); 782*51c0b2f7Stbbdev } 783*51c0b2f7Stbbdev #endif // __TBB_MALLOC_WHITEBOX_TEST 784*51c0b2f7Stbbdev 785*51c0b2f7Stbbdev inline bool LargeObjectCache::isCleanupNeededOnRange(uintptr_t range, uintptr_t currTime) 786*51c0b2f7Stbbdev { 787*51c0b2f7Stbbdev return range >= cacheCleanupFreq 788*51c0b2f7Stbbdev || currTime+range < currTime-1 // overflow, 0 is power of 2, do cleanup 789*51c0b2f7Stbbdev // (prev;prev+range] contains n*cacheCleanupFreq 790*51c0b2f7Stbbdev || alignUp(currTime, cacheCleanupFreq)<currTime+range; 791*51c0b2f7Stbbdev } 792*51c0b2f7Stbbdev 793*51c0b2f7Stbbdev bool LargeObjectCache::doCleanup(uintptr_t currTime, bool doThreshDecr) 794*51c0b2f7Stbbdev { 795*51c0b2f7Stbbdev if (!doThreshDecr) 796*51c0b2f7Stbbdev extMemPool->allLocalCaches.markUnused(); 797*51c0b2f7Stbbdev return largeCache.regularCleanup(extMemPool, currTime, doThreshDecr) 798*51c0b2f7Stbbdev | hugeCache.regularCleanup(extMemPool, currTime, doThreshDecr); 799*51c0b2f7Stbbdev } 800*51c0b2f7Stbbdev 801*51c0b2f7Stbbdev bool LargeObjectCache::decreasingCleanup() 802*51c0b2f7Stbbdev { 803*51c0b2f7Stbbdev return doCleanup(cacheCurrTime.load(std::memory_order_acquire), /*doThreshDecr=*/true); 804*51c0b2f7Stbbdev } 805*51c0b2f7Stbbdev 806*51c0b2f7Stbbdev bool LargeObjectCache::regularCleanup() 807*51c0b2f7Stbbdev { 808*51c0b2f7Stbbdev return doCleanup(cacheCurrTime.load(std::memory_order_acquire), /*doThreshDecr=*/false); 809*51c0b2f7Stbbdev } 810*51c0b2f7Stbbdev 811*51c0b2f7Stbbdev bool LargeObjectCache::cleanAll() 812*51c0b2f7Stbbdev { 813*51c0b2f7Stbbdev return largeCache.cleanAll(extMemPool) | hugeCache.cleanAll(extMemPool); 814*51c0b2f7Stbbdev } 815*51c0b2f7Stbbdev 816*51c0b2f7Stbbdev void LargeObjectCache::reset() 817*51c0b2f7Stbbdev { 818*51c0b2f7Stbbdev largeCache.reset(); 819*51c0b2f7Stbbdev hugeCache.reset(); 820*51c0b2f7Stbbdev } 821*51c0b2f7Stbbdev 822*51c0b2f7Stbbdev template<typename Props> 823*51c0b2f7Stbbdev LargeMemoryBlock *LargeObjectCacheImpl<Props>::get(ExtMemoryPool *extMemoryPool, size_t size) 824*51c0b2f7Stbbdev { 825*51c0b2f7Stbbdev int idx = Props::sizeToIdx(size); 826*51c0b2f7Stbbdev 827*51c0b2f7Stbbdev LargeMemoryBlock *lmb = bin[idx].get(extMemoryPool, size, &bitMask, idx); 828*51c0b2f7Stbbdev 829*51c0b2f7Stbbdev if (lmb) { 830*51c0b2f7Stbbdev MALLOC_ITT_SYNC_ACQUIRED(bin+idx); 831*51c0b2f7Stbbdev STAT_increment(getThreadId(), ThreadCommonCounters, allocCachedLargeObj); 832*51c0b2f7Stbbdev } 833*51c0b2f7Stbbdev return lmb; 834*51c0b2f7Stbbdev } 835*51c0b2f7Stbbdev 836*51c0b2f7Stbbdev template<typename Props> 837*51c0b2f7Stbbdev void LargeObjectCacheImpl<Props>::updateCacheState(ExtMemoryPool *extMemPool, DecreaseOrIncrease op, size_t size) 838*51c0b2f7Stbbdev { 839*51c0b2f7Stbbdev int idx = Props::sizeToIdx(size); 840*51c0b2f7Stbbdev MALLOC_ASSERT(idx<numBins, ASSERT_TEXT); 841*51c0b2f7Stbbdev bin[idx].updateUsedSize(extMemPool, op==decrease? -size : size, &bitMask, idx); 842*51c0b2f7Stbbdev } 843*51c0b2f7Stbbdev 844*51c0b2f7Stbbdev #if __TBB_MALLOC_LOCACHE_STAT 845*51c0b2f7Stbbdev template<typename Props> 846*51c0b2f7Stbbdev void LargeObjectCacheImpl<Props>::reportStat(FILE *f) 847*51c0b2f7Stbbdev { 848*51c0b2f7Stbbdev size_t cachedSize = 0; 849*51c0b2f7Stbbdev for (int i=0; i<numBins; i++) 850*51c0b2f7Stbbdev cachedSize += bin[i].reportStat(i, f); 851*51c0b2f7Stbbdev fprintf(f, "total LOC size %lu MB\n", cachedSize/1024/1024); 852*51c0b2f7Stbbdev } 853*51c0b2f7Stbbdev 854*51c0b2f7Stbbdev void LargeObjectCache::reportStat(FILE *f) 855*51c0b2f7Stbbdev { 856*51c0b2f7Stbbdev largeCache.reportStat(f); 857*51c0b2f7Stbbdev hugeCache.reportStat(f); 858*51c0b2f7Stbbdev fprintf(f, "cache time %lu\n", cacheCurrTime.load(std::memory_order_relaxed)); 859*51c0b2f7Stbbdev } 860*51c0b2f7Stbbdev #endif 861*51c0b2f7Stbbdev 862*51c0b2f7Stbbdev template<typename Props> 863*51c0b2f7Stbbdev void LargeObjectCacheImpl<Props>::putList(ExtMemoryPool *extMemPool, LargeMemoryBlock *toCache) 864*51c0b2f7Stbbdev { 865*51c0b2f7Stbbdev int toBinIdx = Props::sizeToIdx(toCache->unalignedSize); 866*51c0b2f7Stbbdev 867*51c0b2f7Stbbdev MALLOC_ITT_SYNC_RELEASING(bin+toBinIdx); 868*51c0b2f7Stbbdev bin[toBinIdx].putList(extMemPool, toCache, &bitMask, toBinIdx); 869*51c0b2f7Stbbdev } 870*51c0b2f7Stbbdev 871*51c0b2f7Stbbdev void LargeObjectCache::updateCacheState(DecreaseOrIncrease op, size_t size) 872*51c0b2f7Stbbdev { 873*51c0b2f7Stbbdev if (size < maxLargeSize) 874*51c0b2f7Stbbdev largeCache.updateCacheState(extMemPool, op, size); 875*51c0b2f7Stbbdev else if (size < maxHugeSize) 876*51c0b2f7Stbbdev hugeCache.updateCacheState(extMemPool, op, size); 877*51c0b2f7Stbbdev } 878*51c0b2f7Stbbdev 879*51c0b2f7Stbbdev uintptr_t LargeObjectCache::getCurrTime() 880*51c0b2f7Stbbdev { 881*51c0b2f7Stbbdev return ++cacheCurrTime; 882*51c0b2f7Stbbdev } 883*51c0b2f7Stbbdev 884*51c0b2f7Stbbdev uintptr_t LargeObjectCache::getCurrTimeRange(uintptr_t range) 885*51c0b2f7Stbbdev { 886*51c0b2f7Stbbdev return (cacheCurrTime.fetch_add(range) + 1); 887*51c0b2f7Stbbdev } 888*51c0b2f7Stbbdev 889*51c0b2f7Stbbdev void LargeObjectCache::registerRealloc(size_t oldSize, size_t newSize) 890*51c0b2f7Stbbdev { 891*51c0b2f7Stbbdev updateCacheState(decrease, oldSize); 892*51c0b2f7Stbbdev updateCacheState(increase, alignToBin(newSize)); 893*51c0b2f7Stbbdev } 894*51c0b2f7Stbbdev 895*51c0b2f7Stbbdev size_t LargeObjectCache::alignToBin(size_t size) { 896*51c0b2f7Stbbdev return size < maxLargeSize ? LargeCacheType::alignToBin(size) : HugeCacheType::alignToBin(size); 897*51c0b2f7Stbbdev } 898*51c0b2f7Stbbdev 899*51c0b2f7Stbbdev // Used for internal purpose 900*51c0b2f7Stbbdev int LargeObjectCache::sizeToIdx(size_t size) 901*51c0b2f7Stbbdev { 902*51c0b2f7Stbbdev MALLOC_ASSERT(size <= maxHugeSize, ASSERT_TEXT); 903*51c0b2f7Stbbdev return size < maxLargeSize ? 904*51c0b2f7Stbbdev LargeCacheType::sizeToIdx(size) : 905*51c0b2f7Stbbdev LargeCacheType::numBins + HugeCacheType::sizeToIdx(size); 906*51c0b2f7Stbbdev } 907*51c0b2f7Stbbdev 908*51c0b2f7Stbbdev void LargeObjectCache::putList(LargeMemoryBlock *list) 909*51c0b2f7Stbbdev { 910*51c0b2f7Stbbdev LargeMemoryBlock *toProcess, *n; 911*51c0b2f7Stbbdev 912*51c0b2f7Stbbdev for (LargeMemoryBlock *curr = list; curr; curr = toProcess) { 913*51c0b2f7Stbbdev LargeMemoryBlock *tail = curr; 914*51c0b2f7Stbbdev toProcess = curr->next; 915*51c0b2f7Stbbdev if (!sizeInCacheRange(curr->unalignedSize)) { 916*51c0b2f7Stbbdev extMemPool->backend.returnLargeObject(curr); 917*51c0b2f7Stbbdev continue; 918*51c0b2f7Stbbdev } 919*51c0b2f7Stbbdev int currIdx = sizeToIdx(curr->unalignedSize); 920*51c0b2f7Stbbdev 921*51c0b2f7Stbbdev // Find all blocks fitting to same bin. Not use more efficient sorting 922*51c0b2f7Stbbdev // algorithm because list is short (commonly, 923*51c0b2f7Stbbdev // LocalLOC's HIGH_MARK-LOW_MARK, i.e. 24 items). 924*51c0b2f7Stbbdev for (LargeMemoryBlock *b = toProcess; b; b = n) { 925*51c0b2f7Stbbdev n = b->next; 926*51c0b2f7Stbbdev if (sizeToIdx(b->unalignedSize) == currIdx) { 927*51c0b2f7Stbbdev tail->next = b; 928*51c0b2f7Stbbdev tail = b; 929*51c0b2f7Stbbdev if (toProcess == b) 930*51c0b2f7Stbbdev toProcess = toProcess->next; 931*51c0b2f7Stbbdev else { 932*51c0b2f7Stbbdev b->prev->next = b->next; 933*51c0b2f7Stbbdev if (b->next) 934*51c0b2f7Stbbdev b->next->prev = b->prev; 935*51c0b2f7Stbbdev } 936*51c0b2f7Stbbdev } 937*51c0b2f7Stbbdev } 938*51c0b2f7Stbbdev tail->next = NULL; 939*51c0b2f7Stbbdev if (curr->unalignedSize < maxLargeSize) 940*51c0b2f7Stbbdev largeCache.putList(extMemPool, curr); 941*51c0b2f7Stbbdev else 942*51c0b2f7Stbbdev hugeCache.putList(extMemPool, curr); 943*51c0b2f7Stbbdev } 944*51c0b2f7Stbbdev } 945*51c0b2f7Stbbdev 946*51c0b2f7Stbbdev void LargeObjectCache::put(LargeMemoryBlock *largeBlock) 947*51c0b2f7Stbbdev { 948*51c0b2f7Stbbdev size_t blockSize = largeBlock->unalignedSize; 949*51c0b2f7Stbbdev if (sizeInCacheRange(blockSize)) { 950*51c0b2f7Stbbdev largeBlock->next = NULL; 951*51c0b2f7Stbbdev if (blockSize < maxLargeSize) 952*51c0b2f7Stbbdev largeCache.putList(extMemPool, largeBlock); 953*51c0b2f7Stbbdev else 954*51c0b2f7Stbbdev hugeCache.putList(extMemPool, largeBlock); 955*51c0b2f7Stbbdev } else { 956*51c0b2f7Stbbdev extMemPool->backend.returnLargeObject(largeBlock); 957*51c0b2f7Stbbdev } 958*51c0b2f7Stbbdev } 959*51c0b2f7Stbbdev 960*51c0b2f7Stbbdev LargeMemoryBlock *LargeObjectCache::get(size_t size) 961*51c0b2f7Stbbdev { 962*51c0b2f7Stbbdev MALLOC_ASSERT( size >= minLargeSize, ASSERT_TEXT ); 963*51c0b2f7Stbbdev if (sizeInCacheRange(size)) { 964*51c0b2f7Stbbdev return size < maxLargeSize ? 965*51c0b2f7Stbbdev largeCache.get(extMemPool, size) : hugeCache.get(extMemPool, size); 966*51c0b2f7Stbbdev } 967*51c0b2f7Stbbdev return NULL; 968*51c0b2f7Stbbdev } 969*51c0b2f7Stbbdev 970*51c0b2f7Stbbdev LargeMemoryBlock *ExtMemoryPool::mallocLargeObject(MemoryPool *pool, size_t allocationSize) 971*51c0b2f7Stbbdev { 972*51c0b2f7Stbbdev #if __TBB_MALLOC_LOCACHE_STAT 973*51c0b2f7Stbbdev mallocCalls++; 974*51c0b2f7Stbbdev memAllocKB.fetch_add(allocationSize/1024); 975*51c0b2f7Stbbdev #endif 976*51c0b2f7Stbbdev LargeMemoryBlock* lmb = loc.get(allocationSize); 977*51c0b2f7Stbbdev if (!lmb) { 978*51c0b2f7Stbbdev BackRefIdx backRefIdx = BackRefIdx::newBackRef(/*largeObj=*/true); 979*51c0b2f7Stbbdev if (backRefIdx.isInvalid()) 980*51c0b2f7Stbbdev return NULL; 981*51c0b2f7Stbbdev 982*51c0b2f7Stbbdev // unalignedSize is set in getLargeBlock 983*51c0b2f7Stbbdev lmb = backend.getLargeBlock(allocationSize); 984*51c0b2f7Stbbdev if (!lmb) { 985*51c0b2f7Stbbdev removeBackRef(backRefIdx); 986*51c0b2f7Stbbdev loc.updateCacheState(decrease, allocationSize); 987*51c0b2f7Stbbdev return NULL; 988*51c0b2f7Stbbdev } 989*51c0b2f7Stbbdev lmb->backRefIdx = backRefIdx; 990*51c0b2f7Stbbdev lmb->pool = pool; 991*51c0b2f7Stbbdev STAT_increment(getThreadId(), ThreadCommonCounters, allocNewLargeObj); 992*51c0b2f7Stbbdev } else { 993*51c0b2f7Stbbdev #if __TBB_MALLOC_LOCACHE_STAT 994*51c0b2f7Stbbdev cacheHits++; 995*51c0b2f7Stbbdev memHitKB.fetch_add(allocationSize/1024); 996*51c0b2f7Stbbdev #endif 997*51c0b2f7Stbbdev } 998*51c0b2f7Stbbdev return lmb; 999*51c0b2f7Stbbdev } 1000*51c0b2f7Stbbdev 1001*51c0b2f7Stbbdev void ExtMemoryPool::freeLargeObject(LargeMemoryBlock *mBlock) 1002*51c0b2f7Stbbdev { 1003*51c0b2f7Stbbdev loc.put(mBlock); 1004*51c0b2f7Stbbdev } 1005*51c0b2f7Stbbdev 1006*51c0b2f7Stbbdev void ExtMemoryPool::freeLargeObjectList(LargeMemoryBlock *head) 1007*51c0b2f7Stbbdev { 1008*51c0b2f7Stbbdev loc.putList(head); 1009*51c0b2f7Stbbdev } 1010*51c0b2f7Stbbdev 1011*51c0b2f7Stbbdev bool ExtMemoryPool::softCachesCleanup() 1012*51c0b2f7Stbbdev { 1013*51c0b2f7Stbbdev return loc.regularCleanup(); 1014*51c0b2f7Stbbdev } 1015*51c0b2f7Stbbdev 1016*51c0b2f7Stbbdev bool ExtMemoryPool::hardCachesCleanup() 1017*51c0b2f7Stbbdev { 1018*51c0b2f7Stbbdev // thread-local caches must be cleaned before LOC, 1019*51c0b2f7Stbbdev // because object from thread-local cache can be released to LOC 1020*51c0b2f7Stbbdev bool ret = releaseAllLocalCaches(); 1021*51c0b2f7Stbbdev ret |= orphanedBlocks.cleanup(&backend); 1022*51c0b2f7Stbbdev ret |= loc.cleanAll(); 1023*51c0b2f7Stbbdev ret |= backend.clean(); 1024*51c0b2f7Stbbdev return ret; 1025*51c0b2f7Stbbdev } 1026*51c0b2f7Stbbdev 1027*51c0b2f7Stbbdev #if BACKEND_HAS_MREMAP 1028*51c0b2f7Stbbdev void *ExtMemoryPool::remap(void *ptr, size_t oldSize, size_t newSize, size_t alignment) 1029*51c0b2f7Stbbdev { 1030*51c0b2f7Stbbdev const size_t oldUnalignedSize = ((LargeObjectHdr*)ptr - 1)->memoryBlock->unalignedSize; 1031*51c0b2f7Stbbdev void *o = backend.remap(ptr, oldSize, newSize, alignment); 1032*51c0b2f7Stbbdev if (o) { 1033*51c0b2f7Stbbdev LargeMemoryBlock *lmb = ((LargeObjectHdr*)o - 1)->memoryBlock; 1034*51c0b2f7Stbbdev loc.registerRealloc(oldUnalignedSize, lmb->unalignedSize); 1035*51c0b2f7Stbbdev } 1036*51c0b2f7Stbbdev return o; 1037*51c0b2f7Stbbdev } 1038*51c0b2f7Stbbdev #endif /* BACKEND_HAS_MREMAP */ 1039*51c0b2f7Stbbdev 1040*51c0b2f7Stbbdev /*********** End allocation of large objects **********/ 1041*51c0b2f7Stbbdev 1042*51c0b2f7Stbbdev } // namespace internal 1043*51c0b2f7Stbbdev } // namespace rml 1044*51c0b2f7Stbbdev 1045*51c0b2f7Stbbdev #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) 1046*51c0b2f7Stbbdev #pragma warning(pop) 1047*51c0b2f7Stbbdev #endif 1048*51c0b2f7Stbbdev 1049