GFlags使用总结

Github  
官方文档
Gflags简明教程 http://dreamrunner.org/blog/2014/03/09/gflags-jian-ming-shi-yong/
GFlags使用文档 http://www.yeolar.com/note/2014/12/14/gflags/

简介

GFlags是Google开源的一套命令行参数处理的开源库,包括C++的版本和python 版本。和 getopt() 之类的库不同,flag的定义可以散布在各个源码中,而不用放在一起。一个源码文件可以定义一些它自己的flag,链接了该文件的应用都能使用这些flag。这样就能非常方便地复用代码。如果不同的文件定义了相同的flag,链接时会报错。

定义Flags

定义一个flag是简单的:只需要使用你想用的类型的相应的宏就可以。
使用flags需要包含头文件 :

#include <gflags/gflags.h>

example:

// foo.cc
#include <gflags/glags.h>

DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german",
              "comma-separated list of languages to offer in the 'lang' menu");

示例代码分别定义了一个bool和一个string类型的参数,该宏的三个参数含义分别为命令行参数名,参数默认值,以及参数的帮助信息。gflag不支持列表,用户通过灵活借助string参数实现,比如上述的languages参数,可以类型为string,但可看作是以逗号分割的参数列表。
支持的类型:

  • DEFINE_bool: boolean
  • DEFINE_int32: 32-bit integer
  • DEFINE_int64: 64-bit integer
  • DEFINE_uint64: unsigned 64-bit integer
  • DEFINE_double: double
  • DEFINE_string: C++ string

DEFINE宏包含三个参数:flag名、默认值、描述方法的帮助。帮助会在执行 --help flag时显示。
可以在任何源文件中定义flag,但是每个只能定义一次。如果需要在多处使用,那么在一个文件中 DEFINE ,在其他文件中 DECLARE 。比较好的方法是在 .cc 文件中 DEFINE ,在 .h 文件中 DECLARE ,这样包含头文件即可使用flag了。

使用flag访问参数

当参数被定义后,通过FLAGS_name就可访问到对应的参数,定义的flag可以像正常的变量一样使用,只需在前面加上 FLAGS_前缀。如前面例子中的定义了 FLAGS_big_menu 和 FLAGS_languages两个变量。可以像一般变量一样读写:

if (FLAGS_consider_made_up_languages)
  FLAGS_languages += ",klingon";   // implied by --consider_made_up_languages
if (FLAGS_languages.find("finnish") != string::npos)
  HandleFinnish();

以上的访问方式,仅在参数定义和访问在同一个文件(或是通过头文件包含)时,FLAGS_name才能访问到参数,如果要访问其他文件里定义的参数,则需要使用DECLARE_type。比如在foo.cc中DEFINE_string(color, “red”, “the color you want to use”); 这时如果你需要在foo_test.cc中使用color这个参数,你需要加入DECLARE_string(color, “red”, “the color you want to use”);

参数检查

定义参数后,可以给参数注册一个检查函数(validator),当从命令行指定参数或通过SetCommandLineOption()指定参数时,检查函数就会被调用,两个参数分别为命令行参数名,以及设置的参数值

static bool ValidatePort(const char* flagname, int32 value) { 
   if (value > 0 && value < 32768)   // value is ok 
     return true; 
   printf("Invalid value for --%s: %d\n", flagname, (int)value); 
   return false; 
} 
DEFINE_int32(port, 0, "What port to listen on"); 
static const bool port_dummy = RegisterFlagValidator(&FLAGS_port, &ValidatePort)

建议在定义参数后,立即注册检查函数。RegisterFlagValidator()在检查函数注册成功时返回true;如果参数已经注册了检查函数,或者检查函数类型不匹配,返回false。

初始化所有参数

在引用程序的main()里通过 google::ParseCommandLineFlags(&argc, &argv, true); 即完成对gflags参数的初始,其中第三个参数为remove_flag,如果为true,gflags会移除parse过的参数,否则gflags就会保留这些参数,但可能会对参数顺序进行调整。 比如 “/bin/foo” “arg1” “-q” “arg2” 会被调整为 “/bin/foo”, “-q”, “arg1”, “arg2”,这样更好理解。

#include <iostream>
#include <gflags/gflags.h>
 
DEFINE_bool(isvip, false, "If Is VIP");
DEFINE_string(ip, "127.0.0.1", "connect ip");
DECLARE_int32(port);
DEFINE_int32(port, 80, "listen port");
 
int main(int argc, char** argv)
{
  google::ParseCommandLineFlags(&argc, &argv, true); //初始化所有参数
  std::cout<<"ip:"<<FLAGS_ip<<std::endl;
  std::cout<<"port:"<<FLAGS_port<<std::endl;
  if (FLAGS_isvip)
  {
      std::cout<<"isvip:"<<FLAGS_isvip<<std::endl;
  }
  google::ShutDownCommandLineFlags();
  return 0;
}

链接时使用 -gflags ,运行使用 ./gflags -ip=“211.152.52.106” -port=8080 -isvip=true ,但是很遗憾,使用 valgrind 检测有内存泄漏。

输出结果如下:

ip:211.152.52.106
port:8080
isvip:1

在命令行指定参数

比如要在命令行指定languages参数的值,可通过如下4种方式,int32, int64等类型与string类似。

app_containing_foo --languages=“chinese,japanese,korean”
app_containing_foo -languages=“chinese,japanese,korean”
app_containing_foo --languages “chinese,japanese,korean”
app_containing_foo -languages “chinese,japanese,korean”

对于bool类型,则可通过如下几种方式指定参数

app_containing_foo --big_menu
app_containing_foo --nobig_menu
app_containing_foo --big_menu=true
app_containing_foo --big_menu=false

特殊参数

–help 打印定义过的所有参数的帮助信息
–version 打印版本信息 通过google::SetVersionString()指定
–nodefok 但命令行中出现没有定义的参数时,并不退出(error-exit)
–fromenv 从环境变量读取参数值 --fromenv=foo,bar表明要从环境变量读取foo,bar两个参数的值。通过export FLAGS_foo=xxx; export FLAGS_bar=yyy 程序就可读到foo,bar的值分别为xxx,yyy。
–tryfromenv 与–fromenv类似,当参数的没有在环境变量定义时,不退出(fatal-exit)
–flagfile 从文件读取参数值,–flagfile=my.conf表明要从my.conf文件读取参数的值。在配置文件中指定参数值与在命令行方式类似,另外在flagfile里可进一步通过–flagfile来包含其他的文件。

Caffe实例解析

以 tools/caffe.cpp 为例分析gflags使用方法
在tools/caffe.cpp中首先包含了gflags的头文件

#include <gflags/gflags.h>

命令行参数传递

接下来定义了一些需要从命令行传递参数的变量

DEFINE_string(gpu, "",
    "Optional; run in GPU mode on given device IDs separated by ','."
    "Use '-gpu all' to run on all available GPUs. The effective training "
    "batch size is multiplied by the number of devices.");
DEFINE_string(solver, "",
    "The solver definition protocol buffer text file.");
DEFINE_string(model, "",
    "The model definition protocol buffer text file.");
DEFINE_string(phase, "",
    "Optional; network phase (TRAIN or TEST). Only used for 'time'.");
DEFINE_int32(level, 0,
    "Optional; network level.");
......

在main函数中调用的GlobalInit()函数中调用了ParseCommandLineFlags()


// Google flags.
::gflags::ParseCommandLineFlags(pargc, pargv, true);

这个函数的作用就是解析命令行参数。
pargc,pargv为命令行传递的参数个数和参数表,第三个参数作用为:

作用
true函数处理完成后,argv中只保留argv[0],argc会被设置为1
falseargv和argc会被保留,但是注意函数会调整argv中的顺序。

执行完这参数后,就可以用FLAGS_xxx访问变量了。
例如caffe.cpp中, FLAGS_solve表示命令行参数传递的模型文件,FLAGS_weights表示令行参数传递的权值文件

// Train / Finetune a model.
int train() {
  CHECK_GT(FLAGS_solver.size(), 0) << "Need a solver definition to train.";
  CHECK(!FLAGS_snapshot.size() || !FLAGS_weights.size())
      << "Give a snapshot to resume training or weights to finetune "
      "but not both.";

帮助信息

gflags::SetUsageMessage函数用于设置命令行帮助信息。

// Usage message.
  gflags::SetUsageMessage("command line brew\n"
      "usage: caffe <command> <args>\n\n"
      "commands:\n"
      "  train           train or finetune a model\n"
      "  test            score a model\n"
      "  device_query    show GPU diagnostic information\n"
      "  time            benchmark model execution time");

设置帮助信息后,当参数错误或加 -help选项是可以打印帮助信息

 ./build/tools/caffe
caffe: command line brew
usage: caffe <command> <args>
 
commands:
  train           train or finetune a model
  test            score a model
  device_query    show GPU diagnostic information
  time            benchmark model execution time
...

版本信息

gflags::SetVersionString用于设置版本信息

// Set version
  gflags::SetVersionString(AS_STRING(CAFFE_VERSION));

这里的CAFFE_VERSION是在Makefile中定义的
当命令行参数加 -version 时会打印版本信息

./build/tools/caffe -version

caffe version 1.0.0-rc3

demo

//demo_gflags.cc
#include "gflags/gflags.h"
DEFINE_string(server_ip, "127.0.0.1", "server ip");
DEFINE_int32(server_port, 8080, "server port");
// demo.cc
#include <iostream>
#include "gflags/gflags.h"

DEFINE_bool(test_bool, false, "test bool value");
DEFINE_int32(test_int32, 666, "test int value");

DECLARE_string(server_ip);
DECLARE_int32(server_port);

int main(int argc, char** argv) {
  google::ParseCommandLineFlags(&argc, &argv, true);

  // for (int i = 0; i < argc; i++) {
  //   std::cout << argv[i] << std::endl;
  // }

  if (FLAGS_test_bool) {
    std::cout << "test_bool is true." << std::endl;
    std::cout << "test_int32: " << FLAGS_test_int32 << std::endl;
  }

  std::cout << "server ip: " << FLAGS_server_ip
            << " , server port: " << FLAGS_server_port << std::endl;

  return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
project(gflag)

find_package(gflags REQUIRED)

add_executable(${PROJECT_NAME}_demo demo.cc demo_gflags.cc)
target_link_libraries(${PROJECT_NAME}_demo ${GFLAGS_LIBRARIES})
//demo.gflags
-server_ip="192.168.0.123"
-server_port=8899
编译
$ cd gflag
$ mkdir build
$ cmake ..
$ make
执行
# 不加命令行
$ ./gflag_demo
# 加命令行变量控制 `-test_bool=true` test_bool为程序开头定义的标志
$ ./gflag_demo -test_bool=true -test_int32=666
# 加命令行文件输入 `-flagfile=filename` 注意路径, - 和 -- 一样
$ ./gflag_demo -flagfile=../demo.gflags ##文件绝对路径
# 其他
# -fromenv=value 从环境中读取value
# --tryfromenv=value 环境中是否未定义不是致命错误

gflags使用规范

任何好用的工具如果使用不当都会带来不好的后果,gflags也是一样。我遇到过一些gflags的“坑”,还从领导和同事那里获得一些好的想法,整理成7条gflags使用规范。有意识的遵循这些规范,对项目的开发维护和自身的技术成长都将有很大的益处。

  • 规范1:bool类型的gflags默认值设置成false,防止误启用新功能。
    新的功能上线一定要经过代码审查、测试和验证流程,默认为true的gflags风险太大。
  • 规范2:应定时清理旧的gflags。
    随着时间的流逝,代码里的gflags会越来越多,当你的工程代码里包含成百上千行的gflags时,阅读和维护代码的体验简直是太过酸爽。非常有必要定时删除代码中旧的gflags,根据其开关打开(true)和关闭(false)情况来删除gflags及其相关代码。
  • 规范3:清理旧的gflags时应同时删除相应的gflags配置,以保证线上配置的整洁。
    配置文件和代码保持同步是一种非常好的开发和维护体验。
  • 规范4:if语句中应尽量避免gflags参与逻辑运算。
    当if语句中出现与gflags相关的与、或、非逻辑运算时,事情就会变得复杂起来,gflags的开关状态不再是唯一的决定因素,代码阅读和删除gflags也会变得十分困难。我曾经删错过一个旧的gflags,幸运的是在CR阶段(CodeReview,代码审查)被细心的同事指出,避免了一次踩坑。这是那段令我难忘的代码的样子:
    在这里插入图片描述
    在这个例子中同时出现了与、或、非这3个逻辑运算,它是工程中真实存在的代码,绝非由我杜撰。此时gflags a、b、c、d的值都是false,现在的任务是删除这些旧的gflags和它们包住的代码,应该保留哪部分代码呢?如果你和我一样忘记了或运算||和与运算&&谁的优先级更高,那么掉到坑里的概率非常大。言归正传,我们有很多方法避免这样的代码出现,gflags绝不应该参与复杂的逻辑运算。
  • 规范5:公共模块的gflags应尽快删除。
    公共模块的gflags在上线运行一段时间后,应尽快删除,理想的情况是公共模块都没有gflags包含。这样做的理由是使用公共模块往往不知道这些gflags的存在,非常容易留坑。
  • 规范6:不要在单元测试代码中使用gflags。
    如果UT(UnitTest,单元测试)代码里用到了gflags,情况会变得复杂,在删除旧的gflags时需要同步修改单元测试代码,否则会导致测试失败,jenkins上的任务会变红(即测试失败)。因此,最好不要在单元测试代码里使用gflags。
  • 规范7:提交代码时,应记录新增或删除的gflags配置。
    这样的好处是方便测试的同事进行测试,这样利人利己的规范是非常值得遵守的。
    最后,我把这7条规范总结并整理成一张图片,欢迎大家留言补充更多、更好的gflags使用规范。
    在这里插入图片描述
  • 金句分享

人的天性之一,就是不会接受别人的批评,总是认为自己永远是对的,喜欢找各种各样的借口为自己辩解。
——出自《人性的弱点》,戴尔·卡耐基(Dale Carnegie),美国著名人际关系学大师。
解读:永远不要批评别人,因为指责只不过是在浪费自己和他人的时间,应该换种方式去沟通和解决问题。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值