xstrstr: speed up analysis by avoiding string comparisons

With large input one should see cut 15% for overall run time.

Signed-off-by: Sami Kerola <kerolasa@iki.fi>
This commit is contained in:
Sami Kerola 2013-08-31 00:30:28 +01:00
parent d70b08244f
commit 06ffa5ed04
5 changed files with 102 additions and 98 deletions

View file

@ -52,32 +52,4 @@ static const size_t MAXLEN = 1024;
* \brief Maximum number of different shared networks in dhcpd.conf file. */ * \brief Maximum number of different shared networks in dhcpd.conf file. */
static const unsigned int SHARED_NETWORKS = 8192; static const unsigned int SHARED_NETWORKS = 8192;
/*! \var prefixes[2][NUM_OF_PREFIX]
* \brief ISC lease file formats for IPv4 and IPv6.
*
* The .indent.pro in use will mess formatting of array below.
* Please do not commit less readable indentation. */
static const char *prefixes[2][NUM_OF_PREFIX] = {
[VERSION_4] = {
[PREFIX_LEASE] = "lease ",
[PREFIX_BINDING_STATE_FREE] = " binding state free",
[PREFIX_BINDING_STATE_ABANDONED] = " binding state abandoned",
[PREFIX_BINDING_STATE_EXPIRED] = " binding state expired",
[PREFIX_BINDING_STATE_RELEASED] = " binding state released",
[PREFIX_BINDING_STATE_ACTIVE] = " binding state active",
[PREFIX_BINDING_STATE_BACKUP] = " binding state backup",
[PREFIX_HARDWARE_ETHERNET] = " hardware ethernet"
},
[VERSION_6] = {
[PREFIX_LEASE] = " iaaddr ",
[PREFIX_BINDING_STATE_FREE] = " binding state free",
[PREFIX_BINDING_STATE_ABANDONED] = " binding state abandoned",
[PREFIX_BINDING_STATE_EXPIRED] = " binding state expired",
[PREFIX_BINDING_STATE_RELEASED] = " binding state released",
[PREFIX_BINDING_STATE_ACTIVE] = " binding state active",
[PREFIX_BINDING_STATE_BACKUP] = " binding state backup",
[PREFIX_HARDWARE_ETHERNET] = " hardware ethernet"
}
};
#endif /* DEFAULTS_H */ #endif /* DEFAULTS_H */

View file

@ -272,13 +272,6 @@ int main(int argc, char **argv)
* FIXME: This function should return void. */ * FIXME: This function should return void. */
int prepare_memory(void) int prepare_memory(void)
{ {
/* Fill in prefix length cache */
int i, j;
for (i = 0; i < 2; i++) {
for (j = 0; j < NUM_OF_PREFIX; j++) {
prefix_length[i][j] = strlen(prefixes[i][j]);
}
}
config.dhcp_version = VERSION_UNKNOWN; config.dhcp_version = VERSION_UNKNOWN;
RANGES = 64; RANGES = 64;
num_ranges = num_shared_networks = 0; num_ranges = num_shared_networks = 0;

View file

@ -210,8 +210,7 @@ void copy_ipaddr(union ipaddr_t *restrict dst,
const union ipaddr_t *restrict src); const union ipaddr_t *restrict src);
const char *ntop_ipaddr(const union ipaddr_t *ip); const char *ntop_ipaddr(const union ipaddr_t *ip);
double get_range_size(const struct range_t *r); double get_range_size(const struct range_t *r);
int xstrstr(const char *__restrict a, const char *__restrict b, int len) int xstrstr(const char *__restrict str)
__attribute__ ((nonnull(1, 2)))
# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
__attribute__ ((__hot__)) __attribute__ ((__hot__))
# endif # endif

View file

@ -101,62 +101,52 @@ int parse_leases(void)
ethernets = true; ethernets = true;
} }
const char **p = prefixes[config.dhcp_version];
int *l = prefix_length[config.dhcp_version];
/*! \def HAS_PREFIX(line, type)
* \brief A macro to match IPv4 and IPv6 lease lines.
*
* FIXME: This macro should have better name. The HAS_PREFIX sounds like
* some sort of prefix length test. */
#define HAS_PREFIX(line, type) xstrstr((line), p[type], l[type])
while (!feof(dhcpd_leases)) { while (!feof(dhcpd_leases)) {
if (!fgets(line, MAXLEN, dhcpd_leases) && ferror(dhcpd_leases)) { if (!fgets(line, MAXLEN, dhcpd_leases) && ferror(dhcpd_leases)) {
err(EXIT_FAILURE, "parse_leases: %s", err(EXIT_FAILURE, "parse_leases: %s",
config.dhcpdlease_file); config.dhcpdlease_file);
} }
switch(xstrstr(line)) {
/* It's a lease, save IP */ /* It's a lease, save IP */
if (HAS_PREFIX(line, PREFIX_LEASE)) { case PREFIX_LEASE:
nth_field(ipstring, line + l[PREFIX_LEASE]); nth_field(ipstring, line + (config.dhcp_version == VERSION_4 ? 6 : 9));
parse_ipaddr(ipstring, &addr); parse_ipaddr(ipstring, &addr);
continue; break;
} case PREFIX_BINDING_STATE_FREE:
if (HAS_PREFIX(line, PREFIX_BINDING_STATE_FREE) || case PREFIX_BINDING_STATE_ABANDONED:
HAS_PREFIX(line, PREFIX_BINDING_STATE_ABANDONED) || case PREFIX_BINDING_STATE_EXPIRED:
HAS_PREFIX(line, PREFIX_BINDING_STATE_EXPIRED) || case PREFIX_BINDING_STATE_RELEASED:
HAS_PREFIX(line, PREFIX_BINDING_STATE_RELEASED)) {
/* remove old entry, if exists */
if ((lease = find_lease(&addr)) != NULL) { if ((lease = find_lease(&addr)) != NULL) {
delete_lease(lease); delete_lease(lease);
} }
add_lease(&addr, FREE); add_lease(&addr, FREE);
continue; break;
} case PREFIX_BINDING_STATE_ACTIVE:
/* Copy IP to correct array */
if (HAS_PREFIX(line, PREFIX_BINDING_STATE_ACTIVE)) {
/* remove old entry, if exists */ /* remove old entry, if exists */
if ((lease = find_lease(&addr)) != NULL) { if ((lease = find_lease(&addr)) != NULL) {
delete_lease(lease); delete_lease(lease);
} }
add_lease(&addr, ACTIVE); add_lease(&addr, ACTIVE);
continue; break;
} case PREFIX_BINDING_STATE_BACKUP:
if (HAS_PREFIX(line, PREFIX_BINDING_STATE_BACKUP)) {
/* remove old entry, if exists */ /* remove old entry, if exists */
if ((lease = find_lease(&addr)) != NULL) { if ((lease = find_lease(&addr)) != NULL) {
delete_lease(lease); delete_lease(lease);
} }
add_lease(&addr, BACKUP); add_lease(&addr, BACKUP);
config.backups_found = true; config.backups_found = true;
continue; break;
} case PREFIX_HARDWARE_ETHERNET:
if (ethernets && (xstrstr(line, " hardware ethernet", 19))) { if (ethernets == false)
break;
nth_field(macstring, line + 20); nth_field(macstring, line + 20);
macstring[17] = '\0'; macstring[17] = '\0';
if ((lease = find_lease(&addr)) != NULL) { if ((lease = find_lease(&addr)) != NULL) {
lease->ethernet = xstrdup(macstring); lease->ethernet = xstrdup(macstring);
} }
break;
default:
/* do nothing */;
} }
} }
#undef HAS_PREFIX #undef HAS_PREFIX

View file

@ -148,53 +148,103 @@ double get_range_size(const struct range_t *r)
} }
} }
/*! \fn xstrstr(const char *restrict a, const char *restrict b, const int len) /*! \fn xstrstr(const char *restrict str)
* \brief Compare two strings. Similar to strcmp, but tuned to be * \brief Categorize dhcpd.leases line.
* quicker which is possible because input data is known to have certain
* structure.
* *
* \param a String which is been compared, e.g., a haystack. * \param str A line from dhcpd.conf
* \param b Constant string which is hoped to found, e.g., a needle. * \return prefix_t enum value
* \param len Stop point in characters when comparison must be ended. */
* \return Zero if strings differ, one if they are the same. */
int int
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
__attribute__ ((hot)) __attribute__ ((hot))
#endif #endif
xstrstr(const char *restrict a, const char *restrict b, const int len) xstrstr(const char *restrict str)
{ {
int i; size_t len;
/* Needed when dhcpd.conf has zero range definitions. */ /* Needed when dhcpd.conf has zero range definitions. */
if (config.dhcp_version == VERSION_UNKNOWN) { if (config.dhcp_version == VERSION_UNKNOWN) {
if (!strcmp(prefixes[VERSION_4][PREFIX_LEASE], a)) { if (strncmp("lease ", str, 6)) {
config.dhcp_version = VERSION_4; config.dhcp_version = VERSION_4;
return true; return PREFIX_LEASE;
} } else if (strncmp(" iaaddr ", str, 9)) {
if (!strcmp(prefixes[VERSION_6][PREFIX_LEASE], a)) {
config.dhcp_version = VERSION_6; config.dhcp_version = VERSION_6;
return true; return PREFIX_LEASE;
} }
return false; return NUM_OF_PREFIX;
} }
if ((config.dhcp_version == VERSION_4 && str[2] == 'b')
/* two spaces are very common in lease file, after them || (config.dhcp_version == VERSION_6 && str[4] == 'b')
* nearly everything differs */ || str[2] == 'h') {
if (likely(a[2] != b[2])) { len = strlen(str);
return false; } else {
len = 0;
} }
/* " binding state " == 16 chars, this will skip right if (15 < len && config.dhcp_version == VERSION_4) {
* to first differing line. */ switch (str[16]) {
if (17 < len && a[17] != b[17]) { case 'f':
return false; if (!strncmp(" binding state free;", str, 21))
} return PREFIX_BINDING_STATE_FREE;
/* looking good, double check the whole thing... */ break;
for (i = 0; a[i] != '\0' && b[i] != '\0'; i++) { case 'a':
if (a[i] != b[i]) { if (!strncmp(" binding state active;", str, 23))
return false; return PREFIX_BINDING_STATE_ACTIVE;
if (!strncmp(" binding state abandoned;", str, 25))
return PREFIX_BINDING_STATE_ABANDONED;
break;
case 'e':
if (!strncmp(" binding state expired;", str, 24))
return PREFIX_BINDING_STATE_EXPIRED;
break;
case 'r':
if (!strncmp(" binding state released;", str, 25))
return PREFIX_BINDING_STATE_RELEASED;
break;
case 'b':
if (!strncmp(" binding state backup;", str, 23))
return PREFIX_BINDING_STATE_BACKUP;
break;
case 'n':
if (!strncmp(" hardware ethernet", str, 19))
return PREFIX_HARDWARE_ETHERNET;
break;
}
} else if (17 < len /* && config.dhcp_version == VERSION_6 */ ) {
switch (str[18]) {
case 'f':
if (!strncmp(" binding state free;", str, 23))
return PREFIX_BINDING_STATE_FREE;
break;
case 'a':
if (!strncmp(" binding state active;", str, 25))
return PREFIX_BINDING_STATE_ACTIVE;
if (!strncmp(" binding state abandoned;", str, 27))
return PREFIX_BINDING_STATE_ABANDONED;
break;
case 'e':
if (!strncmp(" binding state expired;", str, 26))
return PREFIX_BINDING_STATE_EXPIRED;
break;
case 'r':
if (!strncmp(" binding state released;", str, 27))
return PREFIX_BINDING_STATE_RELEASED;
break;
case 'b':
if (!strncmp(" binding state backup;", str, 25))
return PREFIX_BINDING_STATE_BACKUP;
break;
case 'n':
if (!strncmp(" hardware ethernet", str, 19))
return PREFIX_HARDWARE_ETHERNET;
break;
} }
} }
return true; if (config.dhcp_version == VERSION_4 && !strncmp("lease ", str, 6)) {
return PREFIX_LEASE;
} else if (config.dhcp_version == VERSION_6
&& !strncmp(" iaaddr ", str, 9)) {
return PREFIX_LEASE;
}
return NUM_OF_PREFIX;
} }
/*! \brief Return a double floating point value. /*! \brief Return a double floating point value.