【问题标题】:Efficiently find the first of many keywords in a large NSString在大型 NSString 中有效地找到许多关键字中的第一个
【发布时间】:2011-11-18 09:24:58
【问题描述】:

我需要在一个大的NSString(用于解析源代码)中找到所有关键字,而我目前的实现速度太慢,但我不确定如何改进它。

我正在使用NSRegularExpression,假设它比我能写的任何东西都更优化,但性能比我预期的要慢。有谁知道更快的实现方法吗?

目标字符串将包含 utf-8 字符,但关键字本身将始终是纯字母数字 ascii。我想这可以用来优化一些东西?

@implementation MyClass

// i'm storing the regular expression in a static variable, since it never changes and I need to re-use it often
static NSRegularExpression *keywordsExpression;

+ (void)initialize
{
  [super initialize];

  NSArray *keywords = [NSArray arrayWithObjects:@"accumsan", @"adipiscing", @"aliquam", @"aliquet", @"amet", @"ante", @"arcu", @"at", @"commodo", @"congue", @"consectetur", @"consequat", @"convallis", @"cras", @"curabitur", @"cursus", @"dapibus", @"diam", @"dolor", @"dui", @"elit", @"enim", @"erat", @"eros", @"est", @"et", @"eu", @"felis", @"fermentum", @"gravida", @"iaculis", @"id", @"imperdiet", @"integer", @"ipsum", @"lacinia", @"lectus", @"leo", nil];

  NSString *pattern = [NSString stringWithFormat:@"\\b(%@)\\b", [keywords componentsJoinedByString:@"|"]; // \b(accumsan|adipiscing|aliquam|…)\b
  keywordsExpression = [NSRegularExpression regularExpressionWithPattern:pattern] options:NSRegularExpressionCaseInsensitive error:NULL];
}

// this method will be called in quick succession, I need it to be a able to run tens
// of thousands of times per second. The target string is big (50KB or so), but the
// search range is short, rarely more than 30 characters
- (NSRange)findNextKeyword:(NSString *)string inRange:(NSRange)range
{
  return [keywordsExpression rangeOfFirstMatchInString:string options:0 range:range];
}

@end

编辑根据@CodeBrickie 的回答,我更新了我的代码以对整个字符串执行一次正则表达式搜索,并将匹配项保存到缓存的@987654324 @,然后每次调用该方法时,它都会在 NSIndexSet 中搜索关键字范围,而不是搜索字符串。结果大约快了一个数量级:

@implementation MyClass

static NSRegularExpression *keywordsExpression;
static NSIndexSet *keywordIndexes = nil;

+ (void)initialize
{
  [super initialize];

  NSArray *keywords = [NSArray arrayWithObjects:@"accumsan", @"adipiscing", @"aliquam", @"aliquet", @"amet", @"ante", @"arcu", @"at", @"commodo", @"congue", @"consectetur", @"consequat", @"convallis", @"cras", @"curabitur", @"cursus", @"dapibus", @"diam", @"dolor", @"dui", @"elit", @"enim", @"erat", @"eros", @"est", @"et", @"eu", @"felis", @"fermentum", @"gravida", @"iaculis", @"id", @"imperdiet", @"integer", @"ipsum", @"lacinia", @"lectus", @"leo", nil];

  NSString *pattern = [NSString stringWithFormat:@"\\b(%@)\\b", [keywords componentsJoinedByString:@"|"]; // \b(accumsan|adipiscing|aliquam|…)\b
  keywordsExpression = [NSRegularExpression regularExpressionWithPattern:pattern] options:NSRegularExpressionCaseInsensitive error:NULL];
}

- (void)prepareToFindKeywordsInString:(NSString *)string
{
  NSMutableIndexSet *keywordIndexesMutable = [[NSIndexSet indexSet] mutableCopy];
  [keywordsExpression enumerateMatchesInString:string options:0 range:NSMakeRange(0, string.length) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
    [keywordIndexesMutable addIndexesInRange:match.range];
  }];

  keywordIndexes = [keywordIndexesMutable copy];
}

- (NSRange)findNextKeyword:(NSString *)string inRange:(NSRange)range
{
  NSUInteger foundKeywordMax = (foundCharacterSetRange.location == NSNotFound) ? string.length : foundCharacterSetRange.location;
  NSRange foundKeywordRange = NSMakeRange(NSNotFound, 0);
  for (NSUInteger index = startingAt; index < foundKeywordMax; index++) {
    if ([keywordIndexes containsIndex:index]) {
      if (foundKeywordRange.location == NSNotFound) {
        foundKeywordRange.location = index;
        foundKeywordRange.length = 1;
      } else {
        foundKeywordRange.length++;
      }
    } else {
      if (foundKeywordRange.location != NSNotFound) {
        break;
      }
    }
  }

  return foundKeywordRange;
}

@end

这似乎运作良好,并且性能达到了我想要的范围。不过,我想再等一会儿,看看是否有更多建议,然后再接受。

【问题讨论】:

  • 附注 - 您的 findNextKeyword:inRange: 方法不应接受指向 NSRange指针
  • 感谢@JacobRelkin,我刚刚编辑了它。这不是我的实际代码,只是仪器告诉我的部分太慢(我的 CPU 时间的 95%)

标签: objective-c regex string performance cocoa


【解决方案1】:

由于您需要关键字及其范围,我将使用 enumerateMatchesInString:options:range:usingBlock: 并实现一个块,将关键字作为键和范围作为值添加到 NSMutableDictionary

因此,在该调用之后,您只需要一次调用整个字符串以及字典中的所有关键字及其范围。

【讨论】:

  • 听起来是个好主意,但我仍然需要在我在这里发布的-findNextKeyword:inRange: 方法中一次访问一个搜索结果。你能想出一个好的方法来按范围索引搜索结果,这样就可以通过这种方法进行查找了吗?该方法被用于实时解析文本编辑器的源代码(我可能会在未来几周内开源)。
  • 我将尝试为字符串中匹配任何关键字的每个字符创建一个 NSIndexSet。
  • 我已按问题更新以包含您的答案的实现。它运行良好(快一个数量级)。
  • 再次更新了我的问题,实现了更好的实现。 [string hash] 非常慢(看起来NSString 没有缓存结果),所以现在我将NSIndexSet 构建代码移到一个单独的方法中,现在在我开始解析之前调用一次。跨度>
【解决方案2】:

我建议您使用一个非常基本的正则表达式来匹配任何单词,而不是组装一个正则表达式来匹配所有关键字,然后在包含您的关键字的字典中查找匹配的单词;如果该词不存在,请忽略它继续前进。

您可以根据您对关键字的了解来定制正则表达式,以获得最大的效率。例如,如果您知道您只查找由 3 到 12 个小写 ASCII 字母组成的单词,则可以使用 @"\\b[a-z]{3,12}+\\b"。与你的怪物正则表达式相比,它有几十种替代方法。

我在自己的语法高亮项目中成功使用了这种技术。那是在 Java 中,但快速查看 NSRegularExpression 文档会发现一个非常相似的功能集。

【讨论】:

  • 我认为这不适合我的情况,因为绝大多数词都不是关键字。但我会记住的。
  • 这是否包括字符串文字、cmets 等中的单词?这个正则表达式甚至不应该看到那些。
  • 我同意我的正则表达式可能有点慢,但是你建议的下一步,检查文件中的每个单词是否都在NSDictionary 中,肯定会很慢吗?我试试看。
  • 抱歉,您的建议比使用仅匹配我要查找的单词的复杂正则表达式模式慢 25%。毫无疑问,您的模式更快,但是检查是否需要保留或丢弃每个结果会极大地影响性能。
【解决方案3】:

如果您将搜索切换到某些 NSLiteral 搜索而不是不区分大小写,会发生什么情况?

当然你需要不区分大小写,但如果它更快,它会给你一些想法。

我敢打赌,最大速度需要更多的工作,并且涉及小写的 c 字符串和 strstr()。

【讨论】:

  • 我需要它不区分大小写。我意识到 C 代码可能会更快,但我对 C 语言不够熟悉,无法自己编写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-03
  • 2021-08-08
  • 2012-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多