gperf--GNU完美哈希函数生成器用户手册(翻译)
START-INFO-DIR-ENTRY * Gperf: (gperf). Perfect Hash Function Generator. END-INFO-DIR-ENTRY 介绍 ************ 'gperf'是一个用C++编写的完美的hash函数生成器.它通过一个完美的hash函数F转 换一个含有N元素的用户特定关键字集合到集合W. F唯一映射关键字到W的0..K范围,其 中K>=N如果K=N那么F就是最小化的完美hash函数.'gperf'生成一个0..K元素的静态查 找表和一对C函数.这些函数决定一个给定的字符串S是否在集合W中,通过只多一次的查找. 'gperf'普遍用于为多个商业编译器,研究型编译器,语言处理工具的词法分析器生成一 个关键字识别器.这些编译器包括GNU C, GNU C++, GNU Pascal, GNU Modula 3, 和GNU indent.完整的'gperf'C++源代码可以通过匿名ftp`ics.uci.edu' 和 `ftp. santafe.edu'得到.'gperf'已经随GNU libg++一起发布好几年了.高度相似,功能等价 的K&R C版本'gperf'已经存档于comp.sources.unix, volume 20.最后,一篇有关 'gperf'设计和实现的十分详细资料可以在第二次USENIX C++会议录中找到. `gperf'静态查找结构和GNU 'gperf' **************************************** 一个"静态查找结构"是一个有一些基本操作的抽象数据类型,这些操作如:初始化,插入, 和检索概念上,所有插入都发生在检索之前.在实际中,'gperf'生成一个'static'数组,它 是包含关键字和用户指定相关属性的查找集.这样,基本上没有执行所有插入时间花费(预先放 置在数组中).这是一个有用的数据结构用于表示"静态查找集".静态查找集经常能在软件系统 应用中发现.通常静态查找集包含编译器关键字,汇编指令代码,和内置shell命令解释.查找集 成员,称为关键字,其只被插入结构中一次,通常是在程序初始化时,并在实时运行时不作修改. 有许多静态查找结构实现方式,如,数组,链表,二叉查找树,数字查找元组,和哈希表.不同的 方法提供了在空间利用和查找效率上的权衡.例如,一个N个元素的已排序数组是空间有效,虽然 查找花费的平均时间复杂度是与log N成例.相反,哈希表实现通常能在常数时间定位一个表中 的记录,但通常需要额外的内存开销和呈现出在最坏情况下的低性能. "最小化完美哈希函数"为一种特殊的静态查找集提供一个理想的解决方案.一个最小完美哈 希函数被定义具有以下两个性质 * 它允许识别一个静态查找集中的关键字最多只要在哈希表中进一"一次"探测.这个 性质代表"完美"性质 * 实际内存分配用于保存关键字精确地刚好足够保存关键集,并不会"更大".这是" 最小化"性质. 对于许多应用生成"完美的"哈希函数比生成"最小完美"哈希函数容易.而且,在实际中非最 小化完美哈希函数往往会比"最小完美"哈希函数执行更快.这种现象的发生是因为查找一个稀疏 关键字表增加了定位到一个"NULL"记录的概率,因此减少字符串的比较.'gperf'默认方式是关 键字符集生成"近似最小化"完美哈希函数.然而,'gpref'提供了许多选项许可用户控制最小化 和完美性的程度 静态查找集通常在时间上呈现相对稳定.例如,Ada's 63保留字(就是静态查找集)已保持常数 将近十年.因此,如果其后每次都要重复实现,那么花费努力去一次性商定构造一个最佳查找结构是 十分值得的.'gperf'移除手动实现一个权衡空间和时间的查找集的苦差事.已经证明它是一个十分 实用的工具用于各种程序设计工程.'gpref'的输出普遍被用于多个商业编译器,研究型编译器,包括 GNU C, GNU C++, GNU Pascal, GNU Modula 3.最后两个编译器还没有成为官方GNU发行.每个 编译器利用'gperf'去自动生成静态查找结构去有效地标识它们的保留字. `gperf' GNU'gperf'的高级描述 ************************************* 完美哈希函数生成器'gperf'由一个关键文件("keyfile"或默许的标准输入)读入一个关键字集 合它尝试得到一个完美哈希函数通过最多一次在查找表中探测去识别"静态关键字"成员.如果'gperf' 成功地产生一个这样的函数,那么它会产生一对C源代码例程去执行散列和表的查找识别.所有生成的C代 码定向为标准输出.接下来的命令行选项描述允许你去修改'gperf'的输入输出格式 默认地,'gperf'尝试通过减少强调有效的空间利用来生成更具时间效率的代码.然而,几个选项存在 允许以执行时间换取空间存储.特另地,扩展生成表格大小产生一个稀疏查找结构,一般地产生更快的查找. 相反,你可以指示'gperf'去利用C的'switch'语句安排最小数据空间存储大小.此外,使用C switch可以 实际上在某种程度上提高关键字的检索时间.当然这也与你的C编译器有关 一般地,'gperf'分配值给字母用于散列直到结定的集合中的每一关键字对应一个唯一的值.一个十分 有用的启发式是越大的哈希值范围,'gperf'越容易找到和生成一个完美的哈希函数.实验表明这是'gperf' 最重要的一点. 'gperf'输入格式 ======================= 你可以控制输入文件格式通过变化特定的命令行参数,特别地是'-t'选项.输入的形式类似于GNU实用程序 'flex'和'bison'(或Unix的'lex'和'yacc')这里是一般格式轮廓: declarations %% keywords %% functions 不用于'flex'或'bison','gperf'输入的所有部分是可选的.接下每个小节对应描述输入格式的每一部分. 'struct'声明和C代码包含 ------------------------------------------ 关键输入文件可选地包括一部分用于包含任意C声明和定义,及用户定义'struct'.如果'-t'选项有效,你必 须为输入文件提供一个C'struct'作为声明部分的最后成分.这个结构体第一个域必须是一个'char *' 类型, 名为'name'的标识符,虽然可以通过接下来介绍的'-K'选项修改域名. 这里有一个简单的例子,用一年的月份和它们的属性作为输入: struct months { char *name; int number; int days; int leap_days; }; %% january, 1, 31, 31 february, 2, 28, 29 march, 3, 31, 31 april, 4, 30, 30 may, 5, 31, 31 june, 6, 30, 30 july, 7, 31, 31 august, 8, 31, 31 september, 9, 30, 30 october, 10, 31, 31 november, 11, 30, 30 december, 12, 31, 31 分隔'struct'声明和关键字列表用一对连续的百分号,'%%',它出现在最左边的第一列,就如 UNIX的实用程序'lex' 使用语法类似于GNU实用程序'flex'和'bison',它可能直接包含C源代码和注释,并直接输出 到输出文件中它由封闭的左对齐的'%{','%}'对.这里一个输入片断是基于前面一个例子,用于说明 这种特性: %{ #include <assert.h> /* This section of code is inserted directly into the output. */ int return_month_days (struct months *months, int is_leap_year); %} struct months { char *name; int number; int days; int leap_days; }; %% january, 1, 31, 31 february, 2, 28, 29 march, 3, 31, 31 ... 可能会完全忽略声明部分.在这种情况下关键文件开始于直接的第一行关键字定义,例如: january, 1, 31, 31 february, 2, 28, 29 march, 3, 31, 31 april, 4, 30, 30 ... 关键字记录的格式 -------------------------- 关键文件的第二部分包含一行行的关键字和任意你提供的关联属性.每行第一列用'#'开始 表示为一行注释. 非注释的行最开始域通过是关键字.它以一个简单的名字给出,例如,没有用字符串引号标识, 且可以被左对齐.在上下文中,一个"域"被认为是扩展的,而不包含以空格,逗号或换行开始的. 这里有一个简单的例子列出了部分的C保留字: # These are a few C reserved words, see the c.`gperf' file # for a complete list of ANSI C reserved words. unsigned sizeof switch signed if default for while return 注意,不同于'flex'或'bison',如果声明部分是空,那么'%%'标识可以省略. 在前导关键字后的额外域是可选的.域应该以逗号分隔,且以行尾作为终止.这些域的含义完全看你定义; 他们被用于初始化元素用户定义'struct'(在声明中定义的).如果'-t'选项没有选那么这些域将会简单的被 忽略.所有之前的例子,除了最后一个都包含关键字属性. 包含附加C函数 -------------------------------- 第三可选部分也与'flex'和'bison'中的定义很接近.所有在这部分的内容,开始于最后一个'%%' 至到输入文件尾.这些内容都会直接拷贝到输出文件.当然,保证包含的代码是有效的C代码是你的责任. 'gperf'生成的C代码输出格式 =============================================== 几个选项控制如何生成C代码在标准输出中.两个C函数将被生成.它们名为'hash','in_word_set', 虽然你可修改'in_word_set的名字通过一个命令行选项.所有函数要求两个参数,一个字符串,'char *'STR 和一个长度参数,'int' LEN.他们默认函数原形如下: static int hash (char *str, int len); int in_word_set (char *str, int len); 默认地,生成的'hash'函数返回一个整形值通过把长度加上几个用户指定的STR关键位置 来作为一个"关联值"表保存在一个静态数组的索引.关联值表被构造在'gperf'内部,并且后面 输出为名为HASH_TABLE的静态局部数组;它的含义和性质将在后面描述.注意实现::.有关关键位置 通过运行'gperf'的'-k'选项来指定,这会在后面'选项'一节描述.注意选项::. 两个选项,'-g'(假设你正在用GNUC编译和它的相关'inline'特性)和'-a'(假定ANSI C风格原形, 将会改变生成的'hash'和'in_word_set'例程内容.然而,函数'in_word_set'可以被修改更多,来 响应你的选项设置.影响'in_word_set'结构的选项有: `-t' 使用用户自定义'struct' -S 全部使用SWITCH语句 生成一个或多个C'switch'语句而不是使用一个大的(可能稀疏的)静态数组.虽然 这种方法的精确时间空间节省很大程序上依赖于你的C编译器优化度,这种方法经常会 导致更小和更快的代码. 如果'-t'和'-S'选项被省去,默认行为是生成一个'char*'的数组,包含关键字,和额外用于填充数 组的'NULL'字符串通过测试不同输入和输出选项,和对C产生结果计时,你可以决定为不同关键字集特 性选择最好的选项. 'gperf'实用选项 ****************************** 这里有许多'gperf'选项.它们的加入使用程序在真正应用时更为方便."On-line"帮助很容易 得到过通'-h'选项.这里是完全的选项列表. 影响输入文件解释的选项 ==================================================== -e 关键字分隔符列表 允许用户提供一个字符串包含分隔符用于分隔关键字和它们的属性.默放的是",/n" 这个选项本质是如果你想用嵌入逗号或换行的关键字.那么一个有用的技巧是使用 -e"TAB" 这里TAB是tab字符常量 '-t' 允许你包含一个'struct'类型声明用于生成代码.任何在一对连续%%之前的内容 被认为类型声明的一部分.随后的是关键字和额外域,一组域一行.一组生成完美哈希表 和函数的例子,有关Ada,C,和G++,Pascal,和Modula 2和3的保留字随着程序一起发布. 选项来指定生成输出代码的语言 =================================================== '-L生成语言名' 通过在语言指定的选项参数中来指示'gperf'去生成代码.当前支持的语言包含: `KR-C' 旧式K&R C.这个语言可以被旧式C编译器和ANSIC编译器识别.但ANSI C 因为缺少'const'记号,ANSI C编译器可能会有标记警告(或甚至是错误). `C' 一般C.这种语方可以被ANSI C编译器,对于旧式C编译器识别, 如果不能编译器不能识别关键字,你可以通过"#define const' 为空来支持 `ANSI-C' ANSI C.这种语言可以由ANSI C和C++编译器识别 `C++' C++.可以由C++编译器识别 默认是C. `-a' 这个选项是提供对之前的'gperf'提供兼容,它并不做任何工作. `-g' 这个选项是提供对之前的'gperf'提供兼容,它并不做任何工作. 用于调整代码输出的详细选项 ================================================== '-K 关键字名' 这个选项只用当'-t'选项有效时有效.默认地,程序假定结构本中有关关键字的变量名为'name'. 这个选项允许一个任意选择关键字名的,当然这也只有同时在你提供的'struct'中第一个域指定. '-H 哈希函数名' 允许你去指定生成的哈希函数名.默认名是'hash'.这个选项允许在同一个文件 中使用两个哈希表. '-N 查找函数名' 允许你去指定生成查找函数的名.默认名是'in_word_set'.这个选项允许完全 自动化生成完美函数函数,特别是当多个生成的哈希函数在同一个应用中使用. '-Z 类名' 这个选项只有当选项'-L C++'设定时才有效.它允许你去指定生成的C++类名.默认名是 'perfect_Hash' '-7' 这个选项指定所有传给生成的哈希函数及查找函数的字符串的每个字符都是7位字符 (字符的范围是0..127).(注意在ANSI C中函数'isalnum'和'isgraph'并不保证 参数是在这个范围内.只有明显的测试如'c >= 'A' && c <= 'Z' 保证这一点)在 早期的'gperf'版本中这是默认的选项.注意现在的默认选项是假定为8位. '-c' 产生的C代码都用'strncmp'函数去执行字符串比较.而默认的是使用'strcmp'. '-C' 保证查找表中的所有内容不改变,例如,"只读".在这一点上许多编译器能够保证产生 更有效的代码,通过把表放入只读内存中. '-E' 定义常数值时采用在查找函数中使用一个局部的枚举量,而不是使用#define. 这也意味着不同的查找函数可以同时存在于同一个文件中.感谢James Clark '<jjc@ai.mit.edu>' '-I' 在代码的开始包含必要的系统头文件,'<string,h>'.这并不是默认的.为了能够 编译代码用户必须亲自包含这个头文件 '-G' 生成关键字静态表作为一个静态全局变量,而不是把它封装在查找函数整中 (封装在查找函数中是默认的行为). '-W 哈希表数组名' 允许你去指定生成的包含哈希表的数组名.默认名是'wordlist'.这个选项 允许用户在同一个文件中使用两个哈希表,即使当'-G'选项设置时 '-S 完全SWITCH语句' 导致生成的C代码使用一个'switch'语句来替代一个数组查找表.对于同一个输入文件 这能够产生更好的时间空间效率.选项的参数决定有多少个switch语句生成.1代表用一个 'switch'语句包含所有的元素.而2代表生成两个表,其中每个表用一个'switch'语句,包含了 1/2的元素.如此类推.这是有效的,因为很多C编译器不能够正确生成许多大的'switch'语句 这个选项的灵感来源于Keith Bostic的原著C程序设计 '-T' 阻止类型生明转换到输出文件中.当类型已经在某处定义时使用这个选项 '-p' 这个选项是提供与之前版本'gperf'的兼容,它并不完成任何事情. 用于选择'gperf'算法的选项 ======================================================= '-k KEYS' 允许选项字符关键位置用于关键字的哈希函数.允许选择的范围是1-126(包含1,126)之间.这些位置 以逗号分隔,例如'-k 9,4,13,14';可以使用区间,例如,'-k 2-7'所有位置可以以任何顺序出现. 此外,元符号'*'导致生成的哈希函数对于每一个键,把所有的字符的位置考虑上,而'$'指示使用最后 "一个字符"作为一个键(顺便说明的是这是唯一的方式去使用一个大于126的字符位置) 作为例子,选项 `-k 1,2,4,6-10,'$'' 生成一个哈希函数的产生每一个键考虑 位置1,2,4,6,7,8,9,10,及最后一个字符(显然它可以与任何键不同).键的长度 小于指示的关键位置是合适的,因为选择关键位置超过其长度在哈希函数中会简单的不引用 '-l' 在尝试字符串比较时比较键的长度.这样可能会减少在查找中的字符匹配量,因为 键的长度不同那么就永远不会使用'strcmp'进行比较.然而,如果查找表很大(这 意味着switch选项'-S'没有设置),使用'-l'可能会导致生成的C代码大小增长.因为 记录长度的表包含的元素与在查找表中的记录个数一样多. '-D' 处理关键字当关键位置集散列时有重复值.重复值的发生有以下两个原因: * 因为'gperf' 不能回溯,这样在没有找到唯一映射第一个字它就不可能处 理你输入的所用关键字.然而,经常会有很小部分的重复值发生,且大多数需要 在表中进行一次探测. * 有时一个集合的关键字可以有相同的名字,但是有不同的相关属性.通过-D选项 'gperf'把所有这些键看成一个等价类的一部分并保证一个完美哈希函数,当遇到 相同键值时就进行多次比较.直到由你去修改生成的C代码来消除二义性.然而,'gperf' 帮助你去组织这些输出 选项'-D'是极其有用的,对于那些十分大或高冗余的关键字集,例如汇编指令码集. 使用这个选项通常意味着生成的哈希函数不再是完美的.另一方面,它允许'gperf' 工作在这些关键集中,否则不能处理. '-f 迭代数量' "快速"生成的完美哈希函数.这可以减少'gperf'的运行时间,通过节省对生成表大小的最小化操作时间达到. 迭代次数代表当遇到一个碰撞时的迭代次数.'0'代表迭代次数为关键字的数量.当遇到一个大的关键字集时这 个选项连同选项'-D' 或/和 '-S'可能非常有用 '-i 初始值' 为关键数组值提供一个初始化值,默认是0.增加实始值可能导致最后的表的大小增长,和更多的时间查找. 注意当'-S'选项使用时,这个选项没有特别的用于.同样当'-r'选项使用时,'-i'选项也会被忽略. '-j 跳转值' 影响"跳转值",例如,当碰撞时要前进多远才能到达相关的字符.跳转值是一个奇数值,默认是5 如果跳转值是0,'gperf'跳转为一随机数. '-n' 指示生成器当计算它的哈希值时不包含关键字的长度.在生成查找表时这将可以 节省许多汇编指令 '-o' 通过关键字重排关键集,以至经常发生的关键位置出现在最开始.以一种重排会随后进行, 以至那此"值已经确定的"键放到键表的最前面.这样可以减少生成的一个完美哈希函数的时间要求 ,同时也产生出更多的最小完美哈希函数.这样的结果是重排使得当查找时遇到碰撞时减少查找时间 另一方面关键字集是非常大时,使用'-o'可能会增加'gperf'的执行时间,因为碰撞将开始得最早且 在接下来的处理中一直延续. 更多的请见Cichelli的the January 1980 Communications of the ACM论文 `-r' '-r' 利用随机值去初始化关联的值表.相对于用确定的值进行初始化(开始对所有关联值初始化为0) 这经常会更快地生成结果.此外,使用随机化选项通常会增加表的大小.如果对于一个确定的关键集 有困难,尝试使用'-r'或'-D' '-s SIZE-MULTIPLE' 影响生成哈希表的大小.这个选项的数字参数指最大关联值的范围应该是"多少倍大于或小于"键的数量 如果SIZE-MULTIPLE是一负数则最大的关联值的计算方式是用总的键数除于它.例如,值为3代表"允许最 大最大关联值为3倍于输入键的数目" 相反地,值为-3意味着"允许的最大关联值是输入键数目的1/3.负值对于限制hash表生成是十分有用的, 虽然这样会增加重复的哈希值 如果`generate switch'选项'-S'没有效,那么最大关联值影响着静态数组表的大小, 且一个更大的表应该减少不成功查找的时间 默认值是1,这样默认最大关联值就是关键字的个数(为了更有效,最大关联值通常是2的幂) 实际表的大小可以改变,因为这个技术本质上是一种启发式.特别地,设置该值过高会导致 'gperf'的运行时间变慢,因为它必须在一个非常大的范围值中查找.明智地使用'-f'选项 能够减少这个总开销. 信息输出 ================== '-h' 打印一个简短的有关每个程序选项的摘要.当程序异常时会中断. '-v' 打印当前版本号 '-d' 使能调试选项.这会产生详细的"标准错误"诊断当'gperf'执行时.这对于操纵程序和决定是否一个给定的选项 能够加速找到一个解.一些有用的信息会在程序结束时抛弃当'-d'选项有效时 |