- Revision
- 116659
- Author
- [email protected]
- Date
- 2012-05-10 10:32:39 -0700 (Thu, 10 May 2012)
Log Message
Enh: Hash Const JSString in Backing Stores to Save Memory
https://bugs.webkit.org/show_bug.cgi?id=86024
Reviewed by Filip Pizlo.
During garbage collection, each marking thread keeps a HashMap of
strings. While visiting via MarkStack::copyAndAppend(), we check to
see if the string we are visiting is already in the HashMap. If not
we add it. If so, we change the reference to the current string we're
visiting to the prior string.
To somewhat reduce the performance impact of this change, if a string
is unique at the end of a marking it will not be checked during further
GC phases. In some cases this won't catch all duplicates, but we are
trying to catch the growth of duplicate strings.
* heap/Heap.cpp:
(JSC::Heap::markRoots):
* heap/MarkStack.cpp:
(JSC::MarkStackThreadSharedData::resetChildren): New method called by the
main thread to reset the slave threads. This is primarily done to
clear the m_uniqueStrings HashMap.
(JSC):
(JSC::MarkStackThreadSharedData::markingThreadMain):
(JSC::MarkStackThreadSharedData::markingThreadStartFunc):
(JSC::MarkStackThreadSharedData::MarkStackThreadSharedData):
(JSC::MarkStackThreadSharedData::reset):
(JSC::MarkStack::reset): Added call to clear m_uniqueStrings.
(JSC::MarkStack::internalAppend): New method that performs the hash consting.
(JSC::SlotVisitor::copyAndAppend): Changed to call the new hash consting
internalAppend()
* heap/MarkStack.h:
(MarkStackThreadSharedData):
(MarkStack):
(JSC::MarkStack::sharedData):
* runtime/JSString.h:
(JSString): Added m_isHashConstSingleton flag, accessors for the flag and
code to initialize the flag.
(JSC::JSString::finishCreation):
(JSC::JSString::isHashConstSingleton):
(JSC::JSString::clearHashConstSingleton):
(JSC::JSString::setHashConstSingleton):
(JSC::JSRopeString::finishCreation):
Modified Paths
Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (116658 => 116659)
--- trunk/Source/_javascript_Core/ChangeLog 2012-05-10 17:26:32 UTC (rev 116658)
+++ trunk/Source/_javascript_Core/ChangeLog 2012-05-10 17:32:39 UTC (rev 116659)
@@ -1,3 +1,49 @@
+2012-05-10 Michael Saboff <[email protected]>
+
+ Enh: Hash Const JSString in Backing Stores to Save Memory
+ https://bugs.webkit.org/show_bug.cgi?id=86024
+
+ Reviewed by Filip Pizlo.
+
+ During garbage collection, each marking thread keeps a HashMap of
+ strings. While visiting via MarkStack::copyAndAppend(), we check to
+ see if the string we are visiting is already in the HashMap. If not
+ we add it. If so, we change the reference to the current string we're
+ visiting to the prior string.
+
+ To somewhat reduce the performance impact of this change, if a string
+ is unique at the end of a marking it will not be checked during further
+ GC phases. In some cases this won't catch all duplicates, but we are
+ trying to catch the growth of duplicate strings.
+
+ * heap/Heap.cpp:
+ (JSC::Heap::markRoots):
+ * heap/MarkStack.cpp:
+ (JSC::MarkStackThreadSharedData::resetChildren): New method called by the
+ main thread to reset the slave threads. This is primarily done to
+ clear the m_uniqueStrings HashMap.
+ (JSC):
+ (JSC::MarkStackThreadSharedData::markingThreadMain):
+ (JSC::MarkStackThreadSharedData::markingThreadStartFunc):
+ (JSC::MarkStackThreadSharedData::MarkStackThreadSharedData):
+ (JSC::MarkStackThreadSharedData::reset):
+ (JSC::MarkStack::reset): Added call to clear m_uniqueStrings.
+ (JSC::MarkStack::internalAppend): New method that performs the hash consting.
+ (JSC::SlotVisitor::copyAndAppend): Changed to call the new hash consting
+ internalAppend()
+ * heap/MarkStack.h:
+ (MarkStackThreadSharedData):
+ (MarkStack):
+ (JSC::MarkStack::sharedData):
+ * runtime/JSString.h:
+ (JSString): Added m_isHashConstSingleton flag, accessors for the flag and
+ code to initialize the flag.
+ (JSC::JSString::finishCreation):
+ (JSC::JSString::isHashConstSingleton):
+ (JSC::JSString::clearHashConstSingleton):
+ (JSC::JSString::setHashConstSingleton):
+ (JSC::JSRopeString::finishCreation):
+
2012-05-09 Filip Pizlo <[email protected]>
JIT memory allocator is not returning memory to the OS on Darwin
Modified: trunk/Source/_javascript_Core/heap/Heap.cpp (116658 => 116659)
--- trunk/Source/_javascript_Core/heap/Heap.cpp 2012-05-10 17:26:32 UTC (rev 116658)
+++ trunk/Source/_javascript_Core/heap/Heap.cpp 2012-05-10 17:32:39 UTC (rev 116659)
@@ -656,6 +656,9 @@
visitor.doneCopying();
visitor.reset();
m_sharedData.reset();
+#if ENABLE(PARALLEL_GC)
+ m_sharedData.resetChildren();
+#endif
m_storageSpace.doneCopying();
m_operationInProgress = NoOperation;
Modified: trunk/Source/_javascript_Core/heap/MarkStack.cpp (116658 => 116659)
--- trunk/Source/_javascript_Core/heap/MarkStack.cpp 2012-05-10 17:26:32 UTC (rev 116658)
+++ trunk/Source/_javascript_Core/heap/MarkStack.cpp 2012-05-10 17:32:39 UTC (rev 116659)
@@ -36,6 +36,7 @@
#include "JSObject.h"
#include "ScopeChain.h"
#include "Structure.h"
+#include "UString.h"
#include "WriteBarrier.h"
#include <wtf/MainThread.h>
@@ -218,17 +219,24 @@
}
#if ENABLE(PARALLEL_GC)
-void MarkStackThreadSharedData::markingThreadMain()
+void MarkStackThreadSharedData::resetChildren()
{
+ for (unsigned i = 0; i < m_slaveMarkStacks.size(); ++i)
+ m_slaveMarkStacks[i]->reset();
+}
+
+void MarkStackThreadSharedData::markingThreadMain(SlotVisitor* slotVisitor)
+{
WTF::registerGCThread();
- SlotVisitor slotVisitor(*this);
- ParallelModeEnabler enabler(slotVisitor);
- slotVisitor.drainFromShared(SlotVisitor::SlaveDrain);
+ ParallelModeEnabler enabler(*slotVisitor);
+ slotVisitor->drainFromShared(SlotVisitor::SlaveDrain);
+ delete slotVisitor;
}
-void MarkStackThreadSharedData::markingThreadStartFunc(void* shared)
+void MarkStackThreadSharedData::markingThreadStartFunc(void* myVisitor)
{
- static_cast<MarkStackThreadSharedData*>(shared)->markingThreadMain();
+ SlotVisitor* slotVisitor = static_cast<SlotVisitor*>(myVisitor);
+ slotVisitor->sharedData().markingThreadMain(slotVisitor);
}
#endif
@@ -241,7 +249,9 @@
{
#if ENABLE(PARALLEL_GC)
for (unsigned i = 1; i < Options::numberOfGCMarkers; ++i) {
- m_markingThreads.append(createThread(markingThreadStartFunc, this, "_javascript_Core::Marking"));
+ SlotVisitor* slotVisitor = new SlotVisitor(*this);
+ m_slaveMarkStacks.append(slotVisitor);
+ m_markingThreads.append(createThread(markingThreadStartFunc, slotVisitor, "_javascript_Core::Marking"));
ASSERT(m_markingThreads.last());
}
#endif
@@ -273,7 +283,6 @@
#else
ASSERT(m_opaqueRoots.isEmpty());
#endif
-
m_weakReferenceHarvesters.removeAll();
}
@@ -286,6 +295,7 @@
#else
m_opaqueRoots.clear();
#endif
+ m_uniqueStrings.clear();
}
void MarkStack::append(ConservativeRoots& conservativeRoots)
@@ -486,6 +496,34 @@
return CopiedSpace::allocateFromBlock(m_copyBlock, bytes);
}
+inline void MarkStack::internalAppend(JSValue* slot)
+{
+ ASSERT(slot);
+ JSValue value = *slot;
+ ASSERT(value);
+ if (!value.isCell())
+ return;
+
+ if (value.isString()) {
+ JSString* string = jsCast<JSString*>(value.asCell());
+ if (!string->isHashConstSingleton() && string->length() > 1 && !string->isRope()) {
+ UniqueStringMap::AddResult addResult = m_uniqueStrings.add(string->string().impl(), value);
+ if (addResult.isNewEntry)
+ string->setHashConstSingleton();
+ else {
+ JSValue existingJSValue = addResult.iterator->second;
+ if (value != existingJSValue)
+ jsCast<JSString*>(existingJSValue.asCell())->clearHashConstSingleton();
+ *slot = existingJSValue;
+ return;
+ }
+ }
+ }
+
+ internalAppend(value.asCell());
+}
+
+
void SlotVisitor::copyAndAppend(void** ptr, size_t bytes, JSValue* values, unsigned length)
{
void* oldPtr = *ptr;
@@ -499,7 +537,7 @@
newValues[i] = value;
if (!value)
continue;
- internalAppend(value);
+ internalAppend(&newValues[i]);
}
memcpy(newPtr, oldPtr, jsValuesOffset);
Modified: trunk/Source/_javascript_Core/heap/MarkStack.h (116658 => 116659)
--- trunk/Source/_javascript_Core/heap/MarkStack.h 2012-05-10 17:26:32 UTC (rev 116658)
+++ trunk/Source/_javascript_Core/heap/MarkStack.h 2012-05-10 17:32:39 UTC (rev 116659)
@@ -34,12 +34,14 @@
#include "UnconditionalFinalizer.h"
#include "VTableSpectrum.h"
#include "WeakReferenceHarvester.h"
+#include <wtf/Forward.h>
#include <wtf/HashMap.h>
#include <wtf/HashSet.h>
#include <wtf/Vector.h>
#include <wtf/Noncopyable.h>
#include <wtf/OSAllocator.h>
#include <wtf/PageBlock.h>
+#include <wtf/text/StringHash.h>
namespace JSC {
@@ -171,13 +173,17 @@
~MarkStackThreadSharedData();
void reset();
-
+
+#if ENABLE(PARALLEL_GC)
+ void resetChildren();
+#endif
+
private:
friend class MarkStack;
friend class SlotVisitor;
#if ENABLE(PARALLEL_GC)
- void markingThreadMain();
+ void markingThreadMain(SlotVisitor*);
static void markingThreadStartFunc(void* heap);
#endif
@@ -187,6 +193,7 @@
MarkStackSegmentAllocator m_segmentAllocator;
Vector<ThreadIdentifier> m_markingThreads;
+ Vector<MarkStack*> m_slaveMarkStacks;
Mutex m_markingLock;
ThreadCondition m_markingCondition;
@@ -221,13 +228,14 @@
void addOpaqueRoot(void*);
bool containsOpaqueRoot(void*);
int opaqueRootCount();
-
+
+ MarkStackThreadSharedData& sharedData() { return m_shared; }
bool isEmpty() { return m_stack.isEmpty(); }
void reset();
size_t visitCount() const { return m_visitCount; }
-
+
#if ENABLE(SIMPLE_HEAP_PROFILING)
VTableSpectrum m_visitedTypeCounts;
#endif
@@ -251,6 +259,7 @@
void internalAppend(JSCell*);
void internalAppend(JSValue);
+ void internalAppend(JSValue*);
JS_EXPORT_PRIVATE void mergeOpaqueRoots();
@@ -270,6 +279,8 @@
MarkStackArray m_stack;
HashSet<void*> m_opaqueRoots; // Handle-owning data structures not visible to the garbage collector.
+ typedef HashMap<StringImpl*, JSValue> UniqueStringMap;
+ UniqueStringMap m_uniqueStrings;
#if !ASSERT_DISABLED
public:
Modified: trunk/Source/_javascript_Core/runtime/JSString.h (116658 => 116659)
--- trunk/Source/_javascript_Core/runtime/JSString.h 2012-05-10 17:26:32 UTC (rev 116658)
+++ trunk/Source/_javascript_Core/runtime/JSString.h 2012-05-10 17:32:39 UTC (rev 116659)
@@ -67,6 +67,7 @@
friend class JSGlobalData;
friend class SpecializedThunkJIT;
friend class JSRopeString;
+ friend class MarkStack;
friend struct ThunkHelpers;
typedef JSCell Base;
@@ -91,6 +92,7 @@
Base::finishCreation(globalData);
m_length = length;
m_is8Bit = m_value.impl()->is8Bit();
+ m_isHashConstSingleton = false;
}
void finishCreation(JSGlobalData& globalData, size_t length, size_t cost)
@@ -99,6 +101,7 @@
Base::finishCreation(globalData);
m_length = length;
m_is8Bit = m_value.impl()->is8Bit();
+ m_isHashConstSingleton = false;
Heap::heap(this)->reportExtraMemoryCost(cost);
}
@@ -108,6 +111,7 @@
Base::finishCreation(globalData);
m_length = 0;
m_is8Bit = true;
+ m_isHashConstSingleton = false;
}
public:
@@ -161,9 +165,13 @@
protected:
bool isRope() const { return m_value.isNull(); }
bool is8Bit() const { return m_is8Bit; }
+ bool isHashConstSingleton() const { return m_isHashConstSingleton; }
+ void clearHashConstSingleton() { m_isHashConstSingleton = false; }
+ void setHashConstSingleton() { m_isHashConstSingleton = true; }
// A string is represented either by a UString or a rope of fibers.
bool m_is8Bit : 1;
+ bool m_isHashConstSingleton : 1;
unsigned m_length;
mutable UString m_value;
@@ -233,6 +241,7 @@
Base::finishCreation(globalData);
m_length = s1->length() + s2->length();
m_is8Bit = (s1->is8Bit() && s2->is8Bit());
+ m_isHashConstSingleton = false;
m_fibers[0].set(globalData, this, s1);
m_fibers[1].set(globalData, this, s2);
}
@@ -242,6 +251,7 @@
Base::finishCreation(globalData);
m_length = s1->length() + s2->length() + s3->length();
m_is8Bit = (s1->is8Bit() && s2->is8Bit() && s3->is8Bit());
+ m_isHashConstSingleton = false;
m_fibers[0].set(globalData, this, s1);
m_fibers[1].set(globalData, this, s2);
m_fibers[2].set(globalData, this, s3);