Title: [174516] trunk/Source/WebCore
Revision
174516
Author
[email protected]
Date
2014-10-09 12:05:51 -0700 (Thu, 09 Oct 2014)

Log Message

[Mac] Spending too much time mapping desired font families to available ones
https://bugs.webkit.org/show_bug.cgi?id=137539

Reviewed by Darin Adler.

While profiling the load of weather.com, I noticed that we are spending
quite a bit of time trying to map the font family requested to a font
that is available on the system. The process involves:
1. Doing a linear search of all the installed font families and do a
   case-insensitive string comparison for each of them until we find a
   match,
2. Then, if we don't find a match, do another linear search of the
   fonts' postscript names this time and do again a case-insensitive
   string comparison for each of them.

This process is costly and the fonts requested by weather.com are not
available, causing us to do 2 linear searches and a lot of string
comparisons (accounting for ~2% of the WebProcess CPU time for the page
load). As a result, we end up spending ~90ms in
internalFontWithFamily() when loading weather.com.

This patch introduces a cache for the mapping between desired font
families and available font families. This cuts the time spent in
internalFontWithFamily() in half (~45ms). The cache gets invalidated
when fonts are installed / uninstalled on the system so we don't break
that scenario. The cache is also limited in size to avoid using too
much memory.

No new tests, but manual testing making sure the cache gets invalidated
when installing a font on the system.

* platform/graphics/mac/FontCacheMac.mm:
(WebCore::invalidateFontCache):
* platform/mac/WebFontCache.h:
* platform/mac/WebFontCache.mm:
(desiredFamilyToAvailableFamilyDictionary):
(rememberDesiredFamilyToAvailableFamilyMapping):
(+[WebFontCache internalFontWithFamily:traits:weight:size:]):
(+[WebFontCache invalidate]):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (174515 => 174516)


--- trunk/Source/WebCore/ChangeLog	2014-10-09 18:43:25 UTC (rev 174515)
+++ trunk/Source/WebCore/ChangeLog	2014-10-09 19:05:51 UTC (rev 174516)
@@ -1,3 +1,45 @@
+2014-10-09  Chris Dumez  <[email protected]>
+
+        [Mac] Spending too much time mapping desired font families to available ones
+        https://bugs.webkit.org/show_bug.cgi?id=137539
+
+        Reviewed by Darin Adler.
+
+        While profiling the load of weather.com, I noticed that we are spending
+        quite a bit of time trying to map the font family requested to a font
+        that is available on the system. The process involves:
+        1. Doing a linear search of all the installed font families and do a
+           case-insensitive string comparison for each of them until we find a
+           match,
+        2. Then, if we don't find a match, do another linear search of the
+           fonts' postscript names this time and do again a case-insensitive
+           string comparison for each of them.
+
+        This process is costly and the fonts requested by weather.com are not
+        available, causing us to do 2 linear searches and a lot of string
+        comparisons (accounting for ~2% of the WebProcess CPU time for the page
+        load). As a result, we end up spending ~90ms in
+        internalFontWithFamily() when loading weather.com.
+
+        This patch introduces a cache for the mapping between desired font
+        families and available font families. This cuts the time spent in
+        internalFontWithFamily() in half (~45ms). The cache gets invalidated
+        when fonts are installed / uninstalled on the system so we don't break
+        that scenario. The cache is also limited in size to avoid using too
+        much memory.
+
+        No new tests, but manual testing making sure the cache gets invalidated
+        when installing a font on the system.
+
+        * platform/graphics/mac/FontCacheMac.mm:
+        (WebCore::invalidateFontCache):
+        * platform/mac/WebFontCache.h:
+        * platform/mac/WebFontCache.mm:
+        (desiredFamilyToAvailableFamilyDictionary):
+        (rememberDesiredFamilyToAvailableFamilyMapping):
+        (+[WebFontCache internalFontWithFamily:traits:weight:size:]):
+        (+[WebFontCache invalidate]):
+
 2014-10-09  Bear Travis  <[email protected]>
 
         [CSS Font Loading] Decrement the font loading count before notifying callbacks

Modified: trunk/Source/WebCore/platform/graphics/mac/FontCacheMac.mm (174515 => 174516)


--- trunk/Source/WebCore/platform/graphics/mac/FontCacheMac.mm	2014-10-09 18:43:25 UTC (rev 174515)
+++ trunk/Source/WebCore/platform/graphics/mac/FontCacheMac.mm	2014-10-09 19:05:51 UTC (rev 174516)
@@ -54,6 +54,7 @@
         return;
     }
     fontCache().invalidate();
+    [WebFontCache invalidate];
 }
 
 static void fontCacheRegisteredFontsChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef name, const void *, CFDictionaryRef)

Modified: trunk/Source/WebCore/platform/mac/WebFontCache.h (174515 => 174516)


--- trunk/Source/WebCore/platform/mac/WebFontCache.h	2014-10-09 18:43:25 UTC (rev 174515)
+++ trunk/Source/WebCore/platform/mac/WebFontCache.h	2014-10-09 19:05:51 UTC (rev 174516)
@@ -32,6 +32,7 @@
 + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size shouldAutoActivateIfNeeded:(BOOL)shouldAutoActivateIfNeeded;
 + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size;
 + (void)getTraits:(Vector<unsigned>&)traitsMasks inFamily:(NSString *)desiredFamily;
++ (void)invalidate;
 
 // This older version of the interface is relied upon by some clients. WebCore doesn't use it.
 + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size;

Modified: trunk/Source/WebCore/platform/mac/WebFontCache.mm (174515 => 174516)


--- trunk/Source/WebCore/platform/mac/WebFontCache.mm	2014-10-09 18:43:25 UTC (rev 174515)
+++ trunk/Source/WebCore/platform/mac/WebFontCache.mm	2014-10-09 19:05:51 UTC (rev 174516)
@@ -35,6 +35,7 @@
 #import <AppKit/AppKit.h>
 #import <Foundation/Foundation.h>
 #import <math.h>
+#import <wtf/MainThread.h>
 
 using namespace WebCore;
 
@@ -115,6 +116,30 @@
                                    FontWeight900Mask));
 }
 
+// Keep a cache for mapping desired font families to font families actually
+// available on the system for performance.
+static NSMutableDictionary* desiredFamilyToAvailableFamilyDictionary()
+{
+    ASSERT(isMainThread());
+    static NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+    return dictionary;
+}
+
+static inline void rememberDesiredFamilyToAvailableFamilyMapping(NSString* desiredFamily, NSString* availableFamily)
+{
+    static const NSUInteger maxCacheSize = 128;
+    NSMutableDictionary *familyMapping = desiredFamilyToAvailableFamilyDictionary();
+    ASSERT([familyMapping count] <= maxCacheSize);
+    if ([familyMapping count] == maxCacheSize) {
+        for (NSString *key in familyMapping) {
+            [familyMapping removeObjectForKey:key];
+            break;
+        }
+    }
+    id value = availableFamily ? availableFamily : [NSNull null];
+    [familyMapping setObject:value forKey:desiredFamily];
+}
+
 @implementation WebFontCache
 
 + (void)getTraits:(Vector<unsigned>&)traitsMasks inFamily:(NSString *)desiredFamily
@@ -160,48 +185,55 @@
 // we then do a search based on the family names of the installed fonts.
 + (NSFont *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size
 {
-
     if (stringIsCaseInsensitiveEqualToString(desiredFamily, @"-webkit-system-font")
         || stringIsCaseInsensitiveEqualToString(desiredFamily, @"-apple-system-font")) {
         // We ignore italic for system font.
         return (desiredWeight >= 7) ? [NSFont boldSystemFontOfSize:size] : [NSFont systemFontOfSize:size];
     }
 
-    NSFontManager *fontManager = [NSFontManager sharedFontManager];
-
-    // Do a simple case insensitive search for a matching font family.
-    // NSFontManager requires exact name matches.
-    // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
-    NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
-    NSString *availableFamily;
-    while ((availableFamily = [e nextObject])) {
-        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
-            break;
+    id cachedAvailableFamily = [desiredFamilyToAvailableFamilyDictionary() objectForKey:desiredFamily];
+    if (cachedAvailableFamily == [NSNull null]) {
+        // We already know this font is not available.
+        return nil;
     }
 
+    NSFontManager *fontManager = [NSFontManager sharedFontManager];
+    NSString *availableFamily = cachedAvailableFamily;
     if (!availableFamily) {
-        // Match by PostScript name.
-        NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
-        NSString *availableFont;
-        NSFont *nameMatchedFont = nil;
-        NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0);
-        while ((availableFont = [availableFonts nextObject])) {
-            if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
-                nameMatchedFont = [NSFont fontWithName:availableFont size:size];
+        // Do a simple case insensitive search for a matching font family.
+        // NSFontManager requires exact name matches.
+        // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
+        for (availableFamily in [fontManager availableFontFamilies]) {
+            if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
+                break;
+        }
 
-                // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to 
-                // treat Osaka-Mono as fixed pitch.
-                if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraitsForNameMatch == 0)
-                    return nameMatchedFont;
+        if (!availableFamily) {
+            // Match by PostScript name.
+            NSFont *nameMatchedFont = nil;
+            NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0);
+            for (NSString *availableFont in [fontManager availableFonts]) {
+                if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
+                    nameMatchedFont = [NSFont fontWithName:availableFont size:size];
 
-                NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
-                if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch)
-                    return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch];
+                    // Special case Osaka-Mono. According to <rdar://problem/3999467>, we need to
+                    // treat Osaka-Mono as fixed pitch.
+                    if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && !desiredTraitsForNameMatch)
+                        return nameMatchedFont;
 
-                availableFamily = [nameMatchedFont familyName];
-                break;
+                    NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
+                    if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch)
+                        return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch];
+
+                    availableFamily = [nameMatchedFont familyName];
+                    break;
+                }
             }
         }
+
+        rememberDesiredFamilyToAvailableFamilyMapping(desiredFamily, availableFamily);
+        if (!availableFamily)
+            return nil;
     }
 
     // Found a family, now figure out what weight and traits to use.
@@ -300,4 +332,8 @@
     return [self fontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size shouldAutoActivateIfNeeded:YES];
 }
 
++ (void)invalidate
+{
+    [desiredFamilyToAvailableFamilyDictionary() removeAllObjects];
+}
 @end
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to