提高C++性能的编程技术笔记:引用计数+测试代码
引用計數(reference counting):基本思想是將銷毀對象的職責從客戶端代碼轉移到對象本身。對象跟蹤記錄自身當前被引用的數目,在引用計數達到零時自行銷毀。換句話說,對象不再被使用時自行銷毀。
引用計數和執(zhí)行速度之間的關系是與上下文緊密關聯的。該關系取決于以下幾個因素:
(1). 目標對象的資源消耗量集中于哪些方面?如果目標對象使用過多內存,比如未保護內存,將使可用內存受限,并導致顯著的性能損失,表現為緩存命不中和頁面錯誤。
(2). 分配(釋放)目標對象所使用資源的代價有多高?即便從堆中分配1字節(jié)空間也需要執(zhí)行數百條指令。釋放時亦然。
(3). 可能有多少對象共享目標對象的單個實例?通過使用賦值操作符和拷貝構造函數可以提升共享率。
(4). 創(chuàng)建(銷毀)第一個(最后一個)目標對象引用的額度有多高?使用構造函數而不是拷貝構造函數創(chuàng)建新的引用計數對象時,會產生對目標對象的初次引用。這種操作代價高昂。與創(chuàng)建一個新的目標對象相比,這種方法包含更多開銷。移除最后一次引用時也是如此。
對于預先存在且無法修改的類,引用計數依然可以實現,只不過,這種情況需要對設計進行一些修改。也可以在多線程執(zhí)行環(huán)境下處理引用計數。在這種情況下,多個線程可能會并發(fā)訪問同一個引用計數對象。因此必須對維持引用計數的變量進行保護,使其對進行的更新是原子操作。原子更新操作要求有一個鎖定機制。
引用計數在性能上并非無往不勝。引用計數、執(zhí)行時間和資源維護會產生微妙的相互作用,如果對性能方面的考慮很重要,就必須對這幾個方面仔細進行評估。引用計數是提升還是損害性能取決于其使用方式。下面的任意一種情況都可以使引用計數變得更為有效:目標對象是很大的資源消費者;資源分配和釋放的代價很高;高度共享,由于使用賦值操作符和拷貝構造函數,引用計數的性能可能會很高;創(chuàng)建和銷毀引用的代價低廉。反之,則應跳出引用計數而轉為使用更加有效的簡單非計數對象。
以下是測試代碼(reference_counting.cpp):
#include "reference_counting.hpp"
#include <iostream>
#include <chrono>
#include <string>
#include <string.h>namespace reference_counting_ {// reference: 《提高C++性能的編程技術》:第十二章:引用計數
namespace {// 為使引用計數更加方便,我們需要為每個BigInt對象關聯一個引用計數。
// 實現這一操作的方式有兩種,為BigInt直接添加refCount成員或使其繼承自基類RCObject
// RCObject是引用計數對象的基類,并且封裝了所有引用計數變量以及針對這些變量的操作
class RCObject {
public:void addReference() { ++refCount; }void removeReference() { if (--refCount == 0) delete this; }void markUnshareable() { shareable = false; }bool isShareable() const { return shareable; }bool isShared() const { return refCount > 1; }protected:RCObject() : refCount(0), shareable(true) {}RCObject(const RCObject& rhs) : refCount(0), shareable(true) {}RCObject& operator=(const RCObject& rhs) { return *this; }virtual ~RCObject() {}private:int refCount;bool shareable;
};// BigInt類的功能是將正整數表示成用二進制編碼的十進制形式
class BigInt : public RCObject {
friend BigInt operator+ (const BigInt&, const BigInt&);public:BigInt(const char*);BigInt(unsigned = 0);BigInt(const BigInt&);BigInt& operator= (const BigInt&);BigInt& operator+= (const BigInt&);~BigInt();char* getDigits() const { return digits; }unsigned getNdigits() const { return ndigits; }void print() { fprintf(stdout, "digits: %s\n", digits); }private:char* digits;unsigned ndigits;unsigned size; // 分配的字符串的大小BigInt(const BigInt&, const BigInt&);char fetch(unsigned i) const;
};BigInt::BigInt(unsigned u)
{unsigned v = u;for (ndigits = 1; (v/=10) > 0; ++ndigits) {;}digits = new char[size=ndigits];for (unsigned i = 0; i < ndigits; ++i) {digits[i] = u%10;u /= 10;}
}BigInt::~BigInt()
{delete [] digits;
}BigInt& BigInt::operator=(const BigInt& rhs)
{if (this == &rhs) return *this;ndigits = rhs.ndigits;if (ndigits > size) {delete [] digits;digits = new char[size = ndigits];}for (unsigned i = 0; i < ndigits; ++i) {digits[i] = rhs.digits[i];}return *this;
}BigInt::BigInt(const char* s)
{if (s[0] == '\0') {s = "0";}size = ndigits = strlen(s);digits = new char[size];for (unsigned i = 0; i < ndigits; ++i) {digits[i] = s[ndigits-1-i] - '0';}
}BigInt::BigInt(const BigInt& copyFrom)
{size = ndigits = copyFrom.ndigits;digits = new char[size];for (unsigned i = 0; i < ndigits; ++i) {digits[i] = copyFrom.digits[i];}
}// BigInt = left + right
BigInt::BigInt(const BigInt& left, const BigInt& right)
{size = 1 + (left.ndigits > right.ndigits ? left.ndigits : right.ndigits);digits = new char[size];ndigits = left.ndigits;for (unsigned i = 0; i < ndigits; ++i) {digits[i] = left.digits[i];}*this += right;
}inline char BigInt::fetch(unsigned i) const
{return i < ndigits ? digits[i] : 0;
}BigInt& BigInt::operator+=(const BigInt& rhs)
{unsigned max = 1 + (rhs.ndigits > ndigits ? rhs.ndigits : ndigits);if (size < max) {char* d = new char[size=max];for (unsigned i = 0; i < ndigits; ++i) {d[i] = digits[i];}delete [] digits;digits = d;}while (ndigits < max) {digits[ndigits++] = 0;}for (unsigned i = 0; i < ndigits; ++i) {digits[i] += rhs.fetch(i);if (digits[i] >= 10) {digits[i] -= 10;digits[i+1] = 1;}}if (digits[ndigits-1] == 0) {--ndigits;}return *this;
}std::ostream& operator<<(std::ostream& os, BigInt& bi)
{char c;const char* d = bi.getDigits();for (int i = bi.getNdigits() - 1; i >= 0; i--) {c = d[i] + '0';os << c;}os << std::endl;return os;
}inline BigInt operator+(const BigInt& left, const BigInt& right)
{return BigInt(left, right);
}// 智能指針:這種特殊指針的任務是對引用計數進行登記
template<class T>
class RCPtr {
public:RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }~RCPtr() { if (pointee) pointee->removeReference(); }RCPtr& operator=(const RCPtr& rhs);T* operator->() const { return pointee; }T& operator*() const { return *pointee; }private:T* pointee;void init();
};template<class T>
void RCPtr<T>::init()
{if (0 == pointee) return;if (false == pointee->isShareable()) {pointee = new T(*pointee);}pointee->addReference();
}template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{if (pointee != rhs.pointee) {if (pointee) pointee->removeReference();pointee = rhs.pointee;init();}return *this;
}// RCBigInt:構造BigInt的引用計數實現,RCBigInt需要指向一個真正的BigInt對象
// 如果是頻繁賦值和復制RCBigInt對象的工作,RCBigInt會大顯身手。另外,與直接使用簡單BigInt相比,
// 由于創(chuàng)建了對新BigInt對象的初次引用,使用新RCBigInt對象的代價要更高一些。每當RCBigInt產生了
// 對BigInt的初次引用時,就會在堆內存中創(chuàng)建一個BigInt對象并指向它。對于基于堆棧(局部變量)的簡單
// BigInt對象而言,則不必付出這種代價。此類的情況在移除最后一次BigInt引用時也會發(fā)生。這是因為
// 底層的BigInt對象被釋放并還給堆內存。
class RCBigInt {friend RCBigInt operator+(const RCBigInt&, const RCBigInt&);public:RCBigInt(const char* p) : value(new BigInt(p)) {}RCBigInt(unsigned u = 0) : value(new BigInt(u)) {}RCBigInt(const BigInt& bi) : value(new BigInt(bi)) {}void print() const { value->print(); }private:RCPtr<BigInt> value;
};inline RCBigInt operator+(const RCBigInt& left, const RCBigInt& right)
{return RCBigInt(*(left.value) + *(right.value));
}} // namespaceint test_reference_counting_1()
{std::chrono::high_resolution_clock::time_point time_start, time_end;const int count{10000000};{ // test BigInt createtime_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {BigInt a = i;BigInt b = i + 1;BigInt c = i + 2;BigInt d = i + 3;}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "BigInt create time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test RCBigInt create// RCBigInt測試會更多地忙于初次引用的創(chuàng)建及之后將其銷毀的工作time_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {RCBigInt a = i;RCBigInt b = i + 1;RCBigInt c = i + 2;RCBigInt d = i + 3;}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "RCBigInt create time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test BigInt assignBigInt a, b, c;BigInt d = 1;time_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {a = b = c = d;}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "BigInt assign time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test RCBigInt assign// 對RCBigInt對象的賦值操作效率高于BigInt對象,但是創(chuàng)建和銷毀對象時卻相反RCBigInt a, b, c;RCBigInt d = 1;time_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {a = b = c = d;}time_end = std::chrono::high_resolution_clock::now(); fprintf(stdout, "RCBigInt assign time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}return 0;
}} // namespace reference_counting_
執(zhí)行結果如下:
GitHub:?https://github.com/fengbingchun/Messy_Test?
總結
以上是生活随笔為你收集整理的提高C++性能的编程技术笔记:引用计数+测试代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高C++性能的编程技术笔记:标准模板库
- 下一篇: 提高C++性能的编程技术笔记:编码优化+