util: optimize expand_escape() by avoiding sscanf()

sscanf() is notoriously slow, and the default scripts have lots of hex
escapes.  This patch reduces Zeek's startup time by 9%.

Before:

            245.04 msec task-clock:u              #    1.002 CPUs utilized
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
            16,411      page-faults:u             #    0.067 M/sec
       629,238,575      cycles:u                  #    2.568 GHz
     1,237,236,556      instructions:u            #    1.97  insn per cycle
       262,223,957      branches:u                # 1070.142 M/sec
         3,351,083      branch-misses:u           #    1.28% of all branches

After:

            220.99 msec task-clock:u              #    1.002 CPUs utilized
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
            16,419      page-faults:u             #    0.074 M/sec
       544,603,653      cycles:u                  #    2.464 GHz
     1,065,862,324      instructions:u            #    1.96  insn per cycle
       229,207,957      branches:u                # 1037.181 M/sec
         3,045,270      branch-misses:u           #    1.33% of all branches
This commit is contained in:
Max Kellermann 2020-01-31 10:17:42 +01:00
parent 70b45d1aba
commit 0b3317b1c2

View file

@ -292,6 +292,26 @@ int streq(const char* s1, const char* s2)
return ! strcmp(s1, s2); return ! strcmp(s1, s2);
} }
static constexpr int ParseOctDigit(char ch) noexcept
{
if (ch >= '0' && ch <= '7')
return ch - '0';
else
return -1;
}
static constexpr int ParseHexDigit(char ch) noexcept
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'a' && ch <= 'f')
return 0xa + ch - 'a';
else if (ch >= 'A' && ch <= 'F')
return 0xa + ch - 'A';
else
return -1;
}
int expand_escape(const char*& s) int expand_escape(const char*& s)
{ {
switch ( *(s++) ) { switch ( *(s++) ) {
@ -309,23 +329,29 @@ int expand_escape(const char*& s)
--s; // put back the first octal digit --s; // put back the first octal digit
const char* start = s; const char* start = s;
// Don't increment inside loop control // require at least one octal digit and parse at most three
// because if isdigit() is a macro it might
// expand into multiple increments ...
// Here we define a maximum length for escape sequence int result = ParseOctDigit(*s++);
// to allow easy handling of string like: "^H0" as if (result < 0)
// "\0100".
for ( int len = 0; len < 3 && isascii(*s) && isdigit(*s);
++s, ++len)
;
int result;
if ( sscanf(start, "%3o", &result) != 1 )
{ {
reporter->Warning("bad octal escape: %s ", start); reporter->Warning("bad octal escape: %s", start);
result = 0; return 0;
}
// second digit?
int digit = ParseOctDigit(*s);
if (digit >= 0)
{
result = (result << 3) | digit;
++s;
// third digit?
digit = ParseOctDigit(*s);
if (digit >= 0)
{
result = (result << 3) | digit;
++s;
}
} }
return result; return result;
@ -336,15 +362,20 @@ int expand_escape(const char*& s)
const char* start = s; const char* start = s;
// Look at most 2 characters, so that "\x0ddir" -> "^Mdir". // Look at most 2 characters, so that "\x0ddir" -> "^Mdir".
for ( int len = 0; len < 2 && isascii(*s) && isxdigit(*s);
++s, ++len)
;
int result; int result = ParseHexDigit(*s++);
if ( sscanf(start, "%2x", &result) != 1 ) if (result < 0)
{ {
reporter->Warning("bad hexadecimal escape: %s", start); reporter->Warning("bad hexadecimal escape: %s", start);
result = 0; return 0;
}
// second digit?
int digit = ParseHexDigit(*s);
if (digit >= 0)
{
result = (result << 4) | digit;
++s;
} }
return result; return result;