抽換"運算"的前後 Part I

目的

如果我們有一個演算法,我們要為他測試他的運行時間,最直接的作法就是: 在他運算之前,將當前時間記錄在一個變數 start_timestamp作為起始時間點。等到運算結束之後,我們再取一次當前時間,並減去 start_timestamp,就可以獲得他的經歷時間。

要為運算邏輯在它的前後,貼上時間記錄的程式碼。

作法

目前我想到作法有下列三種,各有優缺點,其作法原本的目的其實也不一樣,但是都能夠為我們作到想要的: 抽換運算前後的程式碼。

  • Template Pattern
  • RAII
  • Decorator Pattern
  • CRTP

Template Pattern

Template Pattern 的原義其實就是 Base Class 的作者,想要為某個 method A 做好前後修飾的動作,分別叫 setup()teardown(),於是乎就開放了一個公開介面 method B,只準客户端從這裡呼叫。所有從外部想要運行 method A 的邏輯的人,都一定要先走過 Base Class 作者所留下的 setup() 實作,與運行 method A 完所執行的 teardown() 實作

Base Class 會將 method A 開放成一個 可覆寫的 virtual function,跟 Subclass 的作者傳達一個訊息: 想要讓你的程式碼,可以在 run 之後與 run 之後,運行我的這兩個實作,就可以藉由覆寫此 method A 來達到。

今天我們就來當一下 Base Class 的作者,將時間記錄的程式碼,寫在 setup()teardown() 中。然後再把要記時的函式放在 Subclass 的 method A 中。

class Benchmark
{public:
  void run()
  {
    setup();
    process_data();
    teardown();
  };
protected:
  virtual void process_data() = 0;
private:
  void setup()
  {
    m_timestamp = ::GetTime();
  }
  void teardown()
  {
    auto duration = ::GetTime() - m_timestamp;
    std::cout << duration << L" ms.";
  }
  double m_timestamp;
};

class ImageProcessBenchmark : public Benchmark
{protected:
  virtual void process_data()
  { 
    // algorithm logic
  };
};  
client code
void run_benchmark(Benchmark& benchmark)
{
  benchmark.run();
}
ImageProcessBenchmark round1;
run_benchmark(round1);

RAII

RAII 意指變數一宣告好,資源即初始化。白話一點就是: 把變數的後事辦好好。當一個變數在生命週期結束後,理當要去作一些資源釋放的動作,像是 file handle 要 fclose、db connection 要 disconnect、newed variable 要 delete。一般都是由 client 端去手動執行這些釋放的動作。但是會有三種情況會意外地無法作到這些的動作。

  • 忘記寫。
  • function 中途 return。
  • exception 丟出時發生的 stack unwinding。

於是乎就有人想說為何不將這些資源清理的動作放進 destructor 中,讓變數過世的時候,自動去執行 destructor,不用去手動地去作這些事情。

那麼我們可以作的事就是,把記錄時間的程式碼,放進 class 的 constructor 與 destructor 中。那麼所記錄的時間就是等於在描述此 class instance 的壽命。那麼我們就可以在要記錄的 function 開頭定義此 class 的 instance,讓它與計算的邏輯共存亡,也就是間接描述到演算法的計算時間。

class Benchmark
{public:
  Benchmark()
  {
      m_timestamp = ::GetTime();
  };
  void ~Benchmark()
  {
    auto duration = ::GetTime() - m_timestamp;
    std::cout << duration << L" ms.";
  }
private:
  double m_timestamp;
};

void RunImageProcess()
{
  Benchmark benchmark;
  // algorithm logic
}
client code
RunImageProcess();

Decorator Pattern

Decroator Pattern 意指,若有一個 instance A,我要模仿此 A 的所有公開介面,然後在 A 的每一個 method 中,forward 其 A 的 method,並在 forward method 的前後加上我想修飾的邏輯。讓用户在使用被修飾 instance A 時,就好像真的在用 A 一樣,只是在每個 method 的前後,可能就會多了 decorator class 的 善意

那麼我們可以來 decorate 我們的演算法,讓他在 run 的前後,加上我們的記錄時間的裝飾。

class DataProcess
{public:
  virtual void run() = 0;
};

class ImageProcess : public DataProcess
{public:
  virtual void run() 
  {
    // algorithm logic
  }
};

class TimeEstimatedDataProcess : public DataProcess
{public:
  TimeEstimatedDataProcess(DataProcess* p_data_process)
    :m_p_inner_data_process(p_data_process){}  
  virtual void run() 
  {  
    double timestamp = ::GetTime();
    m_p_inner_data_process->run();
    auto duration = ::GetTime() - timestamp;
    std::cout << duration << L" ms.";
  }
  DataProcess* m_p_inner_data_process;
};
client code
void run_process(DataProcess& data_process)
{
  data_process.run();
}
// 原本的 100% 純計算
ImageProcess image_process;
run_process(image_process); 

// 有加料的
TimeEstimatedDataProcess image_process_with_report (&image_process);
run_process(image_process_with_report);

CRTP

明天再寫...

comments powered by Disqus