11

AppKit 中有没有办法真正快速地测量大量 NSString 对象(比如一百万)的宽度?我尝试了 3 种不同的方法来做到这一点:

  • [NSString sizeWithAttributes:]
  • [NSAttributedString 大小]
  • NSLayoutManager(获取文本宽度而不是高度)

    这是一些性能指标

    计数\机制    sizeWithAttributes     NSAttributedString     NSLayoutManager
    1000                0.057                  0.031                  0.007
    10000               0.329                  0.325                  0.064
    100000              3.06                   3.14                   0.689
    1000000             29.5                   31.3                   7.06



    NSLayoutManager 显然是要走的路,但问题是

  • 由于创建了重量级的NSTextStorage对象 ,内存占用高(根据分析器超过 1GB) 。
  • 高创建时间。所花费的所有时间都是在创建上述字符串期间,这本身就是一个破坏者。(随后测量具有创建和布局的字形的 NSTextStorage 对象只需要大约 0.0002 秒)。
  • 7 秒对于我想要做的事情来说仍然太慢了。有更快的方法吗?在一秒钟内测量一百万根弦?

    如果你想玩,这里是 github 项目。

  • 4

    1 回答 1

    3

    这里有一些我没有尝试过的想法。

    1. 直接使用核心文本。其他 API 都建立在它之上。

    2. 并行化。所有现代 Mac(甚至所有现代 iOS 设备)都有多个内核。将字符串数组分成几个子数组。对于每个子数组,将一个块提交到全局 GCD 队列。在块中,创建必要的核心文本或NSLayoutManager对象并测量子数组中的字符串。两种 API 都可以通过这种方式安全使用。(核心文本) ( NSLayoutManager)

    3. 关于“高内存占用”:使用本地自动释放池块来减少峰值内存占用。

    4. 关于“所有时间都花在创建上述字符串期间,这本身就是一个破坏者”:您是说所有时间都花在了这些行上:

      double random = (double)arc4random_uniform(1000) / 1000;
      NSString *randomNumber = [NSString stringWithFormat:@"%f", random];
      

      格式化浮点数的成本很高。这是你真正的用例吗?如果您只想格式化 n/1000 形式的随机有理数,0 ≤ n < 1000,有更快的方法。此外,在许多字体中,所有数字都具有相同的宽度,因此很容易排版数字列。如果您选择这样的字体,则可以避免首先测量字符串。

    更新

    这是我使用 Core Text 想出的最快的代码。在我的 Core i7 MacBook Pro 上,分发版本的速度几乎是单线程版本的两倍。我的你的项目的分支在这里

    static CGFloat maxWidthOfStringsUsingCTFramesetter(
            NSArray *strings, NSRange range) {
        NSString *bigString =
            [[strings subarrayWithRange:range] componentsJoinedByString:@"\n"];
        NSAttributedString *richText =
            [[NSAttributedString alloc]
                initWithString:bigString
                attributes:@{ NSFontAttributeName: (__bridge NSFont *)font }];
        CGPathRef path =
            CGPathCreateWithRect(CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), NULL);
        CGFloat width = 0.0;
        CTFramesetterRef setter =
            CTFramesetterCreateWithAttributedString(
                (__bridge CFAttributedStringRef)richText);
        CTFrameRef frame =
            CTFramesetterCreateFrame(
                setter, CFRangeMake(0, bigString.length), path, NULL);
        NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
        for (id item in lines) {
            CTLineRef line = (__bridge CTLineRef)item;
            width = MAX(width, CTLineGetTypographicBounds(line, NULL, NULL, NULL));
        }
        CFRelease(frame);
        CFRelease(setter);
        CFRelease(path);
        return (CGFloat)width;
    }
    
    static void test_CTFramesetter() {
        runTest(__func__, ^{
            return maxWidthOfStringsUsingCTFramesetter(
                testStrings, NSMakeRange(0, testStrings.count));
        });
    }
    
    static void test_CTFramesetter_dispatched() {
        runTest(__func__, ^{
            dispatch_queue_t gatherQueue = dispatch_queue_create(
                "test_CTFramesetter_dispatched result-gathering queue", nil);
            dispatch_queue_t runQueue =
                dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
            dispatch_group_t group = dispatch_group_create();
    
            __block CGFloat gatheredWidth = 0.0;
    
            const size_t Parallelism = 16;
            const size_t totalCount = testStrings.count;
            // Force unsigned long to get 64-bit math to avoid overflow for
            // large totalCounts.
            for (unsigned long i = 0; i < Parallelism; ++i) {
                NSUInteger start = (totalCount * i) / Parallelism;
                NSUInteger end = (totalCount * (i + 1)) / Parallelism;
                NSRange range = NSMakeRange(start, end - start);
                dispatch_group_async(group, runQueue, ^{
                    double width =
                        maxWidthOfStringsUsingCTFramesetter(testStrings, range);
                    dispatch_sync(gatherQueue, ^{
                        gatheredWidth = MAX(gatheredWidth, width);
                    });
                });
            }
    
            dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
            return gatheredWidth;
        });
    }
    
    于 2015-05-31T20:44:22.867 回答