fmt vs sprintf vs stringstream 速度比較

·

1 min read

目標

粗略的比較這些字串拼接函數

  • fmt::format: 並不是 c++20 的 std::format ,是在 ubuntu20.04 的 apt repo 裡面可以直接安裝的 libfmt-dev: 6.1.2
  • std::sprintf: c 本來就有的函數
  • std::stringstream: c++ 的另一種字串拼接方式

由於我是在嘗試一個把混有字串和數字的 tuple 轉換成 string 的方式, example:

struct AreaData {
    std::string stateName;
    int typeID;
};

我希望能夠有「toString(a1) == toString(a2) => a1 == a2」,所以我會用這種方式把 string 拼進去:

{str.length}\t{str.data}\t

這樣就做到那件事。

而測試時也只會測試把一個字串和一個數字披在一起:

{stateName.size}\t{stateName.data}\t{typeID}\t

Code

fmt::format

這不需要什麼特別注意的地方,不過為了安全,我在拼接 string 時是用指定 (data, size) 的方式:{:.{}}

fmt::format("{}\t{:.{}}\t{}\t", strv1.length(), strv1.data(), strv1.length(), intv);

Sprintf

本來的 sprintf 並不安全,因為他是吃一個指標,並且沒有長度的檢查。所以要用 snprintf

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    auto size = static_cast<size_t>( size_s );
    std::unique_ptr<char[]> buf( new char[ size ] );
    std::snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

然後根據 Using printf with a non-null terminated string 做出的 format string:

string_format("%d\t%.*s\t%d\t", strv.length(), strv.length(), strv.data(), intv);

std::stringstream

std::stringstream ss;
ss << strv1.length() << "\t";
ss.write(strv1.data(), strv1.length()) << "\t";
ss << intv << "\t";
auto val = ss.str();

結果與結論

在 ubuntu 20.04 環境,每個都跑 10^8 次數下的結果:

  • fmt::format: 15 秒
  • sprintf: 39 秒
  • std::stringstream: 60 秒

節論: fmt::format 是個不錯的選擇,不過假如在不需要考慮速度,並且也不太希望增加依賴的情況其實也沒有很必要?