天天看點

萬兆流量下,如何正确使用字元串進行處理

作者:小莫快跑0577

本系列主要記錄筆者在網絡流量解析開發過程中,碰到的一些性能問題。

何為Copy-On-Write

即寫時複制,也叫成為引用計數。這種政策可以消除不必要的記憶體配置設定和不必要的字元拷貝,進而可以提高程式運作效率。比如:

void Print(std::string str)
{
	std::cout << str << std::endl;
}           

上述代碼中,參數傳值重新構造str并不涉及深拷貝,隻進行了一次淺拷貝。

引用技術本身是沒有問題的。問題出在什麼時候進行深拷貝?下面來看gcc-4.8.5中string::operator[]的實作。

const_reference
operator[] (size_type __pos) const
{
    __glibcxx_assert(__pos <= size());
    return _M_data()[__pos];
}

reference
operator[](size_type __pos)
{
    __glibcxx_assert(__pos <= size());

    _M_leak();
    return _M_data()[__pos];
}           

問:為什麼需要區分這兩種?

答:我們可以看到有兩個重載函數。其中一個是常量函數(不涉及字元串更改),另一個是普通函數(可能會改動字元串)。普通函數傳回了字元串引用,這次調用是可能出現修改字元串的。但是,這個string本身可能有多個引用副本,這時候就需要進行Copy。不複制就會意外修改其他無辜string。對于前面的複制操作也失去了意義。

問題一:在常量場合使用非常量操作

先來看一段代碼。

int StringFind(String str)
{
  for (int i=0; i<str.size(); i+=)
  {
  	if (isprint(str[i]))
    	return i;
  }
  return -1;
}           

上述代碼簡單地傳回第一個可列印字元串位置。整個操作其實是不修改str的。但是由于str不是常量,預設調用了普通的operator[]。是以,第一次調用str[i]會進行一次字元串的深拷貝。如果字元串很長,則在這個函數中消耗大量的性能。如果是多線程情況,由于_M_mutate()是會進行上鎖操作,也很影響性能。

如何解決?看一下執行個體:

int StringFind(const String& str)
{
  for (int i=0; i<str.size(); i+=)
  {
  	if (isprint(str[i]))
    	return i;
  }
  return -1;
}           

我們隻需要将參數改為const類型的引用就可以避免這個問題。

問題二:在非常量場合違規操作

經常調試性能的同學都知道,直接char* 字元串操作性能遠大于string提供的API。如:

void StringRelace(String& str)
{
  char* ptr = str.c_str(); // 也可以調用str.data()
  for (int i=0; i<str.size(); i+=)
  {
  	if (! isprint(str[i]))
    	ptr[i] = '.';
  }
}           

上述代碼實作輸入一個字元串将不可見字元轉換為點'.'。可以看到,為了性能取巧地使用了str.c_str()先獲得字元串位址,然後進行周遊修改。看似沒問題?問題大了!

我們上面說過字元串存在Copy-On-Write, 這個str不知道多少個string與之共享同一個位址。這時候str不通過string官方的方式進行修改,就會造成其他string也一起被改變。那這裡該如何操作?簡單!我們執行一次非const的operator[]不就行了。

void StringRelace(String& str)
{
  char* ptr = &str[0];
  for (int i=0; i<str.size(); i+=)
  {
  	if (! isprint(str[i]))
    	ptr[i] = '.';
  }
}           

将char* ptr = str.c_str(); 改為 char* ptr = &str[0];。進行一次寫時深拷貝,再進行操作。

結束語

以上便是std::string引用計數相關的介紹。開始寫代碼時,const,static這些關鍵字不是很了解,隻有坑過了之後才領悟深刻。這兩個關鍵字,隻要能加的場合盡量加上,不僅是閱讀代碼時必要,也更是編譯器優化時需要的。

繼續閱讀