2

我需要尽可能快地从 cStrings(来自数据库的方式)分配大量 NSString 对象。cStringUsingEncoding 之类的东西太慢了——与分配 cString 相比,慢了大约 10-15 倍。

但是,使用 NSString 创建 NSString 非常接近于 cString 分配(1M 分配大约需要 1.2 秒)。编辑:固定分配使用字符串的副本。

const char *n;
const char *s = "Office für iPad: Steve Ballmer macht Hoffnung";
NSString *str = [NSString stringWithUTF8String:s];
int len = strlen(s);
for (int i = 0; i<10000000; i++) {
    NSString *s = [[NSString alloc] initWithString:[str copy]];
    s = s;
}

cString 分配测试(对于 1M 分配也大约 1s):

for (int i = 0; i<10000000; i++) {
    n = malloc(len);
    memccpy((void*)n, s, 0, len) ;
    n = n;
    free(n);
}

但正如我所说,使用 stringWithCString 之类的方法要慢一个数量级。我能得到的最快的是使用 initWithBytesNoCopy(大约 8 秒,因此与 stringWithString 相比慢 8 倍):

NSString *so = [[NSString alloc] initWithBytesNoCopy:(void*)n length:len encoding:NSUTF8StringEncoding freeWhenDone:YES];

那么,是否有另一种神奇的方法可以更快地从 cStrings 进行分配?我什至不排除继承 NSString (是的,我知道它是一个集群类)。

编辑:在仪器中,我看到 NSString 对 CFStringUsingByteStream3 的调用是根本问题。

编辑2:根本问题是根据仪器__CFFromUTF8。仅查看来源 [1],这似乎确实效率很低并且处理了一些遗留案例。

https://www.opensource.apple.com/source/CF/CF-476.17/CFBuiltinConverters.c?txt

4

3 回答 3

2

在我看来,这不是一个公平的测试。

  1. cString 分配测试看起来正在分配一个字节数组并复制数据。我不能确定,因为不包括变量定义。

  2. NSString *s = [[NSString alloc] initWithString:str]; 正在采用现有的 NSString (数据已经采用正确的格式)并且可能只是增加保留计数。即使强制复制,数据仍然是正确的编码,只需要复制。

  3. [NSString stringWithUTF8String:s]; 必须处理 UTF8 编码并从一种编码 (UTF8) 转换为内部 NSString/CFString 编码。正在使用的方法 (CFStreamUsingByteStream) 支持多种编码 (UTF8/UTF16/UTF32/others)。专门的 UTF8 only 方法可能会更快,但这会导致问题是这真的是一个性能问题还是只是一个练习。

您可以在此文件中查看CFStringUsingByteStream3的源代码。

于 2013-10-09T21:24:46.057 回答
1

根据我的评论和布赖恩的回答,我认为这里的问题是要创建NSStrings 你必须解析 UTF-8 字符串。那么问题来了:你真的需要解析它们吗?

如果按需解析是一种选择,那么我建议您编写一个代理,该代理可以模拟NSString以下接口:

@interface BJLazyUTF8String: NSProxy
- (id)initWithBytes:(const char *)bytes length:(size_t)length;
@end

所以它不是的子类,NSString也不会尝试提供任何真正的功能。在里面init只保留字节,例如_bytes,为您的 C 内存所有权做任何正确的事情。然后:

- (NSString *)bjRealString
{
    // we'd better create the NSString if we haven't already

    if(!_string)
        _string = [NSString stringWithUTF8String:_bytes];

    return _string;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // if this is invoked then someone is trying to
    // make a call to what they think is a string;
    // let's forward that call to a string so that
    // it does what they expect
    [anInvocation setTarget:[self bjRealString]];
    [anInvocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
     return [[self bjRealString] methodSignatureForSelector:aSelector];
}

然后你可以这样做:

NSString *myString = [[BJLazyUTF8String alloc] initWithBytes:... length:...];

随后将myString其视为NSString.

于 2013-10-09T22:02:32.620 回答
1

微基准测试非常分散注意力,但很少有用。但是,在这种情况下,它是有效的。

假设,目前,您实际上已经将字符串创建视为性能问题的真正根源,那么真正的问题可以更好地表达为如何减少内存带宽?因为那确实是您的问题所在;您导致大量数据被复制到新分配的缓冲区中。

正如你所发现的,你能做到的最快的就是完全不复制。 initWithBytesNoCopy:...正是为了解决这种情况而存在。因此,您需要创建一个数据结构来保存原始字符串缓冲区并将NSString指向它的所有实例作为一个内聚单元进行管理。

如果不仔细考虑,您可能会将原始缓冲区封装在 NSData 实例中,然后使用关联对象创建从字符串实例到该NSData实例的强引用。这样,当最后一个字符串被释放时,NSData(和相关的内存)将被释放。


加上这是针对CoreData-esque ORM层的额外细节(而且,不,我不会建议你做错了,因为你的描述确实听起来你需要那种级别的控制),然后看起来如上所述,您的 ORM 层将是管理这些字符串的理想场所。

我还鼓励您研究 FMDB 之类的东西,看看它是否可以提供您需要的封装以及添加附加功能的灵活性(以及使其快速的钩子)。

于 2013-10-09T20:47:06.440 回答