Bug 715265 - Optimize all-int array sort and avoid creating a JSString per element when there is no comparator (r=luke)

This commit is contained in:
Santiago Gimeno
2012-01-23 14:06:02 -08:00
parent 87d9b0d8ba
commit 14c361465e
3 changed files with 170 additions and 66 deletions

View File

@@ -1981,7 +1981,89 @@ CompareStringValues(JSContext *cx, const Value &a, const Value &b, bool *lessOrE
return true;
}
struct SortComparatorStrings {
static int32_t const powersOf10Int32[] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
};
static inline int
NumDigitsBase10(int32_t n)
{
/*
* This is just floor_log10(n) + 1
* Algorithm taken from
* http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
*/
int32_t log2, t;
JS_CEILING_LOG2(log2, n);
t = log2 * 1233 >> 12;
return t - (n < powersOf10Int32[t]) + 1;
}
inline bool
CompareLexicographicInt32(JSContext *cx, const Value &a, const Value &b, bool *lessOrEqualp)
{
int32_t aint = a.toInt32();
int32_t bint = b.toInt32();
/*
* If both numbers are equal ... trivial
* If only one of both is negative --> arithmetic comparison as char code
* of '-' is always less than any other digit
* If both numbers are negative convert them to positive and continue
* handling ...
*/
if (aint == bint) {
*lessOrEqualp = true;
} else if ((aint < 0) && (bint >= 0)) {
*lessOrEqualp = true;
} else if ((aint >= 0) && (bint < 0)) {
*lessOrEqualp = false;
} else if (bint == INT32_MIN) { /* a is negative too --> a <= b */
*lessOrEqualp = true;
} else if (aint == INT32_MIN) { /* b is negative too but not INT32_MIN --> a > b */
*lessOrEqualp = false;
} else {
if (aint < 0) { /* b is also negative */
aint = -aint;
bint = -bint;
}
/*
* ... get number of digits of both integers.
* If they have the same number of digits --> arithmetic comparison.
* If digits_a > digits_b: a < b*10e(digits_a - digits_b).
* If digits_b > digits_a: a*10e(digits_b - digits_a) <= b.
*/
int digitsa = NumDigitsBase10(aint);
int digitsb = NumDigitsBase10(bint);
if (digitsa == digitsb)
*lessOrEqualp = (aint <= bint);
else if (digitsa > digitsb)
*lessOrEqualp = (aint < bint*powersOf10Int32[digitsa - digitsb]);
else /* if (digitsb > digitsa) */
*lessOrEqualp = (aint*powersOf10Int32[digitsb - digitsa] <= bint);
}
return true;
}
inline bool
CompareSubStringValues(JSContext *cx, const jschar *s1, size_t l1,
const jschar *s2, size_t l2, bool *lessOrEqualp)
{
if (!JS_CHECK_OPERATION_LIMIT(cx))
return false;
int32_t result;
if (!s1 || !s2 || !CompareChars(s1, l1, s2, l2, &result))
return false;
*lessOrEqualp = (result <= 0);
return true;
}
struct SortComparatorStrings
{
JSContext *const cx;
SortComparatorStrings(JSContext *cx)
@@ -1992,23 +2074,42 @@ struct SortComparatorStrings {
}
};
struct StringValuePair {
Value str;
Value v;
};
struct SortComparatorStringValuePairs {
struct SortComparatorLexicographicInt32
{
JSContext *const cx;
SortComparatorStringValuePairs(JSContext *cx)
SortComparatorLexicographicInt32(JSContext *cx)
: cx(cx) {}
bool operator()(const StringValuePair &a, const StringValuePair &b, bool *lessOrEqualp) {
return CompareStringValues(cx, a.str, b.str, lessOrEqualp);
bool operator()(const Value &a, const Value &b, bool *lessOrEqualp) {
return CompareLexicographicInt32(cx, a, b, lessOrEqualp);
}
};
struct SortComparatorFunction {
struct StringifiedElement
{
size_t charsBegin;
size_t charsEnd;
size_t elementIndex;
};
struct SortComparatorStringifiedElements
{
JSContext *const cx;
const StringBuffer &sb;
SortComparatorStringifiedElements(JSContext *cx, const StringBuffer &sb)
: cx(cx), sb(sb) {}
bool operator()(const StringifiedElement &a, const StringifiedElement &b, bool *lessOrEqualp) {
return CompareSubStringValues(cx, sb.begin() + a.charsBegin, a.charsEnd - a.charsBegin,
sb.begin() + b.charsBegin, b.charsEnd - b.charsBegin,
lessOrEqualp);
}
};
struct SortComparatorFunction
{
JSContext *const cx;
const Value &fval;
InvokeArgsGuard &ag;
@@ -2123,6 +2224,7 @@ js::array_sort(JSContext *cx, uintN argc, Value *vp)
*/
undefs = 0;
bool allStrings = true;
bool allInts = true;
for (jsuint i = 0; i < len; i++) {
if (!JS_CHECK_OPERATION_LIMIT(cx))
return false;
@@ -2140,6 +2242,7 @@ js::array_sort(JSContext *cx, uintN argc, Value *vp)
}
vec.infallibleAppend(v);
allStrings = allStrings && v.isString();
allInts = allInts && v.isInt32();
}
n = vec.length();
@@ -2151,6 +2254,7 @@ js::array_sort(JSContext *cx, uintN argc, Value *vp)
JS_ALWAYS_TRUE(vec.resize(n * 2));
/* Here len == n + undefs + number_of_holes. */
Value *result = vec.begin();
if (fval.isNull()) {
/*
* Sort using the default comparator converting all elements to
@@ -2159,69 +2263,60 @@ js::array_sort(JSContext *cx, uintN argc, Value *vp)
if (allStrings) {
if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorStrings(cx)))
return false;
} else if (allInts) {
if (!MergeSort(vec.begin(), n, vec.begin() + n,
SortComparatorLexicographicInt32(cx))) {
return false;
}
} else {
/*
* To avoid string conversion on each compare we do it only once
* prior to sorting. But we also need the space for the original
* values to recover the sorting result. For that we move the
* original values to the odd indexes in vec, put the string
* conversion results in the even indexes and do the merge sort
* over resulting string-value pairs using an extra allocated
* scratch space.
* Convert all elements to a jschar array in StringBuffer.
* Store the index and length of each stringified element with
* the corresponding index of the element in the array. Sort
* the stringified elements and with this result order the
* original array.
*/
size_t i = n;
do {
--i;
StringBuffer sb(cx);
Vector<StringifiedElement, 0, TempAllocPolicy> strElements(cx);
if (!strElements.reserve(2 * n))
return false;
int cursor = 0;
for (size_t i = 0; i < n; i++) {
if (!JS_CHECK_OPERATION_LIMIT(cx))
return false;
const Value &v = vec[i];
JSString *str = ToString(cx, v);
if (!str)
if (!ValueToStringBuffer(cx, vec[i], sb))
return false;
/*
* Copying v must come first, because the following line
* overwrites v when i == 0.
*/
vec[2 * i + 1] = v;
vec[2 * i].setString(str);
} while (i != 0);
StringifiedElement el = { cursor, sb.length(), i };
strElements.infallibleAppend(el);
cursor = sb.length();
}
AutoValueVector extraScratch(cx);
if (!extraScratch.resize(n * 2))
return false;
if (!MergeSort(reinterpret_cast<StringValuePair *>(vec.begin()), n,
reinterpret_cast<StringValuePair *>(extraScratch.begin()),
SortComparatorStringValuePairs(cx))) {
/* Resize strElements so we can perform the sorting */
JS_ALWAYS_TRUE(strElements.resize(2 * n));
if (!MergeSort(strElements.begin(), n, strElements.begin() + n,
SortComparatorStringifiedElements(cx, sb))) {
return false;
}
/*
* We want to unroot the cached results of toString calls
* before the operation callback has a chance to run the GC.
* So we do not call JS_CHECK_OPERATION_LIMIT in the loop.
*/
i = 0;
do {
vec[i] = vec[2 * i + 1];
} while (++i != n);
/* Order vec[n:2n-1] using strElements.index */
for (size_t i = 0; i < n; i ++)
vec[n + i] = vec[strElements[i].elementIndex];
result = vec.begin() + n;
}
} else {
InvokeArgsGuard args;
if (!MergeSort(vec.begin(), n, vec.begin() + n,
SortComparatorFunction(cx, fval, args)))
{
SortComparatorFunction(cx, fval, args))) {
return false;
}
}
/*
* We no longer need to root the scratch space for the merge sort, so
* unroot it now to make the job of a potential GC under
* InitArrayElements easier.
*/
vec.resize(n);
if (!InitArrayElements(cx, obj, 0, jsuint(n), vec.begin(), DontUpdateTypes))
if (!InitArrayElements(cx, obj, 0, jsuint(n), result, DontUpdateTypes))
return false;
}