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需要包含頭檔案 :
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的頭檔案
指令行參數傳遞
接下來定義了一些需要從指令行傳遞參數的變量
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 |
false | argv和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),美國著名人際關系學大師。
解讀:永遠不要批評别人,因為指責隻不過是在浪費自己和他人的時間,應該換種方式去溝通和解決問題。