【问题标题】:Fast alternative to sscanfsscanf 的快速替代方案
【发布时间】:2019-11-09 11:32:51
【问题描述】:

我正在用 C 语言为嵌入式系统上的文本文件编写解析器。我需要每秒使用 sscanf 函数数百次,这会造成性能问题。因此,我正在尝试寻找 sscanf 的快速替代方案。

我需要解析的文本文件包含格式为“parameter=%d,%d,%d,%d”的参数。参数名称长度不同,但参数始终由 4 个整数值组成。

解析器逐行读取文本文件的一行并将字符串存储在变量“token”中。变量“format”包含格式为“parameter=%d,%d,%d,%d”的字符串。

void Parser_GetQuadToken( char* token, const char* format, int16_t* res1, int16_t* res2, int16_t* res3, int16_t* res4 )
{
    uint32_t var1, var2, var3, var4;
    sscanf( token, format, &var1, &var2, &var3, &var4 ) );
    *res1 = var1;
    *res2 = var2;
    *res3 = var3;
    *res4 = var4;
}

有人知道如何实施快速替代方案吗?

【问题讨论】:

  • 更改format 并直接分配给参数? if (sscanf(token, format, res1, res2, res3, res4) != 4) /* error */;
  • 你有没有分析过这个例程是瓶颈而不是 I/O?
  • 你也可以传递更短的字符串:传递token+10(跳过“参数=”)和“%d,%d,%d,%d”的格式——这样可以加快速度有点。
  • 使用strchr 找到=,然后反复调用strtol 转换数字可能会更好。 strtol 给你一个指向你没有转换的第一个数字字符的指针,你可以检查它是否是 ,,如果是,循环并再次转换。
  • 或者如果函数调用开销是个问题,你可以用我的手来做这件事,在字符串中移动一个char *指针,寻找=和数字和,并滚动你自己内联atoi.

标签: c parsing embedded


【解决方案1】:

sscanf() 必须将格式字符串解释为与您的token 匹配并转换字符串内容。您可以使用atoi()等直接转换函数来避免不必要的格式字符串解析:

#include <string.h>
#include <stdlib.h>

void Parser_GetQuadToken( const char* token, int16_t* res1, int16_t* res2, int16_t* res3, int16_t* res4 )
{
    char* resstr = strchr( token, '=' ) + 1 ;
    *res1 = (int16_t)atoi( resstr ) ;

    resstr = strchr( resstr, ',' ) + 1 ;
    *res2 = (int16_t)atoi( resstr ) ;

    resstr = strchr( resstr, ',' ) + 1 ;
    *res3 = (int16_t)atoi( resstr ) ;

    resstr = strchr( resstr, ',' ) + 1 ;
    *res4 = (int16_t)atoi( resstr ) ;
}

由于目标是提高性能,因此您需要衡量改进。我在 VC++ 的 64 位调试版本中测得它快 9 倍,在 32 位代码中快 12 倍。用于您的目标和编译器的 YMMV。优化影响不大,因为它绑定在库代码中。

请注意,上述实现具有与原始代码一样多的错误检查 - 即没有。您需要确定输入 token 作为前提条件将是有效的。在我的测试中,添加错误检查对性能影响不大:

bool Parser_GetQuadToken( const char* token, int16_t* res1, int16_t* res2, int16_t* res3, int16_t* res4 )
{
    char* resstr = 0 ;
    if( (resstr = strchr( token, '=' )) != NULL )
    {
        *res1 = (int16_t)atoi( ++resstr ) ;

        if( (resstr = strchr( resstr, ',' )) != NULL )
        {
            *res2 = (int16_t)atoi( ++resstr ) ;

            if( (resstr = strchr( resstr, ',' )) != NULL )
            {
                *res3 = (int16_t)atoi( ++resstr ) ;

                if( (resstr = strchr( resstr, ',' )) != NULL )
                {
                    *res4 = (int16_t)atoi( ++resstr ) ;
                }
            }
        }
    }

    return resstr != NULL ;
}

但它可能不会产生您期望的影响 - 文件 I/O 将比sscanf() 施加的内存和字符串操作慢得多 - 在嵌入式系统中sscanf() 的问题通常是代码空间和需要使用堆栈。

@SteveSummit 建议使用strtol()。由于这会跟踪扫描的“最后位置”,以避免重复字符串。您可以利用它来省略对逗号分隔符的显式搜索:

void Parser_GetQuadToken( const char* token, int16_t* res1, int16_t* res2, int16_t* res3, int16_t* res4 )
{
    char* resstr = strchr( token, '=' ) ;
    *res1 = (int16_t)strtol( ++resstr, &resstr, 10 ) ;
    *res2 = (int16_t)strtol( ++resstr, &resstr, 10 ) ;
    *res3 = (int16_t)strtol( ++resstr, &resstr, 10 ) ;
    *res4 = (int16_t)strtol( ++resstr, &resstr, 10 ) ;
}

在我的测试中,这在调试中快了大约 12 倍,在优化中快了 17 倍 - 然而,它在 Windows 上的测试有些可变。我会把错误检查留给你考虑。

【讨论】:

  • 通过调用strchr,然后调用atoi,您将不必要地扫描所有内容两次。为什么不使用strtol
  • @Clifford:非常感谢!这对我帮助很大。它在我的嵌入式系统上大约快 10 倍。
  • 另外,atoi 没有错误处理,所以无论如何都不应该使用它。如果不需要负数,请使用strtol,或者更好的strtoul
  • @Lundin 因为性能是这里的关键,而原始代码没有进行任何检查,我故意将错误检查留给 OP。如果在此之前已经检查过输入,则检查可能是多余的。 GIGO。它明显更快。 我的工作已经完成。如果当然要考虑您的建议。
  • strto... 的性能与atoi 相当,但后者通常不安全,因此不应使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-14
  • 2021-09-11
  • 1970-01-01
  • 2013-10-03
相关资源
最近更新 更多