Protobuf是一種可以實現記憶體與外存交換的協議介面。這是由Google開發的開源工具,目前研究Caffe源碼時用到。
一個軟體項目 = 資料結構 + 演算法 + 參數,對於資料結構和演算法我們都已經有較多研究,但不同開發人員對參數管理卻各有千秋。有人喜歡TXT格式化的參數檔案,有人喜歡BIN簡單高效,也有人喜歡圖形化介面的直觀。不一致的參數管理帶來很多問題,例如一個項目組內不同成員必須約定一套統一的參數方案,或者稱為通訊協定,這樣便於模組整合。而Protobuf工具就完美解決了這個問題,關鍵區段代碼自動產生,節省了大量的開發、調試時間。
首先下載protobuf,地址(打不開。……不解釋)
這裡用Linux版本2.5.0
解壓:
tar zxvf protobuf-2.5.0.tar.gz
切到主目錄:
cd protobuf-2.5.0
編譯:
./configure
make
sudo make install
添加環境變數:
export PKG_CONFIG_PATH=$(pwd)
編譯examples:
cd examples/
make cpp
這裡我們只編譯C++代碼。
編譯完成,產生了以下可執行檔:
add_person_cpp
list_people_cpp
這是個通訊錄的例子。我們首先運行add_person_cpp:
./add_person_cpp zykzyk: File not found. Creating a new file.Enter person ID number: 123Enter name: zhaoyongkeEnter email address (blank for none): zhaoyongke@yeah.netEnter a phone number (or leave blank to finish): 188188188Is this a mobile, home, or work phone?(斷行符號)Unknown phone type. Using default.Enter a phone number (or leave blank to finish):(斷行符號)
然後運行list_people_cpp:
./list_people_cpp zykPerson ID: 123 Name: zhaoyongke E-mail address: zhaoyongke@yeah.net Home phone #: 188188188
可見我們產生了新的通訊錄zyk,裡面儲存了相應的資訊。
例子運行結束了,我們看下代碼是如何產生的。
protobuf使用前,先編寫proto檔案,這是描述我們需要配置參數的資料結構。這個例子裡面的proto如下:
// See README.txt for information and build instructions.package tutorial;option java_package = "com.example.tutorial";option java_outer_classname = "AddressBookProtos";message Person { required string name = 1; required int32 id = 2; // Unique ID number for this person. optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4;}// Our address book file is just one of these.message AddressBook { repeated Person person = 1;}
前幾行是定義包的,可以忽略。
message Person{...}定義了一個需要傳輸的參數結構體,可見包括這麼幾個單元:name(string類型)、id(int32類型)、email(string類型)、phone(PhoneNumber類型,嵌套在Person內的類)。前面標記為“required”是必須有值的,而“optional“則為可選項,”repeated“表示後面單元為相同類型的一組向量。
有了如上定義,我們可以用protobuf工具產生介面代碼,命令如下:
protoc --cpp_out=. addressbook.proto
運行後產生了兩個檔案:addressbook.pb.cc 和addressbook.pb.h,代碼比較長就不貼了。我們的應用程式可以通過自動產生的介面實現參數的序列化/還原序列化,代碼如下:
//add_person.c#include <iostream>#include <fstream>#include <string>#include "addressbook.pb.h"using namespace std;// This function fills in a Person message based on user input.void PromptForAddress(tutorial::Person* person) { cout << "Enter person ID number: "; int id; cin >> id; person->set_id(id); cin.ignore(256, '\n'); cout << "Enter name: "; getline(cin, *person->mutable_name()); cout << "Enter email address (blank for none): "; string email; getline(cin, email); if (!email.empty()) { person->set_email(email); } while (true) { cout << "Enter a phone number (or leave blank to finish): "; string number; getline(cin, number); if (number.empty()) { break; } tutorial::Person::PhoneNumber* phone_number = person->add_phone(); phone_number->set_number(number); cout << "Is this a mobile, home, or work phone? "; string type; getline(cin, type); if (type == "mobile") { phone_number->set_type(tutorial::Person::MOBILE); } else if (type == "home") { phone_number->set_type(tutorial::Person::HOME); } else if (type == "work") { phone_number->set_type(tutorial::Person::WORK); } else { cout << "Unknown phone type. Using default." << endl; } }}// Main function: Reads the entire address book from a file,// adds one person based on user input, then writes it back out to the same// file.int main(int argc, char* argv[]) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) { cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook address_book; { // Read the existing address book. fstream input(argv[1], ios::in | ios::binary); if (!input) { cout << argv[1] << ": File not found. Creating a new file." << endl; } else if (!address_book.ParseFromIstream(&input)) { cerr << "Failed to parse address book." << endl; return -1; } } // Add an address. PromptForAddress(address_book.add_person()); { // Write the new address book back to disk. fstream output(argv[1], ios::out | ios::trunc | ios::binary); if (!address_book.SerializeToOstream(&output)) { cerr << "Failed to write address book." << endl; return -1; } } // Optional: Delete all global objects allocated by libprotobuf. google::protobuf::ShutdownProtobufLibrary(); return 0;}
可見只需要調用addressbook.pb.h中聲明的tutorial::AddressBook類、Person類中的介面(add_person(), add_phone(), set_number(), set_email()等)就能操作相應的參數,最後將記憶體中的參數序列化為檔案只需要執行SerializeToOstream()。相應的讀取參數檔案的操作為ParseFromIstream()。這裡貼出例子中的第二個程式如下:
// list_people.c#include <iostream>#include <fstream>#include <string>#include "addressbook.pb.h"using namespace std;// Iterates though all people in the AddressBook and prints info about them.void ListPeople(const tutorial::AddressBook& address_book) { for (int i = 0; i < address_book.person_size(); i++) { const tutorial::Person& person = address_book.person(i); cout << "Person ID: " << person.id() << endl; cout << " Name: " << person.name() << endl; if (person.has_email()) { cout << " E-mail address: " << person.email() << endl; } for (int j = 0; j < person.phone_size(); j++) { const tutorial::Person::PhoneNumber& phone_number = person.phone(j); switch (phone_number.type()) { case tutorial::Person::MOBILE: cout << " Mobile phone #: "; break; case tutorial::Person::HOME: cout << " Home phone #: "; break; case tutorial::Person::WORK: cout << " Work phone #: "; break; } cout << phone_number.number() << endl; } }}// Main function: Reads the entire address book from a file and prints all// the information inside.int main(int argc, char* argv[]) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 2) { cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; return -1; } tutorial::AddressBook address_book; { // Read the existing address book. fstream input(argv[1], ios::in | ios::binary); if (!address_book.ParseFromIstream(&input)) { cerr << "Failed to parse address book." << endl; return -1; } } ListPeople(address_book); // Optional: Delete all global objects allocated by libprotobuf. google::protobuf::ShutdownProtobufLibrary(); return 0;}
相信做完這個實驗,你將不再對Caffe代碼中的參數初始化、參數儲存操作感到陌生,一切都很自然。
除了上述簡易功能,Protobuf還可以用來傳遞不同語言(C/C++與Java、Python)之間的參數,省去了自己手動維護資料結構的繁瑣工作。也可以支援用戶端/伺服器模式,在主機/從機之間傳遞參數。