您的位置:

Gperf详解

一、Gperf简介

Gperf是一款代码生成工具,用于生成key-value对的散列表并以C或C++的形式输出。它使用了一种名为“字典树”(trie-tree)的数据结构,并在其基础上进行了优化和改进。采用gperf可以快速生成高效的哈希表。

相比于其他哈希算法,gperf的散列表具有更快的查找速度以及更低的冲突率。在处理大规模数据时,gperf具有优越的性能表现。同时,通过使用gperf的工具,我们可以生成一份清晰且易于阅读的C/C++代码,并且还能够自定义哈希函数。

二、Gperf工具结构

Gperf的工具结构非常简单,我们可以先手动创建一份包含key-value对的输入文件,然后使用gperf工具进行处理,生成源代码文件。

下面以一个简单的例子来展示gperf工具的使用:

%{
#include <stdio.h>
#include <stdlib.h>
%}

struct keyword {
    char *name;
    int type;
};

%%
if      { return IF; }
else    { return ELSE; }
for     { return FOR; }
while   { return WHILE; }
return  { return RETURN; }
// 前面定义了关键词,下面开始定义一些常量或者一些有特殊意义的符号
"+"       { return ADD; }
"-"       { return SUB; }
"*"       { return MUL; }
"/"       { return DIV; }
"="       { return ASSIGN; }
"("       { return LPARE; }
")"       { return RPARE; }
";"       { return SEMICOLON; }
":"       { return COLON; }
","       { return COMMA; }
.{Text}   { return ID; }
\n        { return 0; }
%%

int main(void) {
    yylex();
    return 0;
}

int yywrap(void) {
    return 1;
}

int yylex(void) {
    int c = getchar();
    if (c == EOF) {
        return 0;
    }
    if (c >= 'a' && c <= 'z') {
        char id[100];
        int i = 0;
        do {
            id[i++] = c;
            c = getchar();
        } while (c >= 'a' && c <= 'z');
        ungetc(c, stdin);
        id[i] = '\0';

        struct keyword *p = lookup(id);
        if (p) {
            printf("%d ", p->type);
        } else {
            printf("%d ", ID);
        }
    } else {
        printf("%c ", c);
    }
    return yylex();
}

char *strdup(const char *);

#define NHASH  9997
#define MULTIPLIER  31

struct keyword *lookup(char *s) {
    static struct keyword keywords[] = {
        {"if", IF},
        {"else", ELSE},
        {"for", FOR},
        {"while", WHILE},
        {"return", RETURN},
        {0, 0}
    };
    struct keyword *p;
    char *t;
    unsigned int h = 0;

    for (t = s; *t; t++) {
        h = MULTIPLIER * h + *t;
    }

    h %= NHASH;

    for (p = &keywords[h]; p->name; p++) {
        if (!strcmp(p->name, s)) {
            return p;
        }
    }

    p->name = strdup(s);
    p->type = ID;
    return p;
}

char *strdup(const char *s) {
    char *p;
    p = (char *) malloc(strlen(s) + 1);
    if (p != NULL) {
        strcpy(p, s);
    }
    return p;
}

在上述代码中,我们定义了一个简单的语法分析器,它可以对包含关键词、运算符和标识符等符号的输入文件进行分析,并按照一定规则输出结果。这里我们引入了gperf工具,使用下面的命令处理上述输入文件:

gperf -L ANSI-C -t -E --enum yykey -k "*" -N keyword input.gperf

根据上述命令生成的代码中,会包含一个叫做lookup()的函数,它返回输入值对应的数据结构。下面是生成的C代码:

struct keyword { char *name; int type; };

#define TOTAL_KEYWORDS 5
#define MIN_WORD_LENGTH 2
#define MAX_WORD_LENGTH 6
#define MIN_HASH_VALUE 2
#define MAX_HASH_VALUE 11
/* maximum key range = 10, duplicates = 0 */
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static unsigned int
hash (register const char *str, register unsigned int len)
{
  static unsigned short asso_values[] =
    {
      1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0
    };
  return len + asso_values[(unsigned char)str[0]];
}

struct keyword *
in_word_set (register const char *str, register unsigned int len)
{
  static struct keyword wordlist[] =
    {
      {"for",          1},
      {"else",         2},
      {"while",        3},
      {"if",           4},
      {"return",       5},
      {""}, {""},
      {"",0}
    };

  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
    {
      register int key = hash (str, len);

      if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE)
        {
          register const char *s = wordlist[key].name;

          if (*str == *s && !strcmp (str + 1, s + 1))
            return &wordlist[key];
        }
    }
  return 0;
}

这段代码实现了一个哈希表,并提供了一个快速查找的函数in_word_set()。我们可以使用这个函数来查找输入的key是否在哈希表中,并返回对应的value值。

三、gperf性能分析

3.1 gperftools内存分析

gperftools是一套开源的性能分析工具集,其中包括了一些常用的工具,例如CPU性能分析、内存分析、堆栈跟踪等等。其中,内存分析工具heapcheck可以用于检查程序在运行时的内存分配情况,减少内存泄漏和内存溢出等问题。下面我们以一个简单的例子来演示heapcheck的使用方法:

#include <stdio.h>
#include <stdlib.h>
#include <gperftools/heap-checker.h>

void * LeakyFunction(int leakCount) {
  void **memory = (void **) malloc(sizeof(void *) * leakCount);
  for (int i = 0; i < leakCount; i++) {
    memory[i] = (void *) malloc(1024 * 1024);
  }
  return memory;
}

int main(int argc, char *argv[]) {
  int leakCount = 100;
  HeapLeakChecker checker("basic");
  void *memory = LeakyFunction(leakCount);
  checker.ExpectLeaks(leakCount);
  free(memory);
  checker.NoLeaks();
  return 0;
}

通过将gperftools的头文件包含在代码中,并在主函数中添加HeapLeakChecker的实例,我们可以对这个程序进行内存分配的检查。上述例子中,我们定义了一个函数LeakyFunction(),它会分配100个1MB大小的内存块。通过HeapLeakChecker的ExpectLeaks()可以检查我们期望的内存泄漏数目,并通过NoLeaks()方法来检查程序是否发生了内存泄漏。

3.2 gperformance性能分析

gperftools还包含另一个称为gperformance的性能分析工具,它是一款延迟分析工具,可以对函数调用的延迟进行采样和统计。使用gperformance需要先对代码进行编译,然后在启动时加入--gperf-start、--gperf-stop等参数。下面我们以一个简单的例子来演示gperformance的使用方法:

#include <gperftools/profiler.h>
#include <stdlib.h>
#include <unistd.h>

void InnerFunction()
{
  usleep(100);
}

void OutterFunction()
{
  usleep(200);
  InnerFunction();
}

int main(int argc, char **argv)
{
  ProfilerStart("/tmp/prof-example.profile");
  for (int i = 0; i < 100000; i++) {
    OutterFunction();
  }
  ProfilerStop();
  return 0;
}

在上述代码中,我们在开始时调用了ProfilerStart()函数,并指定了统计结果输出到的文件名,然后循环调用了100000次OutterFunction(),每次调用时OutterFunction都会调用InnerFunction()。程序运行结束后,gperformance会自动生成一个性能报告,并在报告中展示各个函数的耗时占比。

四、Gperf特点及应用场景

Gperf具有以下四个特点:

1、性能优秀:使用gperf生成的哈希表具有更快的查找速度以及更低的冲突率。

2、代码简洁:gperf可以生成一份清晰且易于阅读的C/C++代码,并且还能够自定义哈希函数。

3、易于使用:使用gperf可以快速生成高效的哈希表。

4、可靠性高:gperf已经经过了广泛的测试和验证,在处理大规模数据时具有优越的性能表现。

gperf主要应用于大规模数据的快速查找、编译器的语法分析、关键词查找等场景。例如,我们可以在编译器中使用gperf来生成一份自定义的语法分析表,从而提高编译器的语法检查效率。