Ignore:
Timestamp:
Jul 25, 2007, 2:50:00 PM (18 years ago)
Author:
mjs
Message:

JavaScriptCore:

Reviewed by Darin.


  • JavaScriptCore part of fix for <rdar://problem/5300291> Optimize GC to reclaim big, temporary objects (like XMLHttpRequest.responseXML) quickly


Also, as a side effect of optimizations included in this patch:

  • 7% speedup on JavaScript iBench
  • 4% speedup on "Celtic Kane" JS benchmark


The basic idea is explained in a big comment in collector.cpp. When unusually
large objecs are allocated, we push the next GC closer on the assumption that
most objects are short-lived.


I also did the following two optimizations in the course of tuning
this not to be a performance regression:

1) Change UString::Rep to hold a self-pointer as the baseString in
the unshared case, instead of a null pointer; this removes a
number of null checks in hot code because many places already
wanted to use the rep itself or the baseString as appropriate.


2) Avoid creating duplicate StringImpls when creating a
StringInstance (the object wrapper for a JS string) or calling
their methods. Since a temporary wrapper object is made every time
a string method is called, this resulted in two useless extra
StringImpls being allocated for no reason whenever a String method
was invoked on a string value. Now we bypass those.


  • kjs/collector.cpp: (KJS::): (KJS::Collector::recordExtraCost): Basics of the extra cost mechanism. (KJS::Collector::allocate): ditto (KJS::Collector::collect): ditto
  • kjs/collector.h: (KJS::Collector::reportExtraMemoryCost): ditto
  • kjs/array_object.cpp: (ArrayInstance::ArrayInstance): record extra cost
  • kjs/internal.cpp: (KJS::StringImp::toObject): don't create a whole new StringImpl just to be the internal value of a StringInstance! StringImpls are immutable so there's no point tot his.
  • kjs/internal.h: (KJS::StringImp::StringImp): report extra cost
  • kjs/string_object.cpp: (KJS::StringInstance::StringInstance): new version that takes a StringImp (KJS::StringProtoFunc::callAsFunction): don't create a whole new StringImpl just to convert self to string! we already have one in the internal value
  • kjs/string_object.h: report extra cost
  • kjs/ustring.cpp: All changes to handle baseString being self instead of null in the unshared case. (KJS::): (KJS::UString::Rep::create): (KJS::UString::Rep::destroy): (KJS::UString::usedCapacity): (KJS::UString::usedPreCapacity): (KJS::UString::expandCapacity): (KJS::UString::expandPreCapacity): (KJS::UString::UString): (KJS::UString::append): (KJS::UString::operator=): (KJS::UString::copyForWriting):
  • kjs/ustring.h: (KJS::UString::Rep::baseIsSelf): new method, now that baseString is self instead of null in the unshared case we can't just null check. (KJS::UString::Rep::data): adjusted as mentioned above (KJS::UString::cost): new method to compute the cost for a UString, for use by StringImpl.
  • kjs/value.cpp: (KJS::jsString): style fixups. (KJS::jsOwnedString): new method, use this for strings allocated from UStrings held by the parse tree. Tracking their cost as part of string cost is pointless, because garbage collecting them will not actually free the relevant string buffer.
  • kjs/value.h: prototyped jsOwnedString.
  • kjs/nodes.cpp: (StringNode::evaluate): use jsOwnedString as appropriate (RegExpNode::evaluate): ditto (PropertyNameNode::evaluate): ditto (ForInNode::execute): ditto


WebCore:

Reviewed by Darin.

  • fixed <rdar://problem/5300291> Optimize GC to reclaim big, temporary objects (like XMLHttpRequest.responseXML) quickly


With this plus related JavaScriptCore changes, a number of XMLHttpRequest situations that
result in huge data sets are addressed, including a single huge responseXML on an XMR done
repeatedly, or accessing responseText repeatedly during loading of a single large XHR.


In addition to the GC changes in JavaScriptCore, I changed responseText to be stored as a
KJS::UString instead of a WebCore::String so that the JavaScript responseText value can
share the buffer (indeed multiple intermediate responseTexts can share its buffer).


First of all, here's some manual test cases that will each blow out the process VM without this fix,
but will settle into decent steady state with.


  • manual-tests/memory: Added.
  • manual-tests/memory/MessageUidsAlreadyDownloaded2: Added.
  • manual-tests/memory/string-growth.html: Added.
  • manual-tests/memory/xhr-multiple-requests-responseText.html: Added.
  • manual-tests/memory/xhr-multiple-requests-responseXML.html: Added.
  • manual-tests/memory/xhr-multiple-requests.html: Added.
  • manual-tests/memory/xhr-repeated-string-access.xml: Added.

And here's the actual code changes:


  • WebCore.xcodeproj/project.pbxproj:
  • bindings/js/JSDocumentCustom.cpp: (WebCore::toJS): Record extra cost if the document is frameless (counting the nodes doesn't make a measurable performance difference here in any case I could find)
  • bindings/js/JSXMLHttpRequest.cpp: (KJS::JSXMLHttpRequest::getValueProperty): Adjust for the fact that ressponseText is now stored as a UString.
  • bindings/js/kjs_binding.cpp: (KJS::jsOwnedStringOrNull): New helper.
  • bindings/js/kjs_binding.h:
  • xml/XMLHttpRequest.cpp: (WebCore::XMLHttpRequest::getResponseText): It's a UString! (WebCore::XMLHttpRequest::getResponseXML): handle the fact that m_responseText is a UString. (WebCore::XMLHttpRequest::XMLHttpRequest): ditto. (WebCore::XMLHttpRequest::abort): call dropProtection (WebCore::XMLHttpRequest::didFinishLoading): call dropProtection (WebCore::XMLHttpRequest::dropProtection): after removing our GC protection, report extra cost of this XHR's responseText buffer.
  • xml/XMLHttpRequest.h:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/kjs/ustring.cpp

    r24394 r24633  
    2727
    2828#include "JSLock.h"
     29#include "collector.h"
    2930#include "dtoa.h"
    3031#include "function.h"
     
    5455extern const double Inf;
    5556
     57// we'd rather not do shared substring append for small strings, since
     58// this runs too much risk of a tiny initial string holding down a
     59// huge buffer. This is also tuned to match the extra cost size, so we
     60// don't ever share a buffer that wouldn't be over the extra cost
     61// threshold already.
     62// FIXME: this should be size_t but that would cause warnings until we
     63// fix UString sizes to be size_t instad of int
     64static const int minShareSize = Collector::minExtraCostSize / sizeof(UChar);
     65
    5666COMPILE_ASSERT(sizeof(UChar) == 2, uchar_is_2_bytes)
    5767
     
    141151// Hack here to avoid a global with a constructor; point to an unsigned short instead of a UChar.
    142152static unsigned short almostUChar;
    143 UString::Rep UString::Rep::null = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
    144 UString::Rep UString::Rep::empty = { 0, 0, 1, 0, 0, 0, reinterpret_cast<UChar*>(&almostUChar), 0, 0, 0, 0 };
     153UString::Rep UString::Rep::null = { 0, 0, 1, 0, 0, &UString::Rep::null, 0, 0, 0, 0, 0 };
     154UString::Rep UString::Rep::empty = { 0, 0, 1, 0, 0, &UString::Rep::empty, reinterpret_cast<UChar*>(&almostUChar), 0, 0, 0, 0 };
    145155const int normalStatBufferSize = 4096;
    146156static char *statBuffer = 0;
     
    183193  ASSERT(JSLock::lockCount() > 0);
    184194
    185   Rep *r = new Rep;
     195  Rep* r = new Rep;
    186196  r->offset = 0;
    187197  r->len = l;
     
    189199  r->_hash = 0;
    190200  r->isIdentifier = 0;
    191   r->baseString = 0;
     201  r->baseString = r;
    192202  r->buf = d;
    193203  r->usedCapacity = l;
     
    207217  int baseOffset = base->offset;
    208218
    209   if (base->baseString) {
    210     base = base->baseString;
    211   }
     219  base = base->baseString;
    212220
    213221  assert(-(offset + baseOffset) <= base->usedPreCapacity);
     
    237245  if (isIdentifier)
    238246    Identifier::remove(this);
    239   if (baseString) {
     247  if (baseString != this) {
    240248    baseString->deref();
    241249  } else {
     
    348356inline int UString::usedCapacity() const
    349357{
    350   return m_rep->baseString ? m_rep->baseString->usedCapacity : m_rep->usedCapacity;
     358  return m_rep->baseString->usedCapacity;
    351359}
    352360
    353361inline int UString::usedPreCapacity() const
    354362{
    355   return m_rep->baseString ? m_rep->baseString->usedPreCapacity : m_rep->usedPreCapacity;
     363  return m_rep->baseString->usedPreCapacity;
    356364}
    357365
    358366void UString::expandCapacity(int requiredLength)
    359367{
    360   Rep *r = m_rep->baseString ? m_rep->baseString : rep();
     368  Rep* r = m_rep->baseString;
    361369
    362370  if (requiredLength > r->capacity) {
     
    372380void UString::expandPreCapacity(int requiredPreCap)
    373381{
    374   Rep *r = m_rep->baseString ? m_rep->baseString : rep();
     382  Rep* r = m_rep->baseString;
    375383
    376384  if (requiredPreCap > r->preCapacity) {
     
    441449    // b is empty
    442450    m_rep = a.m_rep;
    443   } else if (aOffset + aSize == a.usedCapacity() && 4 * aSize >= bSize &&
     451  } else if (aOffset + aSize == a.usedCapacity() && aSize >= minShareSize && 4 * aSize >= bSize &&
    444452             (-bOffset != b.usedPreCapacity() || aSize >= bSize)) {
    445453    // - a reaches the end of its buffer so it qualifies for shared append
     
    454462    } else
    455463        m_rep = &Rep::null;
    456   } else if (-bOffset == b.usedPreCapacity() && 4 * bSize >= aSize) {
     464  } else if (-bOffset == b.usedPreCapacity() && bSize >= minShareSize && 4 * bSize >= aSize) {
    457465    // - b reaches the beginning of its buffer so it qualifies for shared prepend
    458466    // - also, it's at least a quarter the length of a - prepending to a much shorter
     
    680688  } else if (tSize == 0) {
    681689    // t is empty
    682   } else if (!m_rep->baseString && m_rep->rc == 1) {
     690  } else if (m_rep->baseIsSelf() && m_rep->rc == 1) {
    683691    // this is direct and has refcount of 1 (so we can just alter it directly)
    684692    expandCapacity(thisOffset + length);
     
    686694    m_rep->len = length;
    687695    m_rep->_hash = 0;
    688   } else if (thisOffset + thisSize == usedCapacity()) {
    689     // this reaches the end of the buffer - extend it
     696  } else if (thisOffset + thisSize == usedCapacity() && thisSize >= minShareSize) {
     697    // this reaches the end of the buffer - extend it if it's long enough to append to
    690698    expandCapacity(thisOffset + length);
    691699    memcpy(const_cast<UChar *>(data() + thisSize), t.data(), tSize * sizeof(UChar));
     
    717725  } else if (tSize == 0) {
    718726    // t is empty, we'll just return *this below.
    719   } else if (!m_rep->baseString && m_rep->rc == 1) {
     727  } else if (m_rep->baseIsSelf() && m_rep->rc == 1) {
    720728    // this is direct and has refcount of 1 (so we can just alter it directly)
    721729    expandCapacity(thisOffset + length);
     
    725733    m_rep->len = length;
    726734    m_rep->_hash = 0;
    727   } else if (thisOffset + thisSize == usedCapacity()) {
     735  } else if (thisOffset + thisSize == usedCapacity() && thisSize >= minShareSize) {
    728736    // this string reaches the end of the buffer - extend it
    729737    expandCapacity(thisOffset + length);
     
    759767    m_rep = Rep::create(d, 1);
    760768    m_rep->capacity = newCapacity;
    761   } else if (!m_rep->baseString && m_rep->rc == 1) {
     769  } else if (m_rep->baseIsSelf() && m_rep->rc == 1) {
    762770    // this is direct and has refcount of 1 (so we can just alter it directly)
    763771    expandCapacity(thisOffset + length + 1);
     
    766774    m_rep->len = length + 1;
    767775    m_rep->_hash = 0;
    768   } else if (thisOffset + length == usedCapacity()) {
     776  } else if (thisOffset + length == usedCapacity() && length >= minShareSize) {
    769777    // this reaches the end of the string - extend it and share
    770778    expandCapacity(thisOffset + length + 1);
     
    831839  int l = c ? static_cast<int>(strlen(c)) : 0;
    832840  UChar *d;
    833   if (m_rep->rc == 1 && l <= m_rep->capacity && !m_rep->baseString && m_rep->offset == 0 && m_rep->preCapacity == 0) {
     841  if (m_rep->rc == 1 && l <= m_rep->capacity && m_rep->baseIsSelf() && m_rep->offset == 0 && m_rep->preCapacity == 0) {
    834842    d = m_rep->buf;
    835843    m_rep->_hash = 0;
     
    11291137void UString::copyForWriting()
    11301138{
    1131   if (m_rep->rc > 1 || m_rep->baseString) {
     1139  if (m_rep->rc > 1 || !m_rep->baseIsSelf()) {
    11321140    int l = size();
    11331141    UChar *n = static_cast<UChar *>(fastMalloc(sizeof(UChar) * l));
Note: See TracChangeset for help on using the changeset viewer.