心心念唸的最佳化完成了,雖然不是很完美

架構師修行手冊發表於2023-05-11

來源:高效能架構探索

你好,我是雨樂!

核心模組有個功能點,一直以來想著將其最佳化掉(雖然線上上穩定執行了這麼多年),要麼沒時間,要麼懶的搞,一拖再拖。期間也想了各種方案,無奈不是很完美,恰好吳老師進群了,隨向有著20多年經驗的吳老師進行了請教,也跟A總,E總等進行了討論,慢慢的也有了最佳化思路,於是用了大概一天的時間,基於這幾個大佬的方案,進行了最佳化。

需求

專案中有這樣一個需求,根據一個類別名以及其對應的型別,建立對應的資料結構。需求很簡單吧。。。

最佳化前的版本,先建立一個配置,然後程式啟動的時候,載入跟配置,然後根據配置內容進行相應的操作。

配置如下:

config.json

{
"phone_brand":"string",
"gender":"int"
}

解析如下:

// parse config.json
if (pare("config.json").failed()) {
  return;
}
for (const auto & item : parse.elements()) {
  auto name = item.first();
  auto type = item.second();
  if (type == "string") {
    mp[name] = std::make_shared<Session<std::string>>(name);
  } else if (type == "int") {
    mp[name] = std::make_shared<Session<int>>(name);
  }
}

其實,說實話,如果沒有潔癖的話,這段程式碼也不是不可行?。也線上穩定執行了幾年,不過,多少感覺這種方式很傻瓜,就像要求寫一個演算法,而自己實現了一個冒泡一樣,雖然功能滿足,但方案並不優雅。

方案一: typelist

既然需求是根據字串型別來建立對應的資料型別,那麼不妨把各種資料型別結合起來,而支援多種資料型別的,對於這種多型別的,第一時間想到了std::variant和std::tuple,不過因為std::variant使用上的限制以及實現本功能的話需要增加很多判斷程式碼,所以最終選擇了std::tuple來實現:

using types = std::tuple<intdouble, std::string, int64_t>;

template<std::size_t N>
using StrType = typename std::tuple_element<N, types>::type;

constexpr bool strings_equal(const char* a, const char* b) {
    return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1));
}

constexpr std::size_t getIdx(const char* name) 
{
    return strings_equal(name, "int") ? 0:
           strings_equal(name, "double") ? 1:
           strings_equal(name, "std::string") ? 2:
           strings_equal(name, "int64_t") ? 3 : 4;
}

int main() {
    if (pare("config.json").failed()) {
      return;
    }
    for (const auto & item : parse.elements()) {
      auto name = item.first();
      auto type = item.second();
      mp[name] = std::make_shared<Session<StrType<getIdx(type)>>>(name);
    }
}

在上述中,依然採取配置檔案的方式,建立了一個支援int、string等型別的std::tuple,並透過getIdx和strings_equal來獲取該型別在tuple中的index,進而建立相應的型別。

其實,如果把該方案跟現有實現(第一個)相比較的話,並沒有變得多優雅,反而多了很多程式碼。。。

方案二: reflection

其實,這種需求從概念上講,應該是reflection,中文稱為反射,眾所周知C++標準委員會那幫人不食人間煙火,也一直沒有將反射納入標準。這塊也在群裡進行了討論,也聊了java中反射的實現機制和其弊端。其間,吳老師也發表了相關意見,原來對於反射這塊,於13年就專門成立了反射研究組,只是一直沒達到能進標準的共識。具體可以參考靜態反射

如果對需求進行重構的話,我的需求也比較簡單,就是一個struct,裡面有各種變數,需要實現一個功能,就是獲取struct中的變數list以及對應的變數內容:

struct Config {
  int a;
  std::string b;
};

void fun() {
  Config cfg;
  std::vector<Filed> fileds = GetFileds(cfg);
  for (const auto & item : fileds) {
    auto name = item.name();
    auto c = std::make_shared<item::type>();
    // do other sth
  }
}

於是在gayhub上也調研了實現,沒有一個特別滿意的方法,因為專案中大量用到了pb,所以藉助pb的反射功能來進行實現:

message Config {
int32 a;
string b;
}

Config category;
const google::protobuf::Descriptor* desc = category.GetDescriptor();
std::vector<const google::protobuf::FieldDescriptor *> vfd;

for (int i = 0; i <  desc->field_count(); ++i) {
    const google::protobuf::FieldDescriptor* fd = desc->field(i);
    const auto &category_name = fd->name();
    switch(fd->cpp_type()) {
      case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
        auto name = item.name();
        auto c = std::make_shared<std::string>();
        break;
      }
      case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: {
        auto name = item.name();
        auto c = std::make_shared<uint32_t>();
        break;
      }
      default:
    break;
  }
}

上面這塊藉助於pb也基本滿足了需求,唯一不足的是需要有這個switch case,需要將pb的type轉換成cpp支援的type,不過不過怎麼說,也比現線上上的方式要優雅的多。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027824/viewspace-2951462/,如需轉載,請註明出處,否則將追究法律責任。

相關文章