瀏覽代碼

Merge pull request #14478 from ghalliday/issue25187

HPCC-25187 Minimize memory consumption by reducing space allocated to arrays

Reviewed-By: Jake Smith <jake.smith@lexisnexis.com>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 4 年之前
父節點
當前提交
3cbe9e21a6

+ 2 - 2
common/thorhelper/thortparse.cpp

@@ -87,7 +87,7 @@ NonTerminal::NonTerminal(symbol_id _id, IAtom * _name, FeatureValue & _features,
 {
     unsigned nullCount = 0;
     unsigned nonNullIndex = 0;
-    reduced.ensure(numSymbols);
+    reduced.ensureCapacity(numSymbols);
     for (unsigned i= 0; i < numSymbols; i++)
     {
         GrammarSymbol * cur = symbols[i];
@@ -1038,7 +1038,7 @@ void TomitaResultIterator::reset(const GrammarSymbolArray & _values)
     if (def->notMatchedOnly)
         return;
 
-    values.ensure(numValues);
+    values.ensureCapacity(numValues);
     for (unsigned i = 0; i < numValues; i++)
         values.append(OLINK(_values.item(i)));
 

+ 2 - 2
dali/base/dasds.cpp

@@ -6649,8 +6649,8 @@ public:
     {
         unsigned c;
         src.read(c);
-        xpaths.ensure(c);
-        modes.ensure(c);
+        xpaths.ensureCapacity(c);
+        modes.ensureCapacity(c);
         while (c--)
         {
             StringAttr xpath;

+ 1 - 1
dali/base/dautils.cpp

@@ -3406,7 +3406,7 @@ public:
         mb.read(count);
         if (count)
         {
-            ldInfo.ensure(count);
+            ldInfo.ensureCapacity(count);
             for (unsigned c=0; c<count; c++)
                 ldInfo.append(new CLockMetaData(mb));
         }

+ 1 - 1
ecl/eclagent/eclgraph.cpp

@@ -1430,7 +1430,7 @@ const void * GraphResult::getLinkedRowResult()
 
 GraphResults::GraphResults(unsigned _maxResults)
 {
-    results.ensure(_maxResults);
+    results.ensureCapacity(_maxResults);
 }
 
 void GraphResults::clear()

+ 32 - 14
ecl/hql/hqlexpr.cpp

@@ -4037,6 +4037,7 @@ IHqlExpression * CHqlExpression::calcNormalizedSelector() const
     if ((normalizedLeft != left) || (operands.ordinality() > 2))
     {
         HqlExprArray args;
+        args.ensureCapacity(2);
         args.append(*LINK(normalizedLeft));
         args.append(OLINK(operands.item(1)));
         return doCreateSelectExpr(args);
@@ -4084,6 +4085,7 @@ CHqlRealExpression::~CHqlRealExpression()
 IHqlExpression *CHqlRealExpression::closeExpr()
 {
     updateFlagsAfterOperands();
+    operands.trimMemory();
     return CHqlExpression::closeExpr();
 }
 
@@ -4965,7 +4967,7 @@ void CHqlRealExpression::appendSingleOperand(IHqlExpression * arg0)
     if (!arg0)
         return;
 
-    operands.ensure(1);
+    operands.ensureSpace(1);
     doAppendOperand(*arg0);
 }
 
@@ -5187,7 +5189,7 @@ void expandOperands(HqlExprArray & args, const std::initializer_list<IHqlExpress
         }
     }
 
-    args.ensure(count);
+    args.ensureSpace(count);
     for (auto & cur : operands)
     {
         //Skip null entries
@@ -6027,7 +6029,7 @@ void CHqlSelectBaseExpression::setOperands(IHqlExpression * left, IHqlExpression
 {
     //Need to be very careful about the order that this is done in, since queryType() depends on operand2
     unsigned max = attr ? 3 : 2;
-    operands.ensure(max);
+    operands.ensureSpace(max);
     operands.append(*left);
     operands.append(*right);
     if (attr)
@@ -7615,7 +7617,7 @@ IHqlExpression * CHqlNamedSymbol::cloneSymbol(IIdAtom * optid, IHqlExpression *
 
     CHqlNamedSymbol * e = new CHqlNamedSymbol(newid, moduleId, LINK(newbody), LINK(newfuncdef), isExported(), isShared(), symbolFlags, text, startLine, startColumn, startpos, bodypos, endpos);
     //NB: do not all doAppendOpeand() because the parameters to a named symbol do not change it's attributes - e.g., whether pure.
-    e->operands.ensure(newoperands->ordinality());
+    e->operands.ensureCapacity(newoperands->ordinality());
     ForEachItemIn(idx, *newoperands)
         e->operands.append(OLINK(newoperands->item(idx)));
     return e->closeExpr();
@@ -7695,7 +7697,7 @@ IError * queryAnnotatedWarning(const IHqlExpression * expr)
 CHqlAnnotationWithOperands::CHqlAnnotationWithOperands(IHqlExpression *_body, HqlExprArray & _args)
 : CHqlAnnotation(_body)
 {
-    operands.ensure(_args.ordinality());
+    operands.ensureCapacity(_args.ordinality());
     ForEachItemIn(i, _args)
         operands.append(OLINK(_args.item(i)));
 }
@@ -8274,7 +8276,7 @@ void CHqlScope::sethash()
     //MORE: Should symbols also be added as operands - would make more sense....
     SymbolTableIterator iter(symbols);
     HqlExprCopyArray sortedSymbols;
-    sortedSymbols.ensure(symbols.count());
+    sortedSymbols.ensureCapacity(symbols.count());
     for (iter.first(); iter.isValid(); iter.next()) 
     {
         IHqlExpression *cur = symbols.mapToValue(&iter.query());
@@ -15325,7 +15327,7 @@ extern HQL_API bool isSimplifiedRecord(IHqlExpression * expr, bool isKey)
 void unwindChildren(HqlExprArray & children, IHqlExpression * expr)
 {
     unsigned max = expr->numChildren();
-    children.ensure(max);
+    children.ensureSpace(max);
     for (unsigned idx=0; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -15337,7 +15339,9 @@ void unwindChildren(HqlExprArray & children, IHqlExpression * expr)
 void unwindChildren(HqlExprArray & children, const IHqlExpression * expr, unsigned first)
 {
     unsigned max = expr->numChildren();
-    children.ensure(max-first);
+    if (first >= max)
+        return;
+    children.ensureSpace(max-first);
     for (unsigned idx=first; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -15348,7 +15352,9 @@ void unwindChildren(HqlExprArray & children, const IHqlExpression * expr, unsign
 
 void unwindChildren(HqlExprArray & children, const IHqlExpression * expr, unsigned first, unsigned max)
 {
-    children.ensure(max-first);
+    if (first >= max)
+        return;
+    children.ensureSpace(max-first);
     for (unsigned idx=first; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -15360,7 +15366,9 @@ void unwindChildren(HqlExprArray & children, const IHqlExpression * expr, unsign
 void unwindChildren(HqlExprCopyArray & children, const IHqlExpression * expr, unsigned first)
 {
     unsigned max = expr->numChildren();
-    children.ensure(max-first);
+    if (first >= max)
+        return;
+    children.ensureSpace(max-first);
     for (unsigned idx=first; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -15372,7 +15380,9 @@ void unwindChildren(HqlExprCopyArray & children, const IHqlExpression * expr, un
 void unwindRealChildren(HqlExprArray & children, const IHqlExpression * expr, unsigned first)
 {
     unsigned max = expr->numChildren();
-    children.ensure(max-first);
+    if (first >= max)
+        return;
+    children.ensureSpace(max-first);
     for (unsigned idx=first; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -15385,14 +15395,22 @@ void unwindRealChildren(HqlExprArray & children, const IHqlExpression * expr, un
 void unwindAttributes(HqlExprArray & children, const IHqlExpression * expr)
 {
     unsigned max = expr->numChildren();
+    unsigned numAttrs = 0;
     for (unsigned idx=0; idx < max; idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
         if (child->isAttribute())
+            numAttrs++;
+    }
+
+    if (numAttrs)
+    {
+        children.ensureSpace(numAttrs);
+        for (unsigned idx=0; idx < max; idx++)
         {
-            //Subsequent calls to ensure are quick no-ops
-            children.ensure(max - idx);
-            children.append(*LINK(child));
+            IHqlExpression * child = expr->queryChild(idx);
+            if (child->isAttribute())
+                children.append(*LINK(child));
         }
     }
 }

+ 1 - 1
ecl/hql/hqlmeta.cpp

@@ -1788,7 +1788,7 @@ IHqlExpression * createMatchingDistribution(IHqlExpression * expr, const HqlExpr
         return LINK(expr);
 
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     ForEachChild(i, expr)
     {
         IHqlExpression * mapped = createMatchingDistribution(expr->queryChild(i), oldSort, newSort);

+ 2 - 2
ecl/hql/hqlpmap.cpp

@@ -666,7 +666,7 @@ IHqlExpression * NewProjectMapper2::doExpandFields(IHqlExpression * expr, IHqlEx
             if (numNonHidden == 0)
                 numNonHidden = max;
             HqlExprArray args;
-            args.ensure(max);
+            args.ensureCapacity(max);
             bool same = true;
             for (unsigned idx=0; idx < max; idx++)
             {
@@ -806,7 +806,7 @@ IHqlExpression * NewProjectMapper2::doCollapseFields(IHqlExpression * expr, IHql
     if (numNonHidden == 0)
         numNonHidden = max;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     bool same = true;
     for (unsigned idx=0; idx < max; idx++)
     {

+ 10 - 9
ecl/hql/hqltrans.cpp

@@ -477,7 +477,7 @@ bool HqlTransformerBase::optimizedTransformChildren(IHqlExpression * expr, HqlEx
         if (idx == max)
             return true;
 
-        children.ensure(max);
+        children.ensureCapacity(max);
         for (unsigned i=numDone; i < idx; i++)
             children.append(*LINK(expr->queryChild(i)));
         children.append(*lastTransformedChild.getClear());
@@ -485,7 +485,7 @@ bool HqlTransformerBase::optimizedTransformChildren(IHqlExpression * expr, HqlEx
     }
     else
     {
-        children.ensure(max);
+        children.ensureCapacity(max);
         idx = numDone;
     }
 
@@ -530,7 +530,7 @@ bool HqlTransformerBase::transformChildren(IHqlExpression * expr, HqlExprArray &
         if (&children.item(idx) != expr->queryChild(idx))
             same = false;
 
-    children.ensure(max);
+    children.ensureCapacity(max);
     for (idx=numDone;idx<max;idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -1023,6 +1023,7 @@ IHqlExpression * QuickHqlTransformer::createTransformedBody(IHqlExpression * exp
             if (newDs == ds)
                 return LINK(expr);
 
+            children.ensureCapacity(expr->numChildren());
             children.append(*LINK(newDs));
             if (ds->queryRecord() == newDs->queryRecord())
                 children.append(*LINK(field));
@@ -1399,7 +1400,7 @@ IHqlExpression * NewHqlTransformer::quickTransformTransform(IHqlExpression * exp
     bool same = true;
     unsigned max = expr->numChildren();
     HqlExprArray children;
-    children.ensure(max);
+    children.ensureCapacity(max);
     for (unsigned i=0; i < max; i++)
     {
         IHqlExpression * cur = expr->queryChild(i);
@@ -1695,7 +1696,7 @@ IHqlExpression * NewHqlTransformer::getTransformedChildren(IHqlExpression * expr
     unsigned max = expr->numChildren();
     unsigned idx;
     bool same = true;
-    children.ensure(max);
+    children.ensureCapacity(max);
     for (idx=0;idx<max;idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);
@@ -3449,7 +3450,7 @@ IHqlExpression * updateChildSelectors(IHqlExpression * expr, IHqlExpression * ol
     unsigned max = expr->numChildren();
     unsigned i;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     for (i = 0; i < firstChild; i++)
         args.append(*LINK(expr->queryChild(i)));
 
@@ -3478,7 +3479,7 @@ IHqlExpression * updateMappedFields(IHqlExpression * expr, IHqlExpression * oldS
     unsigned max = expr->numChildren();
     unsigned i;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     for (i = 0; i < firstChild; i++)
         args.append(*LINK(expr->queryChild(i)));
 
@@ -3508,7 +3509,7 @@ IHqlExpression * updateActiveSelectorFields(IHqlExpression * expr, IHqlExpressio
     unsigned max = expr->numChildren();
     unsigned i;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     for (i = 0; i < firstChild; i++)
         args.append(*LINK(expr->queryChild(i)));
 
@@ -3964,7 +3965,7 @@ IHqlExpression * ScopedTransformer::createTransformed(IHqlExpression * expr)
 
     node_operator op = expr->getOperator();
     unsigned numChildren = expr->numChildren();
-    children.ensure(numChildren);
+    children.ensureCapacity(numChildren);
 
     unsigned idx;
     switch (op)

+ 2 - 2
ecl/hql/hqlutil.cpp

@@ -1151,7 +1151,7 @@ IHqlExpression * JoinOrderSpotter::doTraverseStripSelect(IHqlExpression * expr,
         if (max != 0)
         {
             HqlExprArray args;
-            args.ensure(max);
+            args.ensureCapacity(max);
 
             unsigned idx;
             bool same = true;
@@ -6091,7 +6091,7 @@ extern HQL_API bool containsVirtualField(IHqlExpression * record, IAtom * kind)
 IHqlExpression * removeVirtualFields(IHqlExpression * record)
 {
     HqlExprArray args;
-    args.ensure(record->numChildren());
+    args.ensureCapacity(record->numChildren());
     ForEachChild(i, record)
     {
         IHqlExpression * cur = record->queryChild(i);

+ 7 - 7
ecl/hqlcpp/hqlcppcase.cpp

@@ -71,7 +71,7 @@ void cvtChooseListToPairs(HqlExprArray & target, IHqlExpression * from, unsigned
 {
     unsigned max = from->numChildren();
     unsigned idx;
-    target.ensure(max);
+    target.ensureSpace(max);
     for (idx = 0; idx < max; idx++)
     {
         IHqlExpression * v1 = createConstant(createIntValue(idx+base, LINK(unsignedType)));
@@ -85,7 +85,7 @@ void cvtIndexListToPairs(HqlExprArray & target, IHqlExpression * from)
 {
     unsigned max = from->numChildren();
     unsigned idx;
-    target.ensure(max);
+    target.ensureSpace(max);
     for (idx = 0; idx < max; idx++)
     {
         IHqlExpression * v1 = from->queryChild(idx);
@@ -101,7 +101,7 @@ void cvtInListToPairs(HqlExprArray & target, IHqlExpression * from, bool valueIf
     unsigned idx;
     IHqlExpression * tValue = queryBoolExpr(valueIfMatch);
     ITypeInfo * type = queryBoolType();
-    target.ensure(max);
+    target.ensureSpace(max);
     for (idx = 0; idx < max; idx++)
     {
         IHqlExpression * v1 = from->queryChild(idx);
@@ -182,7 +182,7 @@ void HqlCppCaseInfo::addPair(IHqlExpression * expr)
 
 void HqlCppCaseInfo::addPairs(HqlExprArray & _pairs)
 {
-    pairs.ensure(_pairs.ordinality());
+    pairs.ensureSpace(_pairs.ordinality());
     ForEachItemIn(idx, _pairs)
         addPair(&_pairs.item(idx));
 }
@@ -415,7 +415,7 @@ IHqlExpression * HqlCppCaseInfo::buildIndexedMap(BuildCtx & ctx, const CHqlBound
     }
     else
     {
-        values.ensure(num);
+        values.ensureSpace(num);
         unsigned idx;
         for (idx = 0; idx < num; idx++)
             values.append(*LINK(dft));
@@ -898,7 +898,7 @@ IHqlExpression * HqlCppCaseInfo::createCompareList()
     }
 
     HqlExprArray values;
-    values.ensure(pairs.ordinality());
+    values.ensureCapacity(pairs.ordinality());
     ForEachItemIn(idx, pairs)
     {
         IHqlExpression & pair = pairs.item(idx);
@@ -960,7 +960,7 @@ IHqlExpression * HqlCppCaseInfo::createResultsExpr(IHqlExpression * matchVar, bo
 
     IHqlExpression * prevValue = NULL;
     HqlExprArray values;
-    values.ensure(pairs.ordinality());
+    values.ensureCapacity(pairs.ordinality());
     ForEachItemIn(idx, pairs)
     {
         IHqlExpression & pair = pairs.item(idx);

+ 1 - 1
ecl/hqlcpp/hqlcse.cpp

@@ -907,7 +907,7 @@ void CseScopeInfo::calcCommonLocation()
 void CseScopeInfo::cloneAliases(ICopyArrayOf<CseScopeInfo> & target) const
 {
     unsigned max = childAliases.ordinality();
-    target.ensure(target.ordinality() + max);
+    target.ensureSpace(max);
     for (unsigned i=0; i < max; ++i)
         target.append(childAliases.item(i));
 }

+ 1 - 1
ecl/hqlcpp/hqliproj.cpp

@@ -1000,7 +1000,7 @@ void ImplicitProjectInfo::addActiveSelects(const SelectUsedArray & src)
     if (selectsUsed.ordinality() == 0)
     {
         //No need to check for pre-existence, can be significant
-        selectsUsed.ensure(numSrc);
+        selectsUsed.ensureCapacity(numSrc);
         for (unsigned i=0; i < numSrc; i++)
             selectsUsed.append(src.item(i));
     }

+ 2 - 2
ecl/hqlcpp/hqliproj.ipp

@@ -69,10 +69,10 @@ public:
             return *singleValue;
         return HqlExprCopyArray::item(i);
     }
-    void ensure(unsigned max)
+    void ensureCapacity(unsigned max)
     {
         if (max > 1)
-            HqlExprCopyArray::ensure(max);
+            HqlExprCopyArray::ensureCapacity(max);
     }
     unsigned find(IHqlExpression & cur) const
     {

+ 1 - 1
ecl/hqlcpp/hqlresource.cpp

@@ -1460,7 +1460,7 @@ IHqlExpression * ActivityInvariantHoister::replaceResourcedReferences(ActivityIn
     {
         bool same = true;
         HqlExprArray args;
-        args.ensure(expr->numChildren());
+        args.ensureCapacity(expr->numChildren());
         ForEachChild(i, expr)
         {
             IHqlExpression * cur = expr->queryChild(i);

+ 1 - 1
ecl/hqlcpp/hqlstmt.cpp

@@ -1222,7 +1222,7 @@ HqlStmt::HqlStmt(StmtKind _kind, HqlStmts * _container)
 void HqlStmt::addExpr(IHqlExpression * expr)
 {
     //Only allocate a single extra expression at a time, since statements generally have very few (1) expressions
-    exprs.ensure(exprs.ordinality()+1);
+    exprs.ensureSpace(1);
     exprs.append(*expr);
 }
 

+ 5 - 5
ecl/hqlcpp/hqlttcpp.cpp

@@ -1266,7 +1266,7 @@ IHqlExpression * ThorScalarTransformer::createTransformed(IHqlExpression * expr)
             HqlExprArray children;
             unsigned firstArg = 0;
             unsigned numChildren = expr->numChildren();
-            children.ensure(numChildren);
+            children.ensureCapacity(numChildren);
             if (op != no_map)
             {
                 firstArg = 1;
@@ -1888,7 +1888,7 @@ protected:
                 unsigned max = expr->numChildren();
                 unsigned idx;
                 bool diff = false;
-                args.ensure(max);
+                args.ensureCapacity(max);
                 for (idx = 0; idx < max; idx++)
                 {
                     IHqlExpression * child = expr->queryChild(idx);
@@ -10359,7 +10359,7 @@ IHqlExpression * HqlScopeTagger::transformAmbiguousChildren(IHqlExpression * exp
 
     bool same = true;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     for(unsigned i=0; i < max; i++)
     {
         IHqlExpression * cur = expr->queryChild(i);
@@ -10379,7 +10379,7 @@ IHqlExpression * HqlScopeTagger::transformCall(IHqlExpression * expr)
     unsigned max = expr->numChildren();
     bool same = true;
     HqlExprArray args;
-    args.ensure(max);
+    args.ensureCapacity(max);
     for(unsigned i=0; i < max; i++)
     {
         IHqlExpression * cur = expr->queryChild(i);
@@ -13752,7 +13752,7 @@ IHqlExpression * HqlTreeNormalizer::createTransformedBody(IHqlExpression * expr)
 
     bool same = true;
     HqlExprArray children;
-    children.ensure(max);
+    children.ensureCapacity(max);
     for (unsigned idx=0;idx<max;idx++)
     {
         IHqlExpression * child = expr->queryChild(idx);

+ 1 - 1
roxie/roxiemem/roxiemem.cpp

@@ -9166,7 +9166,7 @@ protected:
     void createRows(IRowManager * rowManager, size_t numRows, ConstPointerArray & target, bool shuffle)
     {
         Owned<IFixedRowHeap> heap = rowManager->createFixedRowHeap(tuningAllocSize, 0, RHFpacked);
-        target.ensure(numTuningRows);
+        target.ensureSpace(numTuningRows);
         for (size_t i = 0; i < numRows; i++)
             target.append(heap->allocate());
 

+ 27 - 23
system/jlib/jarray.cpp

@@ -28,17 +28,6 @@
 #define DOUBLE_LIMIT      0x100000          // must be a power of 2
 #define ALLOCA_LIMIT      64
 
-inline unsigned ensurePowerOfTwo(unsigned value)
-{
-    value--;
-    value |= (value >> 1);
-    value |= (value >> 2);
-    value |= (value >> 4);
-    value |= (value >> 8);
-    value |= (value >> 16);
-    return value+1;
-}
-
 void Allocator::_space(size32_t iSize)
 {
     if ( used == max )
@@ -46,15 +35,10 @@ void Allocator::_space(size32_t iSize)
     used++;
 }
 
-void Allocator::_ensure(aindex_t next, size32_t iSize)
+void Allocator::_ensure(aindex_t req, size32_t iSize)
 {
-    aindex_t req = used + next;
     if (req > max )
-    {
-        if ((max == 0) && (req < DOUBLE_LIMIT))
-            max = ensurePowerOfTwo(req);
-        _reallocate(req, iSize);
-    }
+        _reallocateExact(req, iSize);
 }
 
 
@@ -77,8 +61,8 @@ void Allocator::_reallocate(aindex_t newLen, size32_t itemSize)
         newMax = (newLen + DOUBLE_LIMIT) & ~(DOUBLE_LIMIT-1);
         if (newLen >= newMax) // wraparound
         {
-            IERRLOG("Out of memory (overflow) in Array allocator: itemSize = %d, trying to allocate %d items", itemSize, newLen);
-            throw MakeStringException(0, "Out of memory (overflow) in Array allocator: itemSize = %d, trying to allocate %d items",itemSize, newLen);
+            IERRLOG("Out of memory (overflow) in Array allocator: itemSize = %u, trying to allocate %u items", itemSize, newLen);
+            throw MakeStringException(0, "Out of memory (overflow) in Array allocator: itemSize = %u, trying to allocate %u items",itemSize, newLen);
         }
     }
     else
@@ -89,20 +73,40 @@ void Allocator::_reallocate(aindex_t newLen, size32_t itemSize)
     size_t allocSize = (size_t)itemSize * newMax;
     if (allocSize < newMax)
     {
-        IERRLOG("Out of memory (overflow) in Array allocator: itemSize = %d, trying to allocate %d items", itemSize, newLen);
+        IERRLOG("Out of memory (overflow) in Array allocator: itemSize = %u, trying to allocate %u items", itemSize, newLen);
         throw MakeStringException(0, "Out of memory (overflow) in Array allocator: itemSize = %u, trying to allocate %u items",itemSize, newLen);
     }
     void *newhead = realloc(_head, allocSize);
     if (!newhead) 
     {
-        IERRLOG("Out of memory in Array allocator: itemSize = %d, trying to allocate %d items", itemSize, max);
-        throw MakeStringException(0, "Out of memory in Array allocator: itemSize = %d, trying to allocate %d items",itemSize, max);
+        IERRLOG("Out of memory in Array allocator: itemSize = %u, trying to allocate %u items", itemSize, max);
+        throw MakeStringException(0, "Out of memory in Array allocator: itemSize = %u, trying to allocate %u items",itemSize, max);
     }
     max = newMax;
     _head = newhead;
 };
 
 
+void Allocator::_reallocateExact(aindex_t newLen, size32_t itemSize)
+{
+    if (max == newLen)
+        return;
+
+    if (used > newLen)
+        throw MakeStringException(0, "Reallocate an array would contain too few items: itemSize = %u, trying to allocate %u items contains %u",itemSize, newLen, used);
+
+    size_t allocSize = (size_t)itemSize * newLen;
+    void *newhead = realloc(_head, allocSize);
+    if (!newhead)
+    {
+        IERRLOG("Out of memory in Array allocator: itemSize = %u, trying to allocate %u items", itemSize, max);
+        throw MakeStringException(0, "Out of memory in Array allocator: itemSize = %u, trying to allocate %u items",itemSize, max);
+    }
+    max = newLen;
+    _head = newhead;
+};
+
+
 void Allocator::_doRotateR(aindex_t pos1, aindex_t pos2, size32_t iSize, aindex_t numItems)
 {
     size32_t saveSize = numItems * iSize;

+ 5 - 2
system/jlib/jarray.hpp

@@ -77,9 +77,10 @@ protected:
     void * _doBAdd(void *, size32_t size, StdCompare compare, bool & isNew);
     void * _doBSearch(const void *, size32_t size, StdCompare compare) const;
     void _doSort(size32_t size, StdCompare compare);
-    void _ensure(aindex_t, size32_t);
+    void _ensure(aindex_t req, size32_t iSize);
     inline void _init() { max=0; _head=0; used=0; }                                 // inline because it is called a lot
     void _reallocate(aindex_t newlen, size32_t iSize);
+    void _reallocateExact(aindex_t newLen, size32_t itemSize);
     void _doRotateR(aindex_t pos1, aindex_t pos2, size32_t iSize, aindex_t num);
     void _doRotateL(aindex_t pos1, aindex_t pos2, size32_t iSize, aindex_t num);
     void _space(size32_t iSize);
@@ -97,13 +98,15 @@ protected:
 template <unsigned SIZE> class AllocatorOf : public Allocator
 {
 public:
-    inline void ensure(aindex_t num)                        { _ensure(num, SIZE); }
+    inline void ensureCapacity(aindex_t num)                 { _ensure(num, SIZE); }                // never shrink, reallocate exactly
+    inline void ensureSpace(aindex_t num)                    { _reallocateExact(used+num, SIZE); }  // reallocate exactly
     inline void rotateR(aindex_t pos1, aindex_t pos2)        { _doRotateR(pos1, pos2, SIZE, 1); }
     inline void rotateL(aindex_t pos1, aindex_t pos2)        { _doRotateL(pos1, pos2, SIZE, 1); }
     inline void rotateRN(aindex_t pos1, aindex_t pos2, aindex_t num)        { _doRotateR(pos1, pos2, SIZE, num); }
     inline void rotateLN(aindex_t pos1, aindex_t pos2, aindex_t num)        { _doRotateL(pos1, pos2, SIZE, num); }
     inline void swap(aindex_t pos1, aindex_t pos2)           { _doSwap(pos1, pos2, SIZE); }
     inline void transfer(aindex_t to, aindex_t from)         { _doTransfer(to, from, SIZE); }
+    inline void trimMemory()                                 { _reallocateExact(used, SIZE); }
 
 protected:
     inline void _space(void)                                { Allocator::_space(SIZE); }

+ 1 - 1
system/jlib/jio.cpp

@@ -1277,7 +1277,7 @@ public:
         num = _num;
         idx = 0;
         assertex(num);
-        oinstreams.ensure(num);
+        oinstreams.ensureCapacity(num);
         for (unsigned n = 0;n<num;n++)
             oinstreams.append(*LINK(_in[n]));
         in = oinstreams.getArray();

+ 2 - 2
system/jlib/jlib.hpp

@@ -284,7 +284,7 @@ inline void appendArray(ARRAY & target, const ARRAY & source)
     unsigned max = source.ordinality();
     if (max)
     {
-        target.ensure(target.ordinality() + max);
+        target.ensureSpace(max);
         for (unsigned i=0; i < max; ++i)
             target.append(OLINK(source.item(i)));
     }
@@ -295,7 +295,7 @@ inline void appendArray(IArray & target, const IArray & source)
     unsigned max = source.ordinality();
     if (max)
     {
-        target.ensure(target.ordinality() + max);
+        target.ensureSpace(max);
         for (unsigned i=0; i < max; ++i)
             target.append(OLINK(source.item(i)));
     }

+ 1 - 1
system/jlib/jset.cpp

@@ -366,7 +366,7 @@ class CBitSetThreadSafe : public CBitSetBase<CBitSetArrayHelper>
         buffer.read(count);
         if (count)
         {
-            bits.ensure(count);
+            bits.ensureCapacity(count);
             while (count--)
             {
                 bits_t b;

+ 1 - 1
system/jlib/jsort.cpp

@@ -983,7 +983,7 @@ protected:
         cProvider(IRowStream **_streams, unsigned numstreams, IRowLinkCounter *_linkcounter)
             : linkcounter(_linkcounter)
         {
-            ostreams.ensure(numstreams);
+            ostreams.ensureCapacity(numstreams);
             unsigned n = 0;
             while (n<numstreams) 
                 ostreams.append(*LINK(_streams[n++]));

+ 1 - 1
system/jlib/jstats.cpp

@@ -1681,7 +1681,7 @@ public:
 
         unsigned numStats;
         in.read(numStats);
-        stats.ensure(numStats);
+        stats.ensureCapacity(numStats);
         while (numStats-- > 0)
         {
             Statistic next (in, version);

+ 2 - 2
system/jlib/jthread.cpp

@@ -736,7 +736,7 @@ void CAsyncFor::For(unsigned num,unsigned maxatonce,bool abortFollowingException
             for (i=0;(i<num)&&(i<maxatonce);i++)
                 ready.signal();
             IArrayOf<Thread> started;
-            started.ensure(num);
+            started.ensureCapacity(num);
             for (i=0;i<num;i++) {
                 ready.wait();
                 if (abortFollowingException && e) break;
@@ -792,7 +792,7 @@ void CAsyncFor::For(unsigned num,unsigned maxatonce,bool abortFollowingException
                 }
             };
             IArrayOf<Thread> started;
-            started.ensure(num);
+            started.ensureCapacity(num);
             for (i=0;i<num-1;i++)
             {
                 Owned<Thread> thread = new cdothread(this,i,&errmutex,e);

+ 1 - 1
thorlcr/activities/fetch/thfetchslave.cpp

@@ -329,7 +329,7 @@ public:
         offsetMapSz = 0;
         if (numParts)
         {
-            parts.ensure(numParts);
+            parts.ensureCapacity(numParts);
             deserializePartFileDescriptors(data, parts);
         }
         data.read(offsetCount);

+ 3 - 3
thorlcr/activities/hashdistrib/thhashdistribslave.cpp

@@ -506,7 +506,7 @@ protected:
                 targets.append(new CTarget(*this, 0, false, "DESTINATION=ALL"));
             else
             {
-                targets.ensure(owner.numnodes);
+                targets.ensureCapacity(owner.numnodes);
                 for (unsigned n=0; n<owner.numnodes; n++)
                 {
                     VStringBuffer info("DESTINATION=%u", n);
@@ -3026,7 +3026,7 @@ protected:
         }
         else if (_numHashTables > numHashTables)
         {
-            _hashTables.ensure(_numHashTables);
+            _hashTables.ensureCapacity(_numHashTables);
             hashTables = (CHashTableRowTable **)_hashTables.getArray();
             for (unsigned i=numHashTables; i<_numHashTables; i++)
             {
@@ -3667,7 +3667,7 @@ unsigned CBucketHandler::getActivityId() const
 void CBucketHandler::init(unsigned _numBuckets, IRowStream *keyStream)
 {
     numBuckets = _numBuckets;
-    _buckets.ensure(numBuckets);
+    _buckets.ensureCapacity(numBuckets);
     buckets = (CBucket **)_buckets.getArray();
     for (unsigned i=0; i<numBuckets; i++)
     {

+ 1 - 1
thorlcr/activities/indexread/thindexread.cpp

@@ -76,7 +76,7 @@ protected:
         IDistributedSuperFile *super = f->querySuperFile();
 
         unsigned nparts = f->numParts(); // includes tlks if any, but unused in array
-        performPartLookup.ensure(nparts);
+        performPartLookup.ensureCapacity(nparts);
 
         bool checkTLKConsistency = (nullptr != super) && !localKey && (0 != (TIRsorted & indexBaseHelper->getFlags()));
         if (nofilter)

+ 1 - 1
thorlcr/activities/indexread/thindexreadslave.cpp

@@ -868,7 +868,7 @@ public:
             const CFieldOffsetSize * fields = rawMeta->queryFields();
             unsigned maxFields = rawMeta->getNumFields();
             seekGEOffset = fields[0].offset;
-            seekSizes.ensure(maxFields);
+            seekSizes.ensureCapacity(maxFields);
             seekSizes.append(fields[0].size);
             for (unsigned i=1; i < maxFields; i++)
                 seekSizes.append(seekSizes.item(i-1) + fields[i].size);

+ 1 - 1
thorlcr/activities/lookupjoin/thlookupjoinslave.cpp

@@ -1431,7 +1431,7 @@ public:
             mpTag = container.queryJobChannel().deserializeMPTag(data);
 
             unsigned slaves = container.queryJob().querySlaves();
-            rhsSlaveRows.ensure(slaves);
+            rhsSlaveRows.ensureCapacity(slaves);
             for (unsigned s=0; s<container.queryJob().querySlaves(); s++)
                 rhsSlaveRows.append(new CThorRowArrayWithFlushMarker(*this));
             channels.allocateN(queryJob().queryJobChannels());

+ 1 - 1
thorlcr/master/thmastermain.cpp

@@ -265,7 +265,7 @@ public:
         LOG(MCdebugProgress, thorJob, "Waiting for %d slaves to register", slaves);
 
         IPointerArrayOf<INode> connectedSlaves;
-        connectedSlaves.ensure(slaves);
+        connectedSlaves.ensureCapacity(slaves);
         unsigned remaining = slaves;
         INode *_sender = nullptr;
         CMessageBuffer msg;

+ 7 - 7
thorlcr/msort/tsorta.cpp

@@ -95,7 +95,7 @@ void CThorKeyArray::expandFPos()
     if (!filepos)
     {
         filepos.setown(new Int64Array);
-        filepos->ensure(keys.ordinality());
+        filepos->ensureCapacity(keys.ordinality());
         for (unsigned i=0;i<keys.ordinality();i++)
             filepos->append(getFixedFilePos(i));
         filerecsize = 0;
@@ -163,7 +163,7 @@ void CThorKeyArray::add(const void *row)
     else if (serialrowsize!=keySz)
     {
         sizes.setown(new UnsignedArray);
-        sizes->ensure(keys.ordinality()+1);
+        sizes->ensureCapacity(keys.ordinality()+1);
         for (unsigned i=0;i<keys.ordinality();i++)
             sizes->append(serialrowsize);
         sizes->append(keySz);
@@ -290,7 +290,7 @@ void CThorKeyArray::sort()
     if (sizes)
     {
         OwnedPtr<UnsignedArray> newsizes(new UnsignedArray);
-        newsizes->ensure(n);
+        newsizes->ensureCapacity(n);
         for (i = 0; i<n; i++)
             newsizes->append(sizes->item(ra[i]));
         sizes.setown(newsizes.getClear());
@@ -324,7 +324,7 @@ void CThorKeyArray::createSortedPartition(unsigned pn)
     if (sizes)
     {
         OwnedPtr<UnsignedArray> newsizes(new UnsignedArray);
-        newsizes->ensure(pn);
+        newsizes->ensureCapacity(pn);
         for (i = 1; i<pn; i++) {
             unsigned p = i*n/pn;
             newsizes->append(sizes->item(ra[p]));
@@ -448,7 +448,7 @@ void CThorKeyArray::calcPositions(IFile *file,CThorKeyArray &sample)
     // calculates positions based on sample
     // not fast!
     filepos.setown(new Int64Array);
-    filepos->ensure(ordinality());
+    filepos->ensureCapacity(ordinality());
     for (unsigned i0=0;i0<ordinality();i0++)
         filepos->append(-1);
     filerecsize = 0;
@@ -519,12 +519,12 @@ void CThorKeyArray::split()
     if (sizes)
     {
         newSizes.setown(new UnsignedArray);
-        newSizes->ensure(n);
+        newSizes->ensureCapacity(n);
     }
     if (filepos)
     {
         newFilePos.setown(new Int64Array);
-        newFilePos->ensure(n);
+        newFilePos->ensureCapacity(n);
     }
     unsigned newss = 0;
     for (unsigned i=0;i<n;i+=2)