我正在使用ANTLR3为C目标编写ASN1的词法分析器,使用该库的3.4版本。实际上词法分析器真的很慢,所以我在执行时执行了一个perf记录,我发现瓶颈是库函数ANTLR38BitConsume,这非常简单。
static void
antlr38BitConsume(pANTLR3_INT_STREAM is)
{
pANTLR3_INPUT_STREAM input;
input = ((pANTLR3_INPUT_STREAM) (is->super));
if ((pANTLR3_UINT8)(input->nextChar) < (((pANTLR3_UINT8)input->data) + input->sizeBuf))
{
/* Indicate one more character in this line
*/
input->charPositionInLine++;
if ((ANTLR3_UCHAR)(*((pANTLR3_UINT8)input->nextChar)) == input->newlineChar)
{
/* Reset for start of a new line of input
*/
input->line++;
input->charPositionInLine = 0;
input->currentLine = (void *)(((pANTLR3_UINT8)input->nextChar) + 1);
}
/* Increment to next character position
*/
input->nextChar = (void *)(((pANTLR3_UINT8)input->nextChar) + 1);
}
}
经过调查,我发现在一个大小为1.4KB的文件中,这个函数,我认为每个字节最多应调用一次,它被称为24.5M次。你知道这是一个已知的问题还是对这种奇怪的行为有另一种解释?
EDITED
经过一些试验,我已经找出导致这个问题的规则。我有一些规则来识别特定的对象标识符,这很简单:
OID1 : {counter==6}?=> 2 3 4 5 840 {counter=0;}
和一个匹配ASN1编码的内容字段中每个字节的规则:
VALUE : ({counter>0}?=> '\u0000'..'\u00FF' {counter--;})+
由于词法分析器使用最长匹配规则,因此永远不会匹配OID1,因为VALUE可以匹配任意长值。因此,为了欺骗词法分析器,我以这种方式编辑OID1规则:
OID1 : {counter==6}?=> 2 3 4 5 840 {counter=0;} VALUE?
规则末尾的VALUE令牌永远不会匹配,因为该规则仅在counter大于0时才有效,但这样lexer在匹配时也会考虑OID1,因为它可能比值。 但是,这个规则导致了对BitConsume函数的大量调用!一旦删除VALUE?部分,我得到了一些调用,精确等于输入文件的字节数。 我认为这是ANTLR的实现错误,因为它应该尝试将OID1之后的输入与VALUE匹配,但它应该立即停止并跳过令牌,因为计数器为0.