一、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具有以下四个特点:
- 性能优秀:使用gperf生成的哈希表具有更快的查找速度以及更低的冲突率。
- 代码简洁:gperf可以生成一份清晰且易于阅读的C/C++代码,并且还能够自定义哈希函数。
- 易于使用:使用gperf可以快速生成高效的哈希表。
- 可靠性高:gperf已经经过了广泛的测试和验证,在处理大规模数据时具有优越的性能表现。 gperf主要应用于大规模数据的快速查找、编译器的语法分析、关键词查找等场景。例如,我们可以在编译器中使用gperf来生成一份自定义的语法分析表,从而提高编译器的语法检查效率。