Title: [143133] trunk/Source/_javascript_Core
Revision
143133
Author
[email protected]
Date
2013-02-17 11:15:30 -0800 (Sun, 17 Feb 2013)

Log Message

Code cache should be explicit about what it caches
https://bugs.webkit.org/show_bug.cgi?id=110039

Reviewed by Oliver Hunt.

This patch makes the code cache more explicit in two ways:

(1) The cache caches top-level scripts. Any sub-functions executed as a
part of a script are cached with it and evicted with it.

This simplifies things by eliminating out-of-band sub-function tracking,
and fixes pathological cases where functions for live scripts would be
evicted in favor of functions for dead scripts, and/or high probability
functions executed early in script lifetime would be evicted in favor of
low probability functions executed late in script lifetime, due to LRU.

Statistical data from general browsing and PLT confirms that caching
functions independently of scripts is not profitable.

(2) The cache tracks script size, not script count.

This reduces the worst-case cache size by a factor of infinity.

Script size is a reasonable first-order estimate of in-memory footprint 
for a cached script because there are no syntactic constructs that have
super-linear memory footprint.

* bytecode/UnlinkedCodeBlock.cpp:
(JSC::generateFunctionCodeBlock): Moved this function out of the cache
because it does not consult the cache, and is not managed by it.

(JSC::UnlinkedFunctionExecutable::visitChildren): Visit our code blocks
because they are strong references now, rather than weak, a la (1).

(JSC::UnlinkedFunctionExecutable::codeBlockFor): Updated for interface changes.

* bytecode/UnlinkedCodeBlock.h:
(UnlinkedFunctionExecutable):
(UnlinkedFunctionCodeBlock): Strong now, not weak, a la (1).

* runtime/CodeCache.cpp:
(JSC::CodeCache::CodeCache):
* runtime/CodeCache.h:
(JSC::SourceCodeKey::length):
(SourceCodeKey):
(CodeCacheMap):
(JSC::CodeCacheMap::CodeCacheMap):
(JSC::CodeCacheMap::find):
(JSC::CodeCacheMap::set):
(JSC::CodeCacheMap::clear):
(CodeCache):
(JSC::CodeCache::clear): Removed individual function tracking, due to (1).
Added explicit character counting, for (2).

You might think 16000000 characters is a lot. It is. But this patch
didn't establish that limit -- it just took the existing limit and
made it more visible. I intend to reduce the size of the cache in a
future patch.

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (143132 => 143133)


--- trunk/Source/_javascript_Core/ChangeLog	2013-02-17 18:59:45 UTC (rev 143132)
+++ trunk/Source/_javascript_Core/ChangeLog	2013-02-17 19:15:30 UTC (rev 143133)
@@ -1,3 +1,64 @@
+2013-02-16  Geoffrey Garen  <[email protected]>
+
+        Code cache should be explicit about what it caches
+        https://bugs.webkit.org/show_bug.cgi?id=110039
+
+        Reviewed by Oliver Hunt.
+
+        This patch makes the code cache more explicit in two ways:
+
+        (1) The cache caches top-level scripts. Any sub-functions executed as a
+        part of a script are cached with it and evicted with it.
+
+        This simplifies things by eliminating out-of-band sub-function tracking,
+        and fixes pathological cases where functions for live scripts would be
+        evicted in favor of functions for dead scripts, and/or high probability
+        functions executed early in script lifetime would be evicted in favor of
+        low probability functions executed late in script lifetime, due to LRU.
+
+        Statistical data from general browsing and PLT confirms that caching
+        functions independently of scripts is not profitable.
+
+        (2) The cache tracks script size, not script count.
+
+        This reduces the worst-case cache size by a factor of infinity.
+
+        Script size is a reasonable first-order estimate of in-memory footprint 
+        for a cached script because there are no syntactic constructs that have
+        super-linear memory footprint.
+
+        * bytecode/UnlinkedCodeBlock.cpp:
+        (JSC::generateFunctionCodeBlock): Moved this function out of the cache
+        because it does not consult the cache, and is not managed by it.
+
+        (JSC::UnlinkedFunctionExecutable::visitChildren): Visit our code blocks
+        because they are strong references now, rather than weak, a la (1).
+
+        (JSC::UnlinkedFunctionExecutable::codeBlockFor): Updated for interface changes.
+
+        * bytecode/UnlinkedCodeBlock.h:
+        (UnlinkedFunctionExecutable):
+        (UnlinkedFunctionCodeBlock): Strong now, not weak, a la (1).
+
+        * runtime/CodeCache.cpp:
+        (JSC::CodeCache::CodeCache):
+        * runtime/CodeCache.h:
+        (JSC::SourceCodeKey::length):
+        (SourceCodeKey):
+        (CodeCacheMap):
+        (JSC::CodeCacheMap::CodeCacheMap):
+        (JSC::CodeCacheMap::find):
+        (JSC::CodeCacheMap::set):
+        (JSC::CodeCacheMap::clear):
+        (CodeCache):
+        (JSC::CodeCache::clear): Removed individual function tracking, due to (1).
+        Added explicit character counting, for (2).
+
+        You might think 16000000 characters is a lot. It is. But this patch
+        didn't establish that limit -- it just took the existing limit and
+        made it more visible. I intend to reduce the size of the cache in a
+        future patch.
+
 2013-02-16  Filip Pizlo  <[email protected]>
 
         Remove support for bytecode comments, since it doesn't build, and hasn't been used in a while.

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp (143132 => 143133)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp	2013-02-17 18:59:45 UTC (rev 143132)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.cpp	2013-02-17 19:15:30 UTC (rev 143133)
@@ -46,6 +46,29 @@
 const ClassInfo UnlinkedEvalCodeBlock::s_info = { "UnlinkedEvalCodeBlock", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(UnlinkedEvalCodeBlock) };
 const ClassInfo UnlinkedFunctionCodeBlock::s_info = { "UnlinkedFunctionCodeBlock", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(UnlinkedFunctionCodeBlock) };
 
+static UnlinkedFunctionCodeBlock* generateFunctionCodeBlock(JSGlobalData& globalData, UnlinkedFunctionExecutable* executable, const SourceCode& source, CodeSpecializationKind kind, DebuggerMode debuggerMode, ProfilerMode profilerMode, ParserError& error)
+{
+    RefPtr<FunctionBodyNode> body = parse<FunctionBodyNode>(&globalData, source, executable->parameters(), executable->name(), executable->isInStrictContext() ? JSParseStrict : JSParseNormal, JSParseFunctionCode, error);
+
+    if (!body) {
+        ASSERT(error.m_type != ParserError::ErrorNone);
+        return 0;
+    }
+
+    if (executable->forceUsesArguments())
+        body->setUsesArguments();
+    body->finishParsing(executable->parameters(), executable->name(), executable->functionNameIsInScopeToggle());
+    executable->recordParse(body->features(), body->hasCapturedVariables(), body->lineNo(), body->lastLine());
+    
+    UnlinkedFunctionCodeBlock* result = UnlinkedFunctionCodeBlock::create(&globalData, FunctionCode, ExecutableInfo(body->needsActivation(), body->usesEval(), body->isStrictMode(), kind == CodeForConstruct));
+    OwnPtr<BytecodeGenerator> generator(adoptPtr(new BytecodeGenerator(globalData, body.get(), result, debuggerMode, profilerMode)));
+    error = generator->generate();
+    body->destroyData();
+    if (error.m_type != ParserError::ErrorNone)
+        return 0;
+    return result;
+}
+
 unsigned UnlinkedCodeBlock::addOrFindConstant(JSValue v)
 {
     unsigned numberOfConstants = numberOfConstantRegisters();
@@ -82,6 +105,8 @@
     COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
     ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren());
     Base::visitChildren(thisObject, visitor);
+    visitor.append(&thisObject->m_codeBlockForCall);
+    visitor.append(&thisObject->m_codeBlockForConstruct);
     visitor.append(&thisObject->m_nameValue);
     visitor.append(&thisObject->m_symbolTableForCall);
     visitor.append(&thisObject->m_symbolTableForConstruct);
@@ -112,31 +137,27 @@
 {
     switch (specializationKind) {
     case CodeForCall:
-        if (UnlinkedFunctionCodeBlock* codeBlock = m_codeBlockForCall.get()) {
-            globalData.codeCache()->usedFunctionCode(globalData, codeBlock);
+        if (UnlinkedFunctionCodeBlock* codeBlock = m_codeBlockForCall.get())
             return codeBlock;
-        }
         break;
     case CodeForConstruct:
-        if (UnlinkedFunctionCodeBlock* codeBlock = m_codeBlockForConstruct.get()) {
-            globalData.codeCache()->usedFunctionCode(globalData, codeBlock);
+        if (UnlinkedFunctionCodeBlock* codeBlock = m_codeBlockForConstruct.get())
             return codeBlock;
-        }
         break;
     }
 
-    UnlinkedFunctionCodeBlock* result = globalData.codeCache()->getFunctionCodeBlock(globalData, this, source, specializationKind, debuggerMode, profilerMode, error);
+    UnlinkedFunctionCodeBlock* result = generateFunctionCodeBlock(globalData, this, source, specializationKind, debuggerMode, profilerMode, error);
     
     if (error.m_type != ParserError::ErrorNone)
         return 0;
 
     switch (specializationKind) {
     case CodeForCall:
-        m_codeBlockForCall = PassWeak<UnlinkedFunctionCodeBlock>(result);
+        m_codeBlockForCall.set(globalData, this, result);
         m_symbolTableForCall.set(globalData, this, result->symbolTable());
         break;
     case CodeForConstruct:
-        m_codeBlockForConstruct = PassWeak<UnlinkedFunctionCodeBlock>(result);
+        m_codeBlockForConstruct.set(globalData, this, result);
         m_symbolTableForConstruct.set(globalData, this, result->symbolTable());
         break;
     }

Modified: trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h (143132 => 143133)


--- trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h	2013-02-17 18:59:45 UTC (rev 143132)
+++ trunk/Source/_javascript_Core/bytecode/UnlinkedCodeBlock.h	2013-02-17 19:15:30 UTC (rev 143133)
@@ -139,8 +139,8 @@
 
 private:
     UnlinkedFunctionExecutable(JSGlobalData*, Structure*, const SourceCode&, FunctionBodyNode*);
-    Weak<UnlinkedFunctionCodeBlock> m_codeBlockForCall;
-    Weak<UnlinkedFunctionCodeBlock> m_codeBlockForConstruct;
+    WriteBarrier<UnlinkedFunctionCodeBlock> m_codeBlockForCall;
+    WriteBarrier<UnlinkedFunctionCodeBlock> m_codeBlockForConstruct;
 
     unsigned m_numCapturedVariables : 29;
     bool m_forceUsesArguments : 1;
@@ -675,9 +675,7 @@
 };
 
 class UnlinkedFunctionCodeBlock : public UnlinkedCodeBlock {
-private:
-    friend class CodeCache;
-
+public:
     static UnlinkedFunctionCodeBlock* create(JSGlobalData* globalData, CodeType codeType, const ExecutableInfo& info)
     {
         UnlinkedFunctionCodeBlock* instance = new (NotNull, allocateCell<UnlinkedFunctionCodeBlock>(globalData->heap)) UnlinkedFunctionCodeBlock(globalData, globalData->unlinkedFunctionCodeBlockStructure.get(), codeType, info);
@@ -685,7 +683,6 @@
         return instance;
     }
 
-public:
     typedef UnlinkedCodeBlock Base;
     static void destroy(JSCell*);
 

Modified: trunk/Source/_javascript_Core/runtime/CodeCache.cpp (143132 => 143133)


--- trunk/Source/_javascript_Core/runtime/CodeCache.cpp	2013-02-17 18:59:45 UTC (rev 143132)
+++ trunk/Source/_javascript_Core/runtime/CodeCache.cpp	2013-02-17 19:15:30 UTC (rev 143133)
@@ -37,6 +37,7 @@
 namespace JSC {
 
 CodeCache::CodeCache()
+    : m_sourceCode(CacheSize)
 {
 }
 
@@ -102,35 +103,6 @@
     return getCodeBlock<UnlinkedEvalCodeBlock>(globalData, executable, source, strictness, debuggerMode, profilerMode, error);
 }
 
-UnlinkedFunctionCodeBlock* CodeCache::generateFunctionCodeBlock(JSGlobalData& globalData, UnlinkedFunctionExecutable* executable, const SourceCode& source, CodeSpecializationKind kind, DebuggerMode debuggerMode, ProfilerMode profilerMode, ParserError& error)
-{
-    RefPtr<FunctionBodyNode> body = parse<FunctionBodyNode>(&globalData, source, executable->parameters(), executable->name(), executable->isInStrictContext() ? JSParseStrict : JSParseNormal, JSParseFunctionCode, error);
-
-    if (!body) {
-        ASSERT(error.m_type != ParserError::ErrorNone);
-        return 0;
-    }
-
-    if (executable->forceUsesArguments())
-        body->setUsesArguments();
-    body->finishParsing(executable->parameters(), executable->name(), executable->functionNameIsInScopeToggle());
-    executable->recordParse(body->features(), body->hasCapturedVariables(), body->lineNo(), body->lastLine());
-    
-    UnlinkedFunctionCodeBlock* result = UnlinkedFunctionCodeBlock::create(&globalData, FunctionCode, ExecutableInfo(body->needsActivation(), body->usesEval(), body->isStrictMode(), kind == CodeForConstruct));
-    OwnPtr<BytecodeGenerator> generator(adoptPtr(new BytecodeGenerator(globalData, body.get(), result, debuggerMode, profilerMode)));
-    error = generator->generate();
-    body->destroyData();
-    if (error.m_type != ParserError::ErrorNone)
-        return 0;
-    m_recentlyUsedFunctions.set(result, Strong<UnlinkedFunctionCodeBlock>(globalData, result));
-    return result;
-}
-
-UnlinkedFunctionCodeBlock* CodeCache::getFunctionCodeBlock(JSGlobalData& globalData, UnlinkedFunctionExecutable* executable, const SourceCode& source, CodeSpecializationKind kind, DebuggerMode debuggerMode, ProfilerMode profilerMode, ParserError& error)
-{
-    return generateFunctionCodeBlock(globalData, executable, source, kind, debuggerMode, profilerMode, error);
-}
-
 UnlinkedFunctionExecutable* CodeCache::getFunctionExecutableFromGlobalCode(JSGlobalData& globalData, const Identifier& name, const SourceCode& source, ParserError& error)
 {
     SourceCodeKey key = SourceCodeKey(source, name.string(), SourceCodeKey::FunctionCallType, JSParseNormal);
@@ -162,9 +134,4 @@
     return functionExecutable;
 }
 
-void CodeCache::usedFunctionCode(JSGlobalData& globalData, UnlinkedFunctionCodeBlock* codeBlock)
-{
-    m_recentlyUsedFunctions.set(codeBlock, Strong<UnlinkedFunctionCodeBlock>(globalData, codeBlock));
 }
-
-}

Modified: trunk/Source/_javascript_Core/runtime/CodeCache.h (143132 => 143133)


--- trunk/Source/_javascript_Core/runtime/CodeCache.h	2013-02-17 18:59:45 UTC (rev 143132)
+++ trunk/Source/_javascript_Core/runtime/CodeCache.h	2013-02-17 19:15:30 UTC (rev 143133)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2012, 2013 Apple Inc. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -53,34 +53,6 @@
 class SourceCode;
 class SourceProvider;
 
-template <int CacheSize, typename KeyType, typename EntryType, typename HashArg = typename DefaultHash<KeyType>::Hash, typename KeyTraitsArg = HashTraits<KeyType> >
-class CacheMap {
-    typedef HashMap<KeyType, EntryType, HashArg, KeyTraitsArg> MapType;
-    typedef typename MapType::iterator iterator;
-
-public:
-    const EntryType* find(const KeyType& key)
-    {
-        iterator result = m_map.find(key);
-        if (result == m_map.end())
-            return 0;
-        return &result->value;
-    }
-
-    void set(const KeyType& key, const EntryType& value)
-    {
-        if (m_map.size() >= CacheSize)
-            m_map.remove(m_map.begin());
-
-        m_map.set(key, value);
-    }
-
-    void clear() { m_map.clear(); }
-
-private:
-    MapType m_map;
-};
-
 class SourceCodeKey {
 public:
     enum CodeType { EvalType, ProgramType, FunctionCallType, FunctionConstructType };
@@ -106,6 +78,8 @@
 
     unsigned hash() const { return m_sourceString.impl()->hash(); }
 
+    size_t length() const { return m_sourceString.length(); }
+
     bool isNull() const { return m_sourceString.isNull(); }
 
     bool operator==(const SourceCodeKey& other) const
@@ -132,37 +106,73 @@
     static bool isEmptyValue(const SourceCodeKey& sourceCodeKey) { return sourceCodeKey.isNull(); }
 };
 
+class CodeCacheMap {
+    typedef HashMap<SourceCodeKey, Strong<JSCell>, SourceCodeKeyHash, SourceCodeKeyHashTraits> MapType;
+    typedef typename MapType::iterator iterator;
+
+public:
+    CodeCacheMap(size_t capacity)
+        : m_size(0)
+        , m_capacity(capacity)
+    {
+    }
+
+    const Strong<JSCell>* find(const SourceCodeKey& key)
+    {
+        iterator result = m_map.find(key);
+        if (result == m_map.end())
+            return 0;
+        return &result->value;
+    }
+
+    void set(const SourceCodeKey& key, const Strong<JSCell>& value)
+    {
+        while (m_size >= m_capacity) {
+            MapType::iterator it = m_map.begin();
+            m_size -= it->key.length();
+            m_map.remove(it);
+        }
+
+        m_size += key.length();
+        m_map.set(key, value);
+    }
+
+    void clear()
+    {
+        m_size = 0;
+        m_map.clear();
+    }
+
+private:
+    MapType m_map;
+    size_t m_size;
+    size_t m_capacity;
+};
+
+// Caches top-level code such as <script>, eval(), new Function, and JSEvaluateScript().
 class CodeCache {
 public:
     static PassOwnPtr<CodeCache> create() { return adoptPtr(new CodeCache); }
 
     UnlinkedProgramCodeBlock* getProgramCodeBlock(JSGlobalData&, ProgramExecutable*, const SourceCode&, JSParserStrictness, DebuggerMode, ProfilerMode, ParserError&);
     UnlinkedEvalCodeBlock* getEvalCodeBlock(JSGlobalData&, EvalExecutable*, const SourceCode&, JSParserStrictness, DebuggerMode, ProfilerMode, ParserError&);
-    UnlinkedFunctionCodeBlock* getFunctionCodeBlock(JSGlobalData&, UnlinkedFunctionExecutable*, const SourceCode&, CodeSpecializationKind, DebuggerMode, ProfilerMode, ParserError&);
     UnlinkedFunctionExecutable* getFunctionExecutableFromGlobalCode(JSGlobalData&, const Identifier&, const SourceCode&, ParserError&);
-    void usedFunctionCode(JSGlobalData&, UnlinkedFunctionCodeBlock*);
     ~CodeCache();
 
     void clear()
     {
         m_sourceCode.clear();
-        m_recentlyUsedFunctions.clear();
     }
 
 private:
     CodeCache();
 
-    UnlinkedFunctionCodeBlock* generateFunctionCodeBlock(JSGlobalData&, UnlinkedFunctionExecutable*, const SourceCode&, CodeSpecializationKind, DebuggerMode, ProfilerMode, ParserError&);
+    template <class UnlinkedCodeBlockType, class ExecutableType> 
+    UnlinkedCodeBlockType* getCodeBlock(JSGlobalData&, ExecutableType*, const SourceCode&, JSParserStrictness, DebuggerMode, ProfilerMode, ParserError&);
 
-    template <class UnlinkedCodeBlockType, class ExecutableType> inline UnlinkedCodeBlockType* getCodeBlock(JSGlobalData&, ExecutableType*, const SourceCode&, JSParserStrictness, DebuggerMode, ProfilerMode, ParserError&);
+    enum { CacheSize = 16000000 }; // Size in characters
 
-    enum {
-        MaxRootEntries = 1280, // Top-level code such as <script>, eval(), JSEvaluateScript(), etc.
-        MaxChildFunctionEntries = MaxRootEntries * 8 // Sampling shows that each root holds about 6 functions. 8 is enough to usually cache all the child functions for each top-level entry.
-    };
-
-    CacheMap<MaxRootEntries, SourceCodeKey, Strong<JSCell>, SourceCodeKeyHash, SourceCodeKeyHashTraits> m_sourceCode;
-    CacheMap<MaxChildFunctionEntries, UnlinkedFunctionCodeBlock*, Strong<UnlinkedFunctionCodeBlock> > m_recentlyUsedFunctions;
+    CodeCacheMap m_sourceCode;
 };
 
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to