从CString初始化NSString的最快方法是什么?

时间:2013-10-09 20:13:12

标签: objective-c nsstring malloc

我需要尽可能快地从cStrings(来自数据库)中分配很多NSString对象。 cStringUsingEncoding和类似的东西太慢 - 比分配cString慢大约10-15倍。

但是,使用NSString创建NSString非常接近cString分配(1M分配大约1.2s)。编辑:修复了alloc使用字符串的副本。

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(大约8s,因此比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

3 个答案:

答案 0 :(得分:2)

在我看来,这不是一个公平的考验。

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

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

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

  4. 您可以看到CFStringUsingByteStream3 in this file的源代码。

答案 1 :(得分:1)

Microbenchmarks是一个很大的分心,但很少有用。但在这种情况下,有效性。

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

正如您所发现的,最快的是不要复制。 initWithBytesNoCopy:...完全存在以解决此案例。因此,您需要创建一个包含原始字符串缓冲区的数据构造,并管理指向它的所有NSString个实例作为一个内聚单元。

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


这是一个额外的细节,这是一个CoreData-esque ORM层(并且,不,我不会建议你做错了因为你的描述确实听起来像你需要那个级别的控制),然后如上所述,您的ORM层似乎是管理这些字符串的理想位置。

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

答案 2 :(得分:1)

根据我的评论和Brian的回答,我认为这里的问题是要创建NSString,你必须解析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